mighty-webcamjs
Version:
HTML5 Webcam Image Capture Library with Flash Fallback
345 lines (306 loc) • 10.8 kB
JavaScript
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var navigator = global.navigator || {};
var ua = navigator.userAgent || '';
var isBrowser = typeof window !== 'undefined' && !ua.includes('Node.js') && !ua.includes('jsdom');
var EXIF = require('exif-js');
var blurMeasureInspectorAsync = isBrowser ? require('inspector-bokeh/async') : Promise.resolve;
var localStorage;
try {
localStorage = global.localStorage;
} catch (e) {
// volatile storage for paranoia mode
localStorage = {
getItem: function getItem(name) {
return localStorage[name];
},
setItem: function setItem(name, value) {
return localStorage[name] = value;
}
};
}
function getAndroidVersion() {
var match = ua.match(/android\s([0-9\.]*)/i);
return match ? match[1] : false;
}
function getIOSVersion() {
return !global.MSStream && function iOSversion() {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
// supports iOS 2.0 and later: <http://bit.ly/TJjs1V>
var v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/) || [0, 0, 0];
return parseFloat(parseInt(v[1], 10) + '.' + parseInt(v[2], 10));
}
}();
}
function measureBlur(canvasData) {
return new Promise(function (resolve, reject) {
if (canvasData) {
blurMeasureInspectorAsync(canvasData).then(resolve, reject);
} else {
reject('No canvasData');
}
});
}
var debouncePromise = function debouncePromise(func) {
// debouncePromise takes function, that returns promise
// and makes sure - there is just one concurrent call of promise.
// NOTE: arguments might be ignored
var isPromiseRunning = false;
var lastPromise = void 0;
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (!isPromiseRunning) {
lastPromise = new Promise(function (resolve, reject) {
isPromiseRunning = true;
var promise = func.apply(undefined, args);
var end = function end() {
isPromiseRunning = false;
};
var resolver = function resolver() {
return end(), resolve.apply(undefined, arguments);
};
var rejector = function rejector() {
return end(), reject.apply(undefined, arguments);
};
promise.then(resolver, rejector);
});
}
return lastPromise;
};
};
var whenDOMReady = function () {
// takes function, that can be called, when DOM is loaded.
var isDOMLoaded = false;
var domReadyPromise = new Promise(function (resolve) {
if (isBrowser) {
document.addEventListener('DOMContentLoaded', resolve, false);
} else {
resolve();
}
});
domReadyPromise.then(function () {
isDOMLoaded = true;
});
return function (context, func) {
// on Chrome 52 @ android mediaDevices.getUserMedia is triggered with a delay.
// functions to be triggered when camera is ready
return function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
if (isDOMLoaded) {
// synchronous call
return func.apply(context, args);
}
// asynchronous call
return domReadyPromise.then(function () {
return func.apply(context, args);
});
};
};
}();
function drawImageScaled(img, canvas) {
if (process.env.NODE_ENV !== 'production') {
console.assert(canvas instanceof HTMLCanvasElement, 'canvas passed to drawImageScaled needs to be a <canvas> element.');
console.assert(img instanceof HTMLImageElement || img instanceof Image || img instanceof HTMLCanvasElement, 'img passed to drawImageScaled need to be a <img> element.');
}
// stackoverflow.com/questions/23104582/scaling-an-image-to-fit-on-canvas#answer-23105310
var ctx = canvas.getContext('2d');
var hRatio = canvas.width / img.width;
var vRatio = canvas.height / img.height;
var ratio = Math.min(hRatio, vRatio);
var centerShift_x = (canvas.width - img.width * ratio) / 2;
var centerShift_y = (canvas.height - img.height * ratio) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, img.width, img.height, centerShift_x, centerShift_y, img.width * ratio, img.height * ratio);
}
function handleImageInput(rawFile) {
return new Promise(function (resolve, reject) {
if (!rawFile) return reject();
var reader = new FileReader();
reader.onload = function (readerEvent) {
var img = new Image();
img.onload = function () {
resolve({ // NOTE: This is not ImageData object!
rawFile: rawFile,
data: img,
width: img.width,
height: img.height
});
};
img.onerror = reject;
img.src = readerEvent.target.result;
};
reader.readAsDataURL(rawFile);
});
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function setPrefixedStyle(element, cssProp, cssValue) {
if (!element) return;
var prefixes = ['o', 'ms', 'moz', 'webkit'];
prefixes.forEach(function (prefix) {
element.style[prefix + capitalizeFirstLetter(cssProp)] = cssValue;
});
element.style[cssProp] = cssValue;
}
function detectFlash() {
// return true if browser supports flash, false otherwise
// Code snippet borrowed from: https://github.com/swfobject/swfobject
var SHOCKWAVE_FLASH = "Shockwave Flash",
SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
FLASH_MIME_TYPE = "application/x-shockwave-flash",
hasFlash = false;
if (navigator.plugins !== undefined && _typeof(navigator.plugins[SHOCKWAVE_FLASH]) === "object") {
var desc = navigator.plugins[SHOCKWAVE_FLASH].description;
if (desc && navigator.mimeTypes !== undefined && navigator.mimeTypes[FLASH_MIME_TYPE] && navigator.mimeTypes[FLASH_MIME_TYPE].enabledPlugin) {
hasFlash = true;
}
} else if (typeof ActiveXObject !== "undefined") {
try {
var ax = new ActiveXObject(SHOCKWAVE_FLASH_AX);
if (ax) {
var ver = ax.GetVariable("$version");
if (ver) hasFlash = true;
}
} catch (e) {}
}
return hasFlash;
}
function createElement(elementName, props) {
var element = document.createElement(elementName);
var uniqAttrs = ['innerHTML', 'className', 'width', 'height'];
if (props.style) {
Object.assign(element.style, props.style);
}
Object.keys(props).forEach(function (prop) {
if (prop === 'style') return;
if (uniqAttrs.includes(prop) || /^on/.test(prop)) {
element[prop] = props[prop];
} else {
element.setAttribute(prop, props[prop]);
}
});
return element;
}
function validateUploadedPhoto(img, params) {
// When selecting "Take a photo" instead of "Select from Library"
// on iOS device, not all Exif data are sent. This how we detect,
// photo was just taken.
// Android's don't allow to pick a photo from a library, so problem
// does not need to be addressed there.
// iOS 11+ has getUserMedia, but 3rdparty browsers like Chrome@iOS does not
// supprt that. EXIF verification does not work there.
return new Promise(function (resolve, reject) {
var iOSversion = getIOSVersion();
if (iOSversion && iOSversion < 11) {
EXIF.getData(img, function () {
var exif = EXIF.getAllTags(this) || {};
if (params.force_fresh_photo) {
if (exif.Orientation && !exif.ExifVersion && !exif.DateTimeOriginal) {
resolve(exif);
} else {
reject();
}
} else {
resolve(exif);
}
});
} else {
resolve();
}
});
}
function adjustUploadedPhoto(img, exif, params) {
if (process.env.NODE_ENV !== 'production') {
console.assert(img instanceof HTMLImageElement || img instanceof Image, 'img passed to adjustUploadedPhoto need to be a <img> element.');
exif && console.assert(exif instanceof Object, 'exif passed to adjustUploadedPhoto needs to be an object');
}
// This function resize and rotate photo if needed
//
// Read more, why rotation is required:
// - http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/
// - http://sylvana.net/jpegcrop/exif_orientation.html
return new Promise(function (resolve, reject) {
var canvas = createElement('canvas', {});
var ctx = canvas.getContext("2d");
var width = img.width;
var height = img.height;
var longerSide = Math.max(width, height);
var longerDestSize = Math.max(params.dest_width, params.dest_height);
var scale = Math.min(longerDestSize / longerSide, 1);
width = parseInt(scale * img.width, 10);
height = parseInt(scale * img.height, 10);
canvas.width = width;
canvas.height = height;
if (getIOSVersion()) {
// rotate photo according to exif on iOS.
if (exif && exif.Orientation) {
if (exif.Orientation >= 5) {
canvas.width = height;
canvas.height = width;
}
var transformProps = [// http://stackoverflow.com/a/31273162/2590921
[1, 0, 0, 1, 0, 0], [-1, 0, 0, 1, width, 0], [-1, 0, 0, -1, width, height], [1, 0, 0, -1, 0, height], [0, 1, 1, 0, 0, 0], [0, 1, -1, 0, height, 0], [0, -1, -1, 0, height, width], [0, -1, 1, 0, 0, width]];
ctx.transform.apply(ctx, transformProps[exif.Orientation - 1]);
} else {
console.warn('Invalid image orientation from EXIF:', img, exif);
}
}
ctx.drawImage(img, 0, 0, width, height);
resolve(canvas);
});
}
// gets device orientation.
// possible values: "landscape" | "portrait" | "" (can't detect)
function getDeviceOrientation() {
function mediaQuery(orientation) {
return '(orientation: ' + orientation + ')';
}
var orientations = ['landscape', 'portrait'];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = orientations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var orientation = _step.value;
if (window.matchMedia(mediaQuery(orientation)).matches) {
return orientation;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return '';
}
module.exports = {
adjustUploadedPhoto: adjustUploadedPhoto,
createElement: createElement,
debouncePromise: debouncePromise,
detectFlash: detectFlash,
drawImageScaled: drawImageScaled,
getAndroidVersion: getAndroidVersion,
getIOSVersion: getIOSVersion,
handleImageInput: handleImageInput,
localStorage: localStorage,
measureBlur: measureBlur,
setPrefixedStyle: setPrefixedStyle,
validateUploadedPhoto: validateUploadedPhoto,
whenDOMReady: whenDOMReady,
isBrowser: isBrowser,
getDeviceOrientation: getDeviceOrientation
};