UNPKG

webcodecamjs

Version:
602 lines (574 loc) 21.2 kB
/*! * WebCodeCamJS 2.0.5 javascript Bar code and QR code decoder * Author: Tóth András * Web: http://atandrastoth.co.uk * email: atandrastoth@gmail.com * Licensed under the MIT license */ var WebCodeCamJS = function(element) { 'use strict'; this.Version = { name: 'WebCodeCamJS', version: '2.0.1', author: 'Tóth András' }; var mediaDevices = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ? navigator.mediaDevices : ((navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? { getUserMedia: function(c) { return new Promise(function(y, n) { (navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia).call(navigator, c, y, n); }); }, enumerateDevices: function(c) { return new Promise(function(c, y, n) { (MediaStreamTrack.getSources).call(navigator, c, y, n); }); } } : null); HTMLVideoElement.prototype.streamSrc = ('srcObject' in HTMLVideoElement.prototype) ? function(stream) { this.srcObject = !!stream ? stream : null; } : function(stream) { this.src = !!stream ? (window.URL || window.webkitURL).createObjectURL(stream) : new String(); }; var videoSelect, lastImageSrc, con, beepSound, w, h, lastCode; var display = Q(element), DecodeWorker = null, video = html('<video muted autoplay></video>'), sucessLocalDecode = false, localImage = false, flipMode = [1, 3, 6, 8], isStreaming = false, delayBool = false, initialized = false, localStream = null, options = { decodeQRCodeRate: 5, decodeBarCodeRate: 3, successTimeout: 500, codeRepetition: true, tryVertical: true, frameRate: 15, width: 320, height: 240, constraints: { video: { mandatory: { maxWidth: 1280, maxHeight: 720 }, optional: [{ sourceId: true }] }, audio: false }, flipVertical: false, flipHorizontal: false, zoom: -1, beep: 'audio/beep.mp3', decoderWorker: 'js/DecoderWorker.js', brightness: 0, autoBrightnessValue: false, grayScale: false, contrast: 0, threshold: 0, sharpness: [], resultFunction: function(resText, lastImageSrc) { console.log(resText); }, cameraSuccess: function(stream) { console.log('cameraSuccess'); }, canPlayFunction: function() { console.log('canPlayFunction'); }, getDevicesError: function(error) { console.log(error); }, getUserMediaError: function(error) { console.log(error); }, cameraError: function(error) { console.log(error); } }; function init() { var constraints = changeConstraints(); try { mediaDevices.getUserMedia(constraints).then(cameraSuccess).catch(function(error) { options.cameraError(error); return false; }); } catch (error) { options.getUserMediaError(error); return false; } return true; } function play() { if (!localImage) { if (!localStream) { init(); } delayBool = true; video.play(); setTimeout(function() { delayBool = false; if (options.decodeBarCodeRate) { tryParseBarCode(); } if (options.decodeQRCodeRate) { tryParseQRCode(); } }, 2E3); } } function stop() { delayBool = true; video.pause(); video.streamSrc(null); con.clearRect(0, 0, w, h); if (localStream) { for (var i = 0; i < localStream.getTracks().length; i++) { localStream.getTracks()[i].stop(); } } localStream = null; } function pause() { delayBool = true; video.pause(); } function beep() { if (options.beep) { beepSound.play(); } } function cameraSuccess(stream) { localStream = stream; video.streamSrc(stream); video.play(); options.cameraSuccess(stream); } function cameraError(error) { options.cameraError(error); } function setEventListeners() { video.addEventListener('canplay', function(e) { if (!isStreaming) { if (video.videoWidth > 0) { h = video.videoHeight / (video.videoWidth / w); } display.setAttribute('width', w); display.setAttribute('height', h); isStreaming = true; if (options.decodeQRCodeRate || options.decodeBarCodeRate) { delay(); } } }, false); video.addEventListener('play', function() { setInterval(function() { if (!video.paused && !video.ended) { var z = options.zoom; if (z < 0) { z = optimalZoom(); } con.drawImage(video, (w * z - w) / -2, (h * z - h) / -2, w * z, h * z); var imageData = con.getImageData(0, 0, w, h); if (options.grayScale) { imageData = grayScale(imageData); } if (options.brightness !== 0 || options.autoBrightnessValue) { imageData = brightness(imageData, options.brightness); } if (options.contrast !== 0) { imageData = contrast(imageData, options.contrast); } if (options.threshold !== 0) { imageData = threshold(imageData, options.threshold); } if (options.sharpness.length !== 0) { imageData = convolute(imageData, options.sharpness); } con.putImageData(imageData, 0, 0); } }, 1E3 / options.frameRate); }, false); } function setCallBack() { DecodeWorker.onmessage = function(e) { if (localImage || (!delayBool && !video.paused)) { if (e.data.success === true && e.data.success != 'localization') { sucessLocalDecode = true; delayBool = true; delay(); setTimeout(function() { if (options.codeRepetition || lastCode != e.data.result[0].Value) { beep(); lastCode = e.data.result[0].Value; options.resultFunction({ format: e.data.result[0].Format, code: e.data.result[0].Value, imgData: lastImageSrc }); } }, 0); } if ((!sucessLocalDecode || !localImage) && e.data.success != 'localization') { if (!localImage) { setTimeout(tryParseBarCode, 1E3 / options.decodeBarCodeRate); } } } }; qrcode.callback = function(a) { if (localImage || (!delayBool && !video.paused)) { sucessLocalDecode = true; delayBool = true; delay(); setTimeout(function() { if (options.codeRepetition || lastCode != a) { beep(); lastCode = a; options.resultFunction({ format: 'QR Code', code: a, imgData: lastImageSrc }); } }, 0); } }; } function tryParseBarCode() { display.style.transform = 'scale(' + (options.flipHorizontal ? '-1' : '1') + ', ' + (options.flipVertical ? '-1' : '1') + ')'; if (options.tryVertical && !localImage) { flipMode.push(flipMode[0]); flipMode.remove(0); } else { flipMode = [1, 3, 6, 8]; } lastImageSrc = display.toDataURL(); DecodeWorker.postMessage({ scan: con.getImageData(0, 0, w, h).data, scanWidth: w, scanHeight: h, multiple: false, decodeFormats: ["Code128", "Code93", "Code39", "EAN-13", "2Of5", "Inter2Of5", "Codabar"], rotation: flipMode[0] }); } function tryParseQRCode() { display.style.transform = 'scale(' + (options.flipHorizontal ? '-1' : '1') + ', ' + (options.flipVertical ? '-1' : '1') + ')'; try { lastImageSrc = display.toDataURL(); qrcode.decode(); } catch (e) { if (!localImage && !delayBool) { setTimeout(tryParseQRCode, 1E3 / options.decodeQRCodeRate); } } } function delay() { if (!localImage) { setTimeout(play, options.successTimeout, true); } } function optimalZoom() { return video.videoHeight / h; } function getImageLightness() { var pixels = con.getImageData(0, 0, w, h), d = pixels.data, colorSum = 0, r, g, b, avg; for (var x = 0, len = d.length; x < len; x += 4) { r = d[x]; g = d[x + 1]; b = d[x + 2]; avg = Math.floor((r + g + b) / 3); colorSum += avg; } return Math.floor(colorSum / (w * h)); } function brightness(pixels, adjustment) { adjustment = adjustment === 0 && options.autoBrightnessValue ? Number(options.autoBrightnessValue) - getImageLightness() : adjustment; var d = pixels.data; for (var i = 0; i < d.length; i += 4) { d[i] += adjustment; d[i + 1] += adjustment; d[i + 2] += adjustment; } return pixels; } function grayScale(pixels) { var d = pixels.data; for (var i = 0; i < d.length; i += 4) { var r = d[i], g = d[i + 1], b = d[i + 2], v = 0.2126 * r + 0.7152 * g + 0.0722 * b; d[i] = d[i + 1] = d[i + 2] = v; } return pixels; } function contrast(pixels, cont) { var d = pixels.data, average; for (var i = 0; i < d.length; i += 4) { cont = 10, average = Math.round((d[i] + d[i + 1] + d[i + 2]) / 3); if (average > 127) { d[i] += d[i] / average * cont; d[i + 1] += d[i + 1] / average * cont; d[i + 2] += d[i + 2] / average * cont; } else { d[i] -= d[i] / average * cont; d[i + 1] -= d[i + 1] / average * cont; d[i + 2] -= d[i + 2] / average * cont; } } return pixels; } function threshold(pixels, thres) { var average, d = pixels.data; for (var i = 0, len = w * h * 4; i < len; i += 4) { average = d[i] + d[i + 1] + d[i + 2]; if (average < thres) { d[i] = d[i + 1] = d[i + 2] = 0; } else { d[i] = d[i + 1] = d[i + 2] = 255; } d[i + 3] = 255; } return pixels; } function convolute(pixels, weights, opaque) { var sw = pixels.width, sh = pixels.height, w = sw, h = sh, side = Math.round(Math.sqrt(weights.length)), halfSide = Math.floor(side / 2), src = pixels.data, tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d'), output = tmpCtx.createImageData(w, h), dst = output.data, alphaFac = opaque ? 1 : 0; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var sy = y, sx = x, r = 0, g = 0, b = 0, a = 0, dstOff = (y * w + x) * 4; for (var cy = 0; cy < side; cy++) { for (var cx = 0; cx < side; cx++) { var scy = sy + cy - halfSide, scx = sx + cx - halfSide; if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { var srcOff = (scy * sw + scx) * 4, 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; } function buildSelectMenu(selectorVideo, ind) { videoSelect = Q(selectorVideo); videoSelect.innerHTML = ''; try { if (mediaDevices && mediaDevices.enumerateDevices) { mediaDevices.enumerateDevices().then(function(devices) { devices.forEach(function(device) { gotSources(device); }); videoSelect.selectedIndex = videoSelect.children.length <= ind ? 0 : ind; }).catch(function(error) { options.getDevicesError(error); }); } else if (mediaDevices && !mediaDevices.enumerateDevices) { html('<option value="true">On</option>', videoSelect); options.getDevicesError(new NotSupportError('enumerateDevices Or getSources is Not supported')); } else { throw new NotSupportError('getUserMedia is Not supported'); } } catch (error) { options.getDevicesError(error); } } function gotSources(device) { if (device.kind === 'video' || device.kind === 'videoinput') { var face = (!device.facing || device.facing === '') ? 'unknown' : device.facing; var text = device.label || 'camera ' + (videoSelect.length + 1) + ' (facing: ' + face + ')'; html('<option value="' + (device.id || device.deviceId) + '">' + text + '</option>', videoSelect); } } function changeConstraints() { var constraints = JSON.parse(JSON.stringify(options.constraints)); if (videoSelect && videoSelect.length !== 0) { switch (videoSelect[videoSelect.selectedIndex].value.toString()) { case 'true': constraints.video.optional = [{ sourceId: true }]; break; case 'false': constraints.video = false; break; default: constraints.video.optional = [{ sourceId: videoSelect[videoSelect.selectedIndex].value }]; break; } } constraints.audio = false; return constraints; } function Q(el) { if (typeof el === 'string') { var els = document.querySelectorAll(el); return typeof els === 'undefined' ? undefined : els.length > 1 ? els : els[0]; } return el; } function decodeLocalImage(url) { stop(); localImage = true; sucessLocalDecode = false; var img = new Image(); img.onload = function() { con.fillStyle = '#fff'; con.fillRect(0, 0, w, h); con.drawImage(this, 5, 5, w - 10, h - 10); tryParseQRCode(); tryParseBarCode(); }; if (url) { download("temp", url); decodeLocalImage(); } else { if (FileReaderHelper) { new FileReaderHelper().Init('jpg|png|jpeg|gif', 'dataURL', function(e) { img.src = e.data; }, true); } else { alert("fileReader class not found!"); } } } function download(filename, url) { var a = window.document.createElement('a'); a.href = url; a.download = filename; a.click(); } function mergeRecursive(target, source) { if (typeof target !== 'object') { target = {}; } for (var property in source) { if (source.hasOwnProperty(property)) { var sourceProperty = source[property]; if (typeof sourceProperty === 'object') { target[property] = mergeRecursive(target[property], sourceProperty); continue; } target[property] = sourceProperty; } } for (var a = 2, l = arguments.length; a < l; a++) { mergeRecursive(target, arguments[a]); } return target; } function html(innerhtml, appendTo) { var item = document.createElement('div'); if (innerhtml) { item.innerHTML = innerhtml; } if (appendTo) { appendTo.appendChild(item.children[0]); return item; } return item.children[0]; } function NotSupportError(message) { this.name = 'NotSupportError'; this.message = (message || ''); } NotSupportError.prototype = Error.prototype; return { init: function(opt) { if (initialized) { return this; } if (!display || display.tagName.toLowerCase() !== 'canvas') { console.log('Element type must be canvas!'); alert('Element type must be canvas!'); return false; } con = display.getContext('2d'); if (opt) { options = mergeRecursive(options, opt); if (options.beep) { beepSound = new Audio(options.beep); } } display.width = w = options.width; display.height = h = options.height; qrcode.sourceCanvas = display; initialized = true; setEventListeners(); DecodeWorker = new Worker(options.decoderWorker); if (options.decodeQRCodeRate || options.decodeBarCodeRate) { setCallBack(); } return this; }, play: function() { localImage = false; play(); return this; }, stop: function() { stop(); return this; }, pause: function() { pause(); return this; }, buildSelectMenu: function(selector, ind) { buildSelectMenu(selector, ind ? ind : 0); return this; }, getOptimalZoom: function() { return optimalZoom(); }, getLastImageSrc: function() { return display.toDataURL(); }, decodeLocalImage: function(url) { decodeLocalImage(url); }, isInitialized: function() { return initialized; }, options: options }; };