UNPKG

mighty-webcamjs

Version:

HTML5 Webcam Image Capture Library with Flash Fallback

1,585 lines (1,417 loc) 281 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Webcam = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it // don't break things. But we need to wrap it in a try catch in case it is // wrapped in strict mode code which doesn't define any globals. It's inside a // function because try/catches deoptimize in certain engines. var cachedSetTimeout; var cachedClearTimeout; function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } (function () { try { if (typeof setTimeout === 'function') { cachedSetTimeout = setTimeout; } else { cachedSetTimeout = defaultSetTimout; } } catch (e) { cachedSetTimeout = defaultSetTimout; } try { if (typeof clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } else { cachedClearTimeout = defaultClearTimeout; } } catch (e) { cachedClearTimeout = defaultClearTimeout; } } ()) function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.prependListener = noop; process.prependOnceListener = noop; process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],2:[function(require,module,exports){ var Filters = {}; if (typeof Float32Array == 'undefined') { Filters.getFloat32Array = Filters.getUint8Array = function(len) { if (len.length) { return len.slice(0); } return new Array(len); }; } else { Filters.getFloat32Array = function(len) { return new Float32Array(len); }; Filters.getUint8Array = function(len) { return new Uint8Array(len); }; } if (typeof document != 'undefined') { Filters.tmpCanvas = document.createElement('canvas'); Filters.tmpCtx = Filters.tmpCanvas.getContext('2d'); Filters.getPixels = function(img) { var c,ctx; if (img.getContext) { c = img; try { ctx = c.getContext('2d'); } catch(e) {} } if (!ctx) { c = this.getCanvas(img.width, img.height); ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0); } return ctx.getImageData(0,0,c.width,c.height); }; Filters.createImageData = function(w, h) { return this.tmpCtx.createImageData(w, h); }; Filters.getCanvas = function(w,h) { var c = document.createElement('canvas'); c.width = w; c.height = h; return c; }; Filters.filterImage = function(filter, image, var_args) { var args = [this.getPixels(image)]; for (var i=2; i<arguments.length; i++) { args.push(arguments[i]); } return filter.apply(this, args); }; Filters.toCanvas = function(pixels) { var canvas = this.getCanvas(pixels.width, pixels.height); canvas.getContext('2d').putImageData(pixels, 0, 0); return canvas; }; Filters.toImageData = function(pixels) { return this.identity(pixels); }; } else { onmessage = function(e) { var ds = e.data; if (!ds.length) { ds = [ds]; } postMessage(Filters.runPipeline(ds)); }; Filters.createImageData = function(w, h) { return {width: w, height: h, data: this.getFloat32Array(w*h*4)}; }; } Filters.runPipeline = function(ds) { var res = null; res = this[ds[0].name].apply(this, ds[0].args); for (var i=1; i<ds.length; i++) { var d = ds[i]; var args = d.args.slice(0); args.unshift(res); res = this[d.name].apply(this, args); } return res; }; Filters.createImageDataFloat32 = function(w, h) { return {width: w, height: h, data: this.getFloat32Array(w*h*4)}; }; Filters.identity = function(pixels, args) { var output = Filters.createImageData(pixels.width, pixels.height); var dst = output.data; var d = pixels.data; for (var i=0; i<d.length; i++) { dst[i] = d[i]; } return output; }; Filters.horizontalFlip = function(pixels) { var output = Filters.createImageData(pixels.width, pixels.height); var w = pixels.width; var h = pixels.height; var dst = output.data; var d = pixels.data; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var off = (y*w+x)*4; var dstOff = (y*w+(w-x-1))*4; dst[dstOff] = d[off]; dst[dstOff+1] = d[off+1]; dst[dstOff+2] = d[off+2]; dst[dstOff+3] = d[off+3]; } } return output; }; Filters.verticalFlip = function(pixels) { var output = Filters.createImageData(pixels.width, pixels.height); var w = pixels.width; var h = pixels.height; var dst = output.data; var d = pixels.data; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var off = (y*w+x)*4; var dstOff = ((h-y-1)*w+x)*4; dst[dstOff] = d[off]; dst[dstOff+1] = d[off+1]; dst[dstOff+2] = d[off+2]; dst[dstOff+3] = d[off+3]; } } return output; }; Filters.luminance = function(pixels, args) { var output = Filters.createImageData(pixels.width, pixels.height); var dst = output.data; var d = pixels.data; for (var i=0; i<d.length; i+=4) { var r = d[i]; var g = d[i+1]; var b = d[i+2]; // CIE luminance for the RGB var v = 0.2126*r + 0.7152*g + 0.0722*b; dst[i] = dst[i+1] = dst[i+2] = v; dst[i+3] = d[i+3]; } return output; }; Filters.grayscale = function(pixels, args) { var output = Filters.createImageData(pixels.width, pixels.height); var dst = output.data; var d = pixels.data; for (var i=0; i<d.length; i+=4) { var r = d[i]; var g = d[i+1]; var b = d[i+2]; var v = 0.3*r + 0.59*g + 0.11*b; dst[i] = dst[i+1] = dst[i+2] = v; dst[i+3] = d[i+3]; } return output; }; Filters.grayscaleAvg = function(pixels, args) { var output = Filters.createImageData(pixels.width, pixels.height); var dst = output.data; var d = pixels.data; var f = 1/3; for (var i=0; i<d.length; i+=4) { var r = d[i]; var g = d[i+1]; var b = d[i+2]; var v = (r+g+b) * f; dst[i] = dst[i+1] = dst[i+2] = v; dst[i+3] = d[i+3]; } return output; }; Filters.threshold = function(pixels, threshold, high, low) { var output = Filters.createImageData(pixels.width, pixels.height); if (high == null) high = 255; if (low == null) low = 0; var d = pixels.data; var dst = output.data; for (var i=0; i<d.length; i+=4) { var r = d[i]; var g = d[i+1]; var b = d[i+2]; var v = (0.3*r + 0.59*g + 0.11*b >= threshold) ? high : low; dst[i] = dst[i+1] = dst[i+2] = v; dst[i+3] = d[i+3]; } return output; }; Filters.invert = function(pixels) { var output = Filters.createImageData(pixels.width, pixels.height); var d = pixels.data; var dst = output.data; for (var i=0; i<d.length; i+=4) { dst[i] = 255-d[i]; dst[i+1] = 255-d[i+1]; dst[i+2] = 255-d[i+2]; dst[i+3] = d[i+3]; } return output; }; Filters.brightnessContrast = function(pixels, brightness, contrast) { var lut = this.brightnessContrastLUT(brightness, contrast); return this.applyLUT(pixels, {r:lut, g:lut, b:lut, a:this.identityLUT()}); }; Filters.applyLUT = function(pixels, lut) { var output = Filters.createImageData(pixels.width, pixels.height); var d = pixels.data; var dst = output.data; var r = lut.r; var g = lut.g; var b = lut.b; var a = lut.a; for (var i=0; i<d.length; i+=4) { dst[i] = r[d[i]]; dst[i+1] = g[d[i+1]]; dst[i+2] = b[d[i+2]]; dst[i+3] = a[d[i+3]]; } return output; }; Filters.createLUTFromCurve = function(points) { var lut = this.getUint8Array(256); var p = [0, 0]; for (var i=0,j=0; i<lut.length; i++) { while (j < points.length && points[j][0] < i) { p = points[j]; j++; } lut[i] = p[1]; } return lut; }; Filters.identityLUT = function() { var lut = this.getUint8Array(256); for (var i=0; i<lut.length; i++) { lut[i] = i; } return lut; }; Filters.invertLUT = function() { var lut = this.getUint8Array(256); for (var i=0; i<lut.length; i++) { lut[i] = 255-i; } return lut; }; Filters.brightnessContrastLUT = function(brightness, contrast) { var lut = this.getUint8Array(256); var contrastAdjust = -128*contrast + 128; var brightnessAdjust = 255 * brightness; var adjust = contrastAdjust + brightnessAdjust; for (var i=0; i<lut.length; i++) { var c = i*contrast + adjust; lut[i] = c < 0 ? 0 : (c > 255 ? 255 : c); } return lut; }; Filters.convolve = function(pixels, weights, opaque) { var side = Math.round(Math.sqrt(weights.length)); var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageData(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cy=0; cy<side; cy++) { for (var cx=0; cx<side; cx++) { var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide)); var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide)); var srcOff = (scy*sw+scx)*4; var wt = weights[cy*side+cx]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.verticalConvolve = function(pixels, weightsVector, opaque) { var side = weightsVector.length; var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageData(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cy=0; cy<side; cy++) { var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide)); var scx = sx; var srcOff = (scy*sw+scx)*4; var wt = weightsVector[cy]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.horizontalConvolve = function(pixels, weightsVector, opaque) { var side = weightsVector.length; var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageData(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cx=0; cx<side; cx++) { var scy = sy; var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide)); var srcOff = (scy*sw+scx)*4; var wt = weightsVector[cx]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.separableConvolve = function(pixels, horizWeights, vertWeights, opaque) { return this.horizontalConvolve( this.verticalConvolveFloat32(pixels, vertWeights, opaque), horizWeights, opaque ); }; Filters.convolveFloat32 = function(pixels, weights, opaque) { var side = Math.round(Math.sqrt(weights.length)); var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageDataFloat32(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cy=0; cy<side; cy++) { for (var cx=0; cx<side; cx++) { var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide)); var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide)); var srcOff = (scy*sw+scx)*4; var wt = weights[cy*side+cx]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.verticalConvolveFloat32 = function(pixels, weightsVector, opaque) { var side = weightsVector.length; var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageDataFloat32(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cy=0; cy<side; cy++) { var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide)); var scx = sx; var srcOff = (scy*sw+scx)*4; var wt = weightsVector[cy]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.horizontalConvolveFloat32 = function(pixels, weightsVector, opaque) { var side = weightsVector.length; var halfSide = Math.floor(side/2); var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageDataFloat32(w, h); var dst = output.data; var alphaFac = opaque ? 1 : 0; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var r=0, g=0, b=0, a=0; for (var cx=0; cx<side; cx++) { var scy = sy; var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide)); var srcOff = (scy*sw+scx)*4; var wt = weightsVector[cx]; r += src[srcOff] * wt; g += src[srcOff+1] * wt; b += src[srcOff+2] * wt; a += src[srcOff+3] * wt; } dst[dstOff] = r; dst[dstOff+1] = g; dst[dstOff+2] = b; dst[dstOff+3] = a + alphaFac*(255-a); } } return output; }; Filters.separableConvolveFloat32 = function(pixels, horizWeights, vertWeights, opaque) { return this.horizontalConvolveFloat32( this.verticalConvolveFloat32(pixels, vertWeights, opaque), horizWeights, opaque ); }; Filters.gaussianBlur = function(pixels, diameter) { diameter = Math.abs(diameter); if (diameter <= 1) return Filters.identity(pixels); var radius = diameter / 2; var len = Math.ceil(diameter) + (1 - (Math.ceil(diameter) % 2)) var weights = this.getFloat32Array(len); var rho = (radius+0.5) / 3; var rhoSq = rho*rho; var gaussianFactor = 1 / Math.sqrt(2*Math.PI*rhoSq); var rhoFactor = -1 / (2*rho*rho) var wsum = 0; var middle = Math.floor(len/2); for (var i=0; i<len; i++) { var x = i-middle; var gx = gaussianFactor * Math.exp(x*x*rhoFactor); weights[i] = gx; wsum += gx; } for (var i=0; i<weights.length; i++) { weights[i] /= wsum; } return Filters.separableConvolve(pixels, weights, weights, false); }; Filters.laplaceKernel = Filters.getFloat32Array( [-1,-1,-1, -1, 8,-1, -1,-1,-1]); Filters.laplace = function(pixels) { return Filters.convolve(pixels, this.laplaceKernel, true); }; Filters.sobelSignVector = Filters.getFloat32Array([-1,0,1]); Filters.sobelScaleVector = Filters.getFloat32Array([1,2,1]); Filters.sobelVerticalGradient = function(px) { return this.separableConvolveFloat32(px, this.sobelSignVector, this.sobelScaleVector); }; Filters.sobelHorizontalGradient = function(px) { return this.separableConvolveFloat32(px, this.sobelScaleVector, this.sobelSignVector); }; Filters.sobelVectors = function(px) { var vertical = this.sobelVerticalGradient(px); var horizontal = this.sobelHorizontalGradient(px); var id = {width: vertical.width, height: vertical.height, data: this.getFloat32Array(vertical.width*vertical.height*8)}; var vd = vertical.data; var hd = horizontal.data; var idd = id.data; for (var i=0,j=0; i<idd.length; i+=2,j++) { idd[i] = hd[j]; idd[i+1] = vd[j]; } return id; }; Filters.sobel = function(px) { px = this.grayscale(px); var vertical = this.sobelVerticalGradient(px); var horizontal = this.sobelHorizontalGradient(px); var id = this.createImageData(vertical.width, vertical.height); for (var i=0; i<id.data.length; i+=4) { var v = Math.abs(vertical.data[i]); id.data[i] = v; var h = Math.abs(horizontal.data[i]); id.data[i+1] = h; id.data[i+2] = (v+h)/4; id.data[i+3] = 255; } return id; }; Filters.bilinearSample = function (pixels, x, y, rgba) { var x1 = Math.floor(x); var x2 = Math.ceil(x); var y1 = Math.floor(y); var y2 = Math.ceil(y); var a = (x1+pixels.width*y1)*4; var b = (x2+pixels.width*y1)*4; var c = (x1+pixels.width*y2)*4; var d = (x2+pixels.width*y2)*4; var df = ((x-x1) + (y-y1)); var cf = ((x2-x) + (y-y1)); var bf = ((x-x1) + (y2-y)); var af = ((x2-x) + (y2-y)); var rsum = 1/(af+bf+cf+df); af *= rsum; bf *= rsum; cf *= rsum; df *= rsum; var data = pixels.data; rgba[0] = data[a]*af + data[b]*bf + data[c]*cf + data[d]*df; rgba[1] = data[a+1]*af + data[b+1]*bf + data[c+1]*cf + data[d+1]*df; rgba[2] = data[a+2]*af + data[b+2]*bf + data[c+2]*cf + data[d+2]*df; rgba[3] = data[a+3]*af + data[b+3]*bf + data[c+3]*cf + data[d+3]*df; return rgba; }; Filters.distortSine = function(pixels, amount, yamount) { if (amount == null) amount = 0.5; if (yamount == null) yamount = amount; var output = this.createImageData(pixels.width, pixels.height); var dst = output.data; var d = pixels.data; var px = this.createImageData(1,1).data; for (var y=0; y<output.height; y++) { var sy = -Math.sin(y/(output.height-1) * Math.PI*2); var srcY = y + sy * yamount * output.height/4; srcY = Math.max(Math.min(srcY, output.height-1), 0); for (var x=0; x<output.width; x++) { var sx = -Math.sin(x/(output.width-1) * Math.PI*2); var srcX = x + sx * amount * output.width/4; srcX = Math.max(Math.min(srcX, output.width-1), 0); var rgba = this.bilinearSample(pixels, srcX, srcY, px); var off = (y*output.width+x)*4; dst[off] = rgba[0]; dst[off+1] = rgba[1]; dst[off+2] = rgba[2]; dst[off+3] = rgba[3]; } } return output; }; Filters.darkenBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = a[i] < b[i] ? a[i] : b[i]; dst[i+1] = a[i+1] < b[i+1] ? a[i+1] : b[i+1]; dst[i+2] = a[i+2] < b[i+2] ? a[i+2] : b[i+2]; dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.lightenBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = a[i] > b[i] ? a[i] : b[i]; dst[i+1] = a[i+1] > b[i+1] ? a[i+1] : b[i+1]; dst[i+2] = a[i+2] > b[i+2] ? a[i+2] : b[i+2]; dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.multiplyBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = (a[i]*b[i])*f; dst[i+1] = (a[i+1]*b[i+1])*f; dst[i+2] = (a[i+2]*b[i+2])*f; dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.screenBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = a[i]+b[i]-a[i]*b[i]*f; dst[i+1] = a[i+1]+b[i+1]-a[i+1]*b[i+1]*f; dst[i+2] = a[i+2]+b[i+2]-a[i+2]*b[i+2]*f; dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.addBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = (a[i]+b[i]); dst[i+1] = (a[i+1]+b[i+1]); dst[i+2] = (a[i+2]+b[i+2]); dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.subBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = (a[i]+b[i]-255); dst[i+1] = (a[i+1]+b[i+1]-255); dst[i+2] = (a[i+2]+b[i+2]-255); dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.differenceBlend = function(below, above) { var output = Filters.createImageData(below.width, below.height); var a = below.data; var b = above.data; var dst = output.data; var f = 1/255; for (var i=0; i<a.length; i+=4) { dst[i] = Math.abs(a[i]-b[i]); dst[i+1] = Math.abs(a[i+1]-b[i+1]); dst[i+2] = Math.abs(a[i+2]-b[i+2]); dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; } return output; }; Filters.erode = function(pixels) { var src = pixels.data; var sw = pixels.width; var sh = pixels.height; var w = sw; var h = sh; var output = Filters.createImageData(w, h); var dst = output.data; for (var y=0; y<h; y++) { for (var x=0; x<w; x++) { var sy = y; var sx = x; var dstOff = (y*w+x)*4; var srcOff = (sy*sw+sx)*4; var v = 0; if (src[srcOff] == 0) { if (src[(sy*sw+Math.max(0,sx-1))*4] == 0 && src[(Math.max(0,sy-1)*sw+sx)*4] == 0) { v = 255; } } else { v = 255; } dst[dstOff] = v; dst[dstOff+1] = v; dst[dstOff+2] = v; dst[dstOff+3] = 255; } } return output; }; if (typeof require != 'undefined') { exports.Filters = Filters; } },{}],3:[function(require,module,exports){ (function() { var debug = false; var root = this; var EXIF = function(obj) { if (obj instanceof EXIF) return obj; if (!(this instanceof EXIF)) return new EXIF(obj); this.EXIFwrapped = obj; }; if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = EXIF; } exports.EXIF = EXIF; } else { root.EXIF = EXIF; } var ExifTags = EXIF.Tags = { // version tags 0x9000 : "ExifVersion", // EXIF version 0xA000 : "FlashpixVersion", // Flashpix format version // colorspace tags 0xA001 : "ColorSpace", // Color space information tag // image configuration 0xA002 : "PixelXDimension", // Valid width of meaningful image 0xA003 : "PixelYDimension", // Valid height of meaningful image 0x9101 : "ComponentsConfiguration", // Information about channels 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel // user information 0x927C : "MakerNote", // Any desired information written by the manufacturer 0x9286 : "UserComment", // Comments by user // related file 0xA004 : "RelatedSoundFile", // Name of related sound file // date and time 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally 0x9290 : "SubsecTime", // Fractions of seconds for DateTime 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized // picture-taking conditions 0x829A : "ExposureTime", // Exposure time (in seconds) 0x829D : "FNumber", // F number 0x8822 : "ExposureProgram", // Exposure program 0x8824 : "SpectralSensitivity", // Spectral sensitivity 0x8827 : "ISOSpeedRatings", // ISO speed rating 0x8828 : "OECF", // Optoelectric conversion factor 0x9201 : "ShutterSpeedValue", // Shutter speed 0x9202 : "ApertureValue", // Lens aperture 0x9203 : "BrightnessValue", // Value of brightness 0x9204 : "ExposureBias", // Exposure bias 0x9205 : "MaxApertureValue", // Smallest F number of lens 0x9206 : "SubjectDistance", // Distance to subject in meters 0x9207 : "MeteringMode", // Metering mode 0x9208 : "LightSource", // Kind of light source 0x9209 : "Flash", // Flash status 0x9214 : "SubjectArea", // Location and area of main subject 0x920A : "FocalLength", // Focal length of the lens in mm 0xA20B : "FlashEnergy", // Strobe energy in BCPS 0xA20C : "SpatialFrequencyResponse", // 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 0xA214 : "SubjectLocation", // Location of subject in image 0xA215 : "ExposureIndex", // Exposure index selected on camera 0xA217 : "SensingMethod", // Image sensor type 0xA300 : "FileSource", // Image source (3 == DSC) 0xA301 : "SceneType", // Scene type (1 == directly photographed) 0xA302 : "CFAPattern", // Color filter array geometric pattern 0xA401 : "CustomRendered", // Special processing 0xA402 : "ExposureMode", // Exposure mode 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual 0xA404 : "DigitalZoomRation", // Digital zoom ratio 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) 0xA406 : "SceneCaptureType", // Type of scene 0xA407 : "GainControl", // Degree of overall image gain adjustment 0xA408 : "Contrast", // Direction of contrast processing applied by camera 0xA409 : "Saturation", // Direction of saturation processing applied by camera 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera 0xA40B : "DeviceSettingDescription", // 0xA40C : "SubjectDistanceRange", // Distance to subject // other tags 0xA005 : "InteroperabilityIFDPointer", 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image }; var TiffTags = EXIF.TiffTags = { 0x0100 : "ImageWidth", 0x0101 : "ImageHeight", 0x8769 : "ExifIFDPointer", 0x8825 : "GPSInfoIFDPointer", 0xA005 : "InteroperabilityIFDPointer", 0x0102 : "BitsPerSample", 0x0103 : "Compression", 0x0106 : "PhotometricInterpretation", 0x0112 : "Orientation", 0x0115 : "SamplesPerPixel", 0x011C : "PlanarConfiguration", 0x0212 : "YCbCrSubSampling", 0x0213 : "YCbCrPositioning", 0x011A : "XResolution", 0x011B : "YResolution", 0x0128 : "ResolutionUnit", 0x0111 : "StripOffsets", 0x0116 : "RowsPerStrip", 0x0117 : "StripByteCounts", 0x0201 : "JPEGInterchangeFormat", 0x0202 : "JPEGInterchangeFormatLength", 0x012D : "TransferFunction", 0x013E : "WhitePoint", 0x013F : "PrimaryChromaticities", 0x0211 : "YCbCrCoefficients", 0x0214 : "ReferenceBlackWhite", 0x0132 : "DateTime", 0x010E : "ImageDescription", 0x010F : "Make", 0x0110 : "Model", 0x0131 : "Software", 0x013B : "Artist", 0x8298 : "Copyright" }; var GPSTags = EXIF.GPSTags = { 0x0000 : "GPSVersionID", 0x0001 : "GPSLatitudeRef", 0x0002 : "GPSLatitude", 0x0003 : "GPSLongitudeRef", 0x0004 : "GPSLongitude", 0x0005 : "GPSAltitudeRef", 0x0006 : "GPSAltitude", 0x0007 : "GPSTimeStamp", 0x0008 : "GPSSatellites", 0x0009 : "GPSStatus", 0x000A : "GPSMeasureMode", 0x000B : "GPSDOP", 0x000C : "GPSSpeedRef", 0x000D : "GPSSpeed", 0x000E : "GPSTrackRef", 0x000F : "GPSTrack", 0x0010 : "GPSImgDirectionRef", 0x0011 : "GPSImgDirection", 0x0012 : "GPSMapDatum", 0x0013 : "GPSDestLatitudeRef", 0x0014 : "GPSDestLatitude", 0x0015 : "GPSDestLongitudeRef", 0x0016 : "GPSDestLongitude", 0x0017 : "GPSDestBearingRef", 0x0018 : "GPSDestBearing", 0x0019 : "GPSDestDistanceRef", 0x001A : "GPSDestDistance", 0x001B : "GPSProcessingMethod", 0x001C : "GPSAreaInformation", 0x001D : "GPSDateStamp", 0x001E : "GPSDifferential" }; var StringValues = EXIF.StringValues = { ExposureProgram : { 0 : "Not defined", 1 : "Manual", 2 : "Normal program", 3 : "Aperture priority", 4 : "Shutter priority", 5 : "Creative program", 6 : "Action program", 7 : "Portrait mode", 8 : "Landscape mode" }, MeteringMode : { 0 : "Unknown", 1 : "Average", 2 : "CenterWeightedAverage", 3 : "Spot", 4 : "MultiSpot", 5 : "Pattern", 6 : "Partial", 255 : "Other" }, LightSource : { 0 : "Unknown", 1 : "Daylight", 2 : "Fluorescent", 3 : "Tungsten (incandescent light)", 4 : "Flash", 9 : "Fine weather", 10 : "Cloudy weather", 11 : "Shade", 12 : "Daylight fluorescent (D 5700 - 7100K)", 13 : "Day white fluorescent (N 4600 - 5400K)", 14 : "Cool white fluorescent (W 3900 - 4500K)", 15 : "White fluorescent (WW 3200 - 3700K)", 17 : "Standard light A", 18 : "Standard light B", 19 : "Standard light C", 20 : "D55", 21 : "D65", 22 : "D75", 23 : "D50", 24 : "ISO studio tungsten", 255 : "Other" }, Flash : { 0x0000 : "Flash did not fire", 0x0001 : "Flash fired", 0x0005 : "Strobe return light not detected", 0x0007 : "Strobe return light detected", 0x0009 : "Flash fired, compulsory flash mode", 0x000D : "Flash fired, compulsory flash mode, return light not detected", 0x000F : "Flash fired, compulsory flash mode, return light detected", 0x0010 : "Flash did not fire, compulsory flash mode", 0x0018 : "Flash did not fire, auto mode", 0x0019 : "Flash fired, auto mode", 0x001D : "Flash fired, auto mode, return light not detected", 0x001F : "Flash fired, auto mode, return light detected", 0x0020 : "No flash function", 0x0041 : "Flash fired, red-eye reduction mode", 0x0045 : "Flash fired, red-eye reduction mode, return light not detected", 0x0047 : "Flash fired, red-eye reduction mode, return light detected", 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode", 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 0x0059 : "Flash fired, auto mode, red-eye reduction mode", 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode", 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode" }, SensingMethod : { 1 : "Not defined", 2 : "One-chip color area sensor", 3 : "Two-chip color area sensor", 4 : "Three-chip color area sensor", 5 : "Color sequential area sensor", 7 : "Trilinear sensor", 8 : "Color sequential linear sensor" }, SceneCaptureType : { 0 : "Standard", 1 : "Landscape", 2 : "Portrait", 3 : "Night scene" }, SceneType : { 1 : "Directly photographed" }, CustomRendered : { 0 : "Normal process", 1 : "Custom process" }, WhiteBalance : { 0 : "Auto white balance", 1 : "Manual white balance" }, GainControl : { 0 : "None", 1 : "Low gain up", 2 : "High gain up", 3 : "Low gain down", 4 : "High gain down" }, Contrast : { 0 : "Normal", 1 : "Soft", 2 : "Hard" }, Saturation : { 0 : "Normal", 1 : "Low saturation", 2 : "High saturation" }, Sharpness : { 0 : "Normal", 1 : "Soft", 2 : "Hard" }, SubjectDistanceRange : { 0 : "Unknown", 1 : "Macro", 2 : "Close view", 3 : "Distant view" }, FileSource : { 3 : "DSC" }, Components : { 0 : "", 1 : "Y", 2 : "Cb", 3 : "Cr", 4 : "R", 5 : "G", 6 : "B" } }; function imageHasData(img) { return !!(img.exifdata); } function base64ToArrayBuffer(base64, contentType) { contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); var binary = atob(base64); var len = binary.length; var buffer = new ArrayBuffer(len); var view = new Uint8Array(buffer); for (var i = 0; i < len; i++) { view[i] = binary.charCodeAt(i); } return buffer; } function objectURLToBlob(url, callback) { var http = new XMLHttpRequest(); http.open("GET", url, true); http.responseType = "blob"; http.onload = function(e) { if (this.status == 200 || this.status === 0) { callback(this.response); } }; http.send(); } function getImageData(img, callback) { function handleBinaryFile(binFile) { var data = findEXIFinJPEG(binFile); var iptcdata = findIPTCinJPEG(binFile); img.exifdata = data || {}; img.iptcdata = iptcdata || {}; if (callback) { callback.call(img); } } if (img.src) { if (/^data\:/i.test(img.src)) { // Data URI var arrayBuffer = base64ToArrayBuffer(img.src); handleBinaryFile(arrayBuffer); } else if (/^blob\:/i.test(img.src)) { // Object URL var fileReader = new FileReader(); fileReader.onload = function(e) { handleBinaryFile(e.target.result); }; objectURLToBlob(img.src, function (blob) { fileReader.readAsArrayBuffer(blob); }); } else { var http = new XMLHttpRequest(); http.onload = function() { if (this.status == 200 || this.status === 0) { handleBinaryFile(http.response); } else { throw "Could not load image"; } http = null; }; http.open("GET", img.src, true); http.responseType = "arraybuffer"; http.send(null); } } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) { var fileReader = new FileReader(); fileReader.onload = function(e) { if (debug) console.log("Got file of length " + e.target.result.byteLength); handleBinaryFile(e.target.result); }; fileReader.readAsArrayBuffer(img); } } function findEXIFinJPEG(file) { var dataView = new DataView(file); if (debug) console.log("Got file of length " + file.byteLength); if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { if (debug) console.log("Not a valid JPEG"); return false; // not a valid jpeg } var offset = 2, length = file.byteLength, marker; while (offset < length) { if (dataView.getUint8(offset) != 0xFF) { if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset)); return false; // not a valid marker, something is wrong } marker = dataView.getUint8(offset + 1); if (debug) console.log(marker); // we could implement handling for other markers here, // but we're only looking for 0xFFE1 for EXIF data if (marker == 225) { if (debug) console.log("Found 0xFFE1 marker"); return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); // offset += 2 + file.getShortAt(offset+2, true); } else { offset += 2 + dataView.getUint16(offset+2); } } } function findIPTCinJPEG(file) { var dataView = new DataView(file); if (debug) console.log("Got file of length " + file.byteLength); if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { if (debug) console.log("Not a valid JPEG"); return false; // not a valid jpeg } var offset = 2, length = file.byteLength; var isFieldSegmentStart = function(dataView, offset){ return ( dataView.getUint8(offset) === 0x38 && dataView.getUint8(offset+1) === 0x42 && dataView.getUint8(offset+2) === 0x49 && dataView.getUint8(offset+3) === 0x4D && dataView.getUint8(offset+4) === 0x04 && dataView.getUint8(offset+5) === 0x04 ); }; while (offset < length) { if ( isFieldSegmentStart(dataView, offset )){ // Get the length of the name header (which is padded to an even number of bytes) var nameHeaderLength = dataView.getUint8(offset+7); if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1; // Check for pre photoshop 6 format if(nameHeaderLength === 0) { // Always 4 nameHeaderLength = 4; } var startOffset = offset + 8 + nameHeaderLength; var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength); return readIPTCData(file, startOffset, sectionLength); break; } // Not the marker, continue searching offset++; } } var IptcFieldMap = { 0x78 : 'caption', 0x6E : 'credit', 0x19 : 'keywords', 0x37 : 'dateCreated', 0x50 : 'byline', 0x55 : 'bylineTitle', 0x7A : 'captionWriter', 0x69 : 'headline', 0x74 : 'copyright', 0x0F : 'category' }; function readIPTCData(file, startOffset, sectionLength){ var dataView = new DataView(file); var data = {}; var fieldValue, fieldName, dataSize, segmentType, segmentSize; var segmentStartPos = startOffset; while(segmentStartPos < startOffset+sectionLength) { if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){ segmentType = dataView.getUint8(segmentStartPos+2); if(segmentType in IptcFieldMap) { dataSize = dataView.getInt16(segmentStartPos+3); segmentSize = dataSize + 5; fieldName = IptcFieldMap[segmentType]; fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize); // Check if we already stored a value with this name if(data.hasOwnProperty(fieldName)) { // Value already stored with this name, create multivalue field if(data[fieldName] instanceof Array) { data[fieldName].push(fieldValue); } else { data[fieldName] = [data[fieldName], fieldValue]; } } else { data[fieldName] = fieldValue; } } } segmentStartPos++; } return data; } function readTags(file, tiffStart, dirStart, strings, bigEnd) { var entries = file.getUint16(dirStart, !bigEnd), tags = {}, entryOffset, tag, i; for (i=0;i<entries;i++) { entryOffset = dirStart + i*12 + 2; tag = strings[file.getUint16(entryOffset, !bigEnd)]; if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd)); tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); } return tags; } function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { var type = file.getUint16(entryOffset+2, !bigEnd), numValues = file.getUint32(entryOffset+4, !bigEnd), valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart, offset, vals, val, n, numerator, denominator; switch (type) { case 1: // byte, 8-bit unsigned int case 7: // undefined, 8-bit byte, value depending on field if (numValues == 1) { return file.getUint8(entryOffset + 8, !bigEnd); } else { offset = numValues > 4 ? valueOffset : (entryOffset + 8); vals = []; for (n=0;n<numValues;n++) { vals[n] = file.getUint8(offset + n); } return vals; } case 2: // ascii, 8-bit byte offset = numValues > 4 ? valueOffset : (entryOffset + 8); return getStringFromDB(file, offset, numValues-1); case 3: // short, 16 bit int if (numValues == 1) { return file.getUint16(entryOffset + 8, !bigEnd); } else { offset = numValues > 2 ? valueOffset : (entryOffset + 8); vals = []; for (n=0;n<numValues;n++) { vals[n] = file.getUint16(offset + 2*n, !bigEnd); } return vals; } case 4: // long, 32 bit int