mighty-webcamjs
Version:
HTML5 Webcam Image Capture Library with Flash Fallback
1,585 lines (1,417 loc) • 281 kB
JavaScript
(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