UNPKG

mighty-webcamjs

Version:

HTML5 Webcam Image Capture Library with Flash Fallback

316 lines (280 loc) 9.31 kB
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: (name) => localStorage[name], setItem: (name, value) => 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'); } }); } const debouncePromise = (func) => { // debouncePromise takes function, that returns promise // and makes sure - there is just one concurrent call of promise. // NOTE: arguments might be ignored let isPromiseRunning = false; let lastPromise; return (...args) => { if (!isPromiseRunning) { lastPromise = new Promise((resolve, reject) => { isPromiseRunning = true; const promise = func(...args); const end = () => { isPromiseRunning = false; } const resolver = (...promiseArgs) => (end(), resolve(...promiseArgs)); const rejector = (...promiseArgs) => (end(), reject(...promiseArgs)); promise.then(resolver, rejector); }); } return lastPromise; }; }; const whenDOMReady = (() => { // takes function, that can be called, when DOM is loaded. let isDOMLoaded = false; const domReadyPromise = new Promise((resolve) => { if (isBrowser) { document.addEventListener('DOMContentLoaded', resolve, false); } else { resolve(); } }); domReadyPromise.then(() => { isDOMLoaded = true; }); return (context, func) => { // on Chrome 52 @ android mediaDevices.getUserMedia is triggered with a delay. // functions to be triggered when camera is ready return (...args) => { if (isDOMLoaded) { // synchronous call return func.apply(context, args); } // asynchronous call return domReadyPromise.then(() => 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((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; const prefixes = ['o', 'ms', 'moz', 'webkit']; prefixes.forEach((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) { const 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})`; } const orientations = ['landscape', 'portrait']; for (let orientation of orientations) { if (window.matchMedia(mediaQuery(orientation)).matches) { return orientation; } } return ''; } module.exports = { adjustUploadedPhoto, createElement, debouncePromise, detectFlash, drawImageScaled, getAndroidVersion, getIOSVersion, handleImageInput, localStorage, measureBlur, setPrefixedStyle, validateUploadedPhoto, whenDOMReady, isBrowser, getDeviceOrientation, };