UNPKG

mighty-webcamjs

Version:

HTML5 Webcam Image Capture Library with Flash Fallback

345 lines (306 loc) 10.8 kB
'use strict'; 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 };