mirror of
https://github.com/serratus/quaggaJS.git
synced 2026-08-13 05:31:37 +08:00
Merged feature/109 to 1.0
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone'),
|
||||
dot: require('gl-vec2/dot')
|
||||
}
|
||||
/**
|
||||
* Creates a cluster for grouping similar orientations of datapoints
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a cluster for grouping similar orientations of datapoints
|
||||
*/
|
||||
export default {
|
||||
create: function(point, threshold) {
|
||||
var points = [],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Cluster2 from './cluster';
|
||||
import ArrayHelper from './array_helper';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone'),
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
const vec3 = {
|
||||
clone: require('gl-vec3/clone'),
|
||||
clone: require('gl-vec3/clone')
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
const hasWindow = typeof window !== 'undefined';
|
||||
const windowRef = hasWindow ? window : {};
|
||||
|
||||
const windowObjects = [
|
||||
"MediaStream",
|
||||
"HTMLImageElement",
|
||||
"HTMLVideoElement",
|
||||
"HTMLCanvasElement",
|
||||
"FileList",
|
||||
"File",
|
||||
"URL"
|
||||
];
|
||||
|
||||
const DOMHelper = windowObjects.reduce((result, obj) => {
|
||||
return {
|
||||
...result,
|
||||
[obj]: obj in windowRef ? windowRef[obj] : () => {}
|
||||
};
|
||||
}, {});
|
||||
|
||||
DOMHelper.setObject = (key, value) => {
|
||||
DOMHelper[key] = value;
|
||||
};
|
||||
|
||||
export default DOMHelper;
|
||||
@@ -1,4 +1,4 @@
|
||||
export default (function() {
|
||||
export default function createEventedElement() {
|
||||
var events = {};
|
||||
|
||||
function getEvent(eventName) {
|
||||
@@ -91,4 +91,4 @@ export default (function() {
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
singleChannel: false // true: only the red color-channel is read
|
||||
},
|
||||
locate: true,
|
||||
numOfWorkers: 0,
|
||||
numOfWorkers: 2,
|
||||
decoder: {
|
||||
readers: [
|
||||
'code_128_reader'
|
||||
|
||||
@@ -68,8 +68,13 @@ export default {
|
||||
}
|
||||
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
|
||||
|
||||
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||
if (_canvas.dom.overlay) {
|
||||
if ($debug) {
|
||||
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||
if (!_canvas.dom.overlay) {
|
||||
_canvas.dom.overlay = document.createElement("canvas");
|
||||
_canvas.dom.overlay.className = "drawingBuffer";
|
||||
$debug.appendChild(_canvas.dom.overlay);
|
||||
}
|
||||
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import ImageWrapper from '../common/image_wrapper';
|
||||
|
||||
var Bresenham = {};
|
||||
|
||||
var Slope = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {merge, pick} from 'lodash';
|
||||
import {merge, pick, omit} from 'lodash';
|
||||
|
||||
var streamRef;
|
||||
|
||||
@@ -64,7 +64,14 @@ function deprecatedConstraints(videoConstraints) {
|
||||
}
|
||||
|
||||
function applyCameraFacing(facing, constraints) {
|
||||
if (typeof constraints.video.deviceId !== 'undefined' || !facing){
|
||||
if (typeof constraints.video.deviceId === 'string' && constraints.video.deviceId.length > 0) {
|
||||
return Promise.resolve({
|
||||
...constraints,
|
||||
video: {
|
||||
...omit(constraints.video, "facingMode")
|
||||
}
|
||||
});
|
||||
} else if (!facing) {
|
||||
return Promise.resolve(constraints);
|
||||
}
|
||||
if ( typeof MediaStreamTrack !== 'undefined' &&
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import {merge, pick, omitBy, isEmpty} from 'lodash';
|
||||
|
||||
import DOMHelper from '../common/dom_helper';
|
||||
|
||||
const isDataURL = {regex: /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i}, // eslint-disable-line max-len
|
||||
isBlobURL = {regex: /^\s*blob:(.*)$/i},
|
||||
isMediaURL = {regex: /^(?:(?:http[s]?|ftp):\/)?\/?(?:(?:[^:\/\s]+)(?:(?:\/\w+)*\/))?([\w\-]+\.([^#?\s]+))(?:.*)?(?:#[\w\-]+)?$/i}, // eslint-disable-line max-len
|
||||
isImageExt = {regex: /(jpe?g|png|gif|tiff)(?:\s+|$)/i},
|
||||
isVideoExt = {regex: /(webm|ogg|mp4|m4v)/i};
|
||||
|
||||
export function createConfigFromSource(config, sourceConfig, source) {
|
||||
if (source instanceof DOMHelper.MediaStream) {
|
||||
return createConfigForStream(config, sourceConfig, {srcObject: source});
|
||||
} else if (source instanceof DOMHelper.HTMLImageElement) {
|
||||
throw new Error('Source "HTMLImageElement": not yet supported');
|
||||
// return createConfigForImage(config, inputConfig, {image: source});
|
||||
} else if (source instanceof DOMHelper.HTMLVideoElement) {
|
||||
throw new Error('Source "HTMLVideoElement": not yet supported');
|
||||
// return createConfigForVideo(config, inputConfig, {video: source});
|
||||
} else if (source instanceof DOMHelper.HTMLCanvasElement) {
|
||||
return createConfigForCanvas(config, sourceConfig, {canvas: source});
|
||||
} else if (source instanceof DOMHelper.FileList) {
|
||||
if (source.length > 0) {
|
||||
return createConfigForFile(config, sourceConfig, source[0]);
|
||||
}
|
||||
} else if (source instanceof DOMHelper.File) {
|
||||
return createConfigForFile(config, sourceConfig, source);
|
||||
} else if (typeof source === 'string') {
|
||||
return createConfigForString(config, sourceConfig, source);
|
||||
} else if (typeof source === 'object'
|
||||
&& (typeof source.constraints !== 'undefined'
|
||||
|| typeof source.area !== 'undefined')) {
|
||||
return createConfigForLiveStream(config, source);
|
||||
} else {
|
||||
throw new Error("No source given!");
|
||||
}
|
||||
}
|
||||
|
||||
function createConfigForImage(config, source, inputConfig = {}) {
|
||||
const staticImageConfig = {
|
||||
inputStream: merge({
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800
|
||||
}, source),
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1
|
||||
};
|
||||
return merge(
|
||||
config,
|
||||
staticImageConfig,
|
||||
{numOfWorkers: typeof config.numOfWorkers === 'number' && config.numOfWorkers > 0 ? 1 : 0},
|
||||
{inputStream: omitBy(pick(config.inputStream, ['size']), isEmpty)},
|
||||
{inputStream: inputConfig});
|
||||
}
|
||||
|
||||
function createConfigForMimeType(config, inputConfig, {src, mime}) {
|
||||
const [, type] = mime.match(/^(video|image)\/(.*)$/i) || [];
|
||||
if (type === 'video') {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
} else if (type === 'image') {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source with mimetype: "${type}" not supported`);
|
||||
}
|
||||
|
||||
function createConfigForFile(config, inputConfig, file) {
|
||||
const src = DOMHelper.URL.createObjectURL(file);
|
||||
return createConfigForMimeType(config, inputConfig, {
|
||||
src,
|
||||
mime: file.type
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForString(config, inputConfig = {}, source) {
|
||||
const [, mime] = source.match(isDataURL.regex) || [];
|
||||
if (mime) {
|
||||
return createConfigForMimeType(config, inputConfig, {src: source, mime});
|
||||
}
|
||||
const blobURL = source.match(isBlobURL.regex);
|
||||
if (blobURL) {
|
||||
throw new Error(`Source "objectURL": not supported`);
|
||||
}
|
||||
const [, , ext] = source.match(isMediaURL.regex) || [];
|
||||
if (ext) {
|
||||
return createConfigForMediaExtension(config, inputConfig, {src: source, ext});
|
||||
}
|
||||
throw new Error(`Source "${source}": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForMediaExtension(config, inputConfig, {src, ext}) {
|
||||
if (ext.match(isImageExt.regex)) {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
} else if (ext.match(isVideoExt.regex)) {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source "MediaString": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForCanvas (config, {canvas}, inputConfig = {}) {
|
||||
// TODO: adjust stream & frame-grabber
|
||||
// once/continous
|
||||
throw new Error('Source "Canvas": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForVideo (config, source, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: merge({
|
||||
type: "VideoStream"
|
||||
}, source)
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForStream(config, {srcObject}, inputConfig = {}) {
|
||||
// TODO: attach to <video> element
|
||||
// wait for the video to be ready (dimensions known)
|
||||
throw new Error('Source "MediaStream": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForLiveStream(config, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: {
|
||||
type: "LiveStream",
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
||||
@@ -286,6 +286,10 @@ InputStream.createImageStream = function() {
|
||||
}
|
||||
};
|
||||
|
||||
that.clearEventHandlers = function() {
|
||||
_eventHandlers = {};
|
||||
};
|
||||
|
||||
that.setTopRight = function(topRight) {
|
||||
_topRight.x = topRight.x;
|
||||
_topRight.y = topRight.y;
|
||||
|
||||
+528
-532
File diff suppressed because it is too large
Load Diff
+123
-529
@@ -1,543 +1,137 @@
|
||||
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
|
||||
import WebrtcAdapter from 'webrtc-adapter'; // eslint-disable-line no-unused-vars
|
||||
import './common/typedefs';
|
||||
import 'webrtc-adapter';
|
||||
import createScanner from './scanner';
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import BarcodeLocator from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import Events from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
import {createConfigFromSource} from './input/config_factory';
|
||||
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null,
|
||||
overlay: null
|
||||
},
|
||||
dom: {
|
||||
image: null,
|
||||
overlay: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {};
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
|
||||
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||
if (!_canvasContainer.dom.overlay) {
|
||||
_canvasContainer.dom.overlay = document.createElement("canvas");
|
||||
_canvasContainer.dom.overlay.className = "drawingBuffer";
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(_canvasContainer.dom.overlay);
|
||||
}
|
||||
var clearFix = document.createElement("br");
|
||||
clearFix.setAttribute("clear", "all");
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(clearFix);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
|
||||
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
BarcodeLocator.init(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return BarcodeLocator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
Events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
Events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency || 60);
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
function fromConfig(config) {
|
||||
const scanner = createScanner();
|
||||
let pendingStart = null;
|
||||
let initialized = false;
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
addEventListener(eventType, cb) {
|
||||
scanner.subscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
removeEventListener(eventType, cb) {
|
||||
scanner.unsubscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
start() {
|
||||
if (scanner.isRunning()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (pendingStart) {
|
||||
return pendingStart;
|
||||
}
|
||||
if (initialized) {
|
||||
scanner.start();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
pendingStart = new Promise((resolve, reject) => {
|
||||
scanner.init(config, (error) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
initialized = true;
|
||||
scanner.start();
|
||||
resolve();
|
||||
pendingStart = null;
|
||||
});
|
||||
});
|
||||
return pendingStart;
|
||||
},
|
||||
stop() {
|
||||
scanner.stop();
|
||||
initialized = false;
|
||||
return this;
|
||||
},
|
||||
toPromise() {
|
||||
if (config.inputStream.type === 'LiveStream'
|
||||
|| config.inputStream.type === 'VideoStream') {
|
||||
let cancelRequested = false;
|
||||
return {
|
||||
cancel() {
|
||||
cancelRequested = true;
|
||||
},
|
||||
promise: new Promise((resolve, reject) => {
|
||||
function onProcessed(result) {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
resolve(result);
|
||||
}
|
||||
if (cancelRequested) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
reject("cancelled!");
|
||||
}
|
||||
}
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
this.start();
|
||||
})
|
||||
};
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
scanner.decodeSingle(config, (result) => {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
return resolve(result);
|
||||
}
|
||||
return reject(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
registerResultCollector(resultCollector) {
|
||||
scanner.registerResultCollector(resultCollector);
|
||||
},
|
||||
getCanvas() {
|
||||
return scanner.canvas.dom.image;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper;
|
||||
function fromSource(config, source, inputConfig = {}) {
|
||||
config = createConfigFromSource(config, inputConfig, source);
|
||||
return fromConfig(config);
|
||||
}
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
Quagga.init(config, ready, imageWrapper);
|
||||
Quagga.onProcessed(onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
Quagga.start();
|
||||
} else if (e.data.cmd === 'setReaders') {
|
||||
Quagga.setReaders(e.data.readers);
|
||||
function setConfig(configuration = {}, key, config = {}) {
|
||||
var mergedConfig = merge({}, configuration, {[key]: config});
|
||||
return createApi(mergedConfig);
|
||||
}
|
||||
|
||||
function createApi(configuration = Config) {
|
||||
return {
|
||||
fromSource(src, inputConfig) {
|
||||
return fromSource(configuration, src, inputConfig);
|
||||
},
|
||||
fromConfig(conf) {
|
||||
return fromConfig(merge({}, configuration, conf));
|
||||
},
|
||||
decoder(conf) {
|
||||
return setConfig(configuration, "decoder", conf);
|
||||
},
|
||||
locator(conf) {
|
||||
return setConfig(configuration, "locator", conf);
|
||||
},
|
||||
throttle(timeInMs) {
|
||||
return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
|
||||
},
|
||||
config(conf) {
|
||||
return createApi(merge({}, configuration, conf));
|
||||
},
|
||||
ImageWrapper,
|
||||
ImageDebug,
|
||||
ResultCollector,
|
||||
_worker: {
|
||||
createScanner
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
_inputStream.clearEventHandlers();
|
||||
}
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
onDetected: function(callback) {
|
||||
Events.subscribe("detected", callback);
|
||||
},
|
||||
offDetected: function(callback) {
|
||||
Events.unsubscribe("detected", callback);
|
||||
},
|
||||
onProcessed: function(callback) {
|
||||
Events.subscribe("processed", callback);
|
||||
},
|
||||
offProcessed: function(callback) {
|
||||
Events.unsubscribe("processed", callback);
|
||||
},
|
||||
setReaders: function(readers) {
|
||||
setReaders(readers);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
canvas: _canvasContainer,
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
config = merge({
|
||||
inputStream: {
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800,
|
||||
src: config.src
|
||||
},
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1,
|
||||
locator: {
|
||||
halfSample: false
|
||||
}
|
||||
}, config);
|
||||
this.init(config, () => {
|
||||
Events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
ImageWrapper: ImageWrapper,
|
||||
ImageDebug: ImageDebug,
|
||||
ResultCollector: ResultCollector
|
||||
};
|
||||
export default createApi();
|
||||
|
||||
+508
@@ -0,0 +1,508 @@
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import createLocator, {checkImageConstraints} from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import createEventedElement from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
|
||||
|
||||
function createScanner() {
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped = true,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null
|
||||
},
|
||||
dom: {
|
||||
image: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {},
|
||||
_events = createEventedElement(),
|
||||
_locator;
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
_locator = createLocator(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return _locator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
_events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
_events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency === 0 ? 60 : (_config.frequency || 60));
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper,
|
||||
scanner = Quagga._worker.createScanner();
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
scanner.init(config, ready, imageWrapper);
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
scanner.start();
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
isRunning: function() {
|
||||
return !_stopped;
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
}
|
||||
_inputStream.clearEventHandlers();
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
subscribe(eventName, callback) {
|
||||
_events.subscribe(eventName, callback);
|
||||
},
|
||||
unsubscribe(eventName, callback) {
|
||||
_events.unsubscribe(eventName, callback);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
this.init(config, () => {
|
||||
_events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
canvas: _canvasContainer
|
||||
};
|
||||
}
|
||||
|
||||
export default createScanner;
|
||||
Reference in New Issue
Block a user