From 633eabe86c067d42fea92df9499e7df5b25d3ecd Mon Sep 17 00:00:00 2001 From: Christoph Oberhofer Date: Sun, 8 May 2016 00:09:09 +0200 Subject: [PATCH] Moved remaining code to instances --- src/common/events.js | 4 +- src/locator/barcode_locator.js | 978 ++++++++++++++++----------------- src/quagga.js | 41 +- src/scanner.js | 28 +- 4 files changed, 510 insertions(+), 541 deletions(-) diff --git a/src/common/events.js b/src/common/events.js index cc56589..8cd3d01 100644 --- a/src/common/events.js +++ b/src/common/events.js @@ -1,4 +1,4 @@ -export default (function() { +export default function createEventedElement() { var events = {}; function getEvent(eventName) { @@ -79,4 +79,4 @@ export default (function() { } } }; -})(); +}; diff --git a/src/locator/barcode_locator.js b/src/locator/barcode_locator.js index dabbe4a..3d4fde6 100644 --- a/src/locator/barcode_locator.js +++ b/src/locator/barcode_locator.js @@ -16,7 +16,7 @@ import Tracer from './tracer'; import skeletonizer from './skeletonizer'; const vec2 = { clone: require('gl-vec2/clone'), - dot: require('gl-vec2/dot'), + dot: require('gl-vec2/dot'), scale: require('gl-vec2/scale'), transformMat2: require('gl-vec2/transformMat2') }; @@ -24,584 +24,580 @@ const mat2 = { copy: require('gl-mat2/copy'), create: require('gl-mat2/create'), invert: require('gl-mat2/invert') -} +}; -var _config, - _currentImageWrapper, - _skelImageWrapper, - _subImageWrapper, - _labelImageWrapper, - _patchGrid, - _patchLabelGrid, - _imageToPatchGrid, - _binaryImageWrapper, - _patchSize, - _canvasContainer = { - ctx: { - binary: null +export default function createLocator(inputImageWrapper, config) { + var _config = config, + _currentImageWrapper, + _skelImageWrapper, + _subImageWrapper, + _labelImageWrapper, + _patchGrid, + _patchLabelGrid, + _imageToPatchGrid, + _binaryImageWrapper, + _patchSize, + _canvasContainer = { + ctx: { + binary: null + }, + dom: { + binary: null + } }, - dom: { - binary: null - } - }, - _numPatches = {x: 0, y: 0}, - _inputImageWrapper, - _skeletonizer; - -function initBuffers() { - var skeletonImageData; - - if (_config.halfSample) { - _currentImageWrapper = new ImageWrapper({ - x: _inputImageWrapper.size.x / 2 | 0, - y: _inputImageWrapper.size.y / 2 | 0 - }); - } else { - _currentImageWrapper = _inputImageWrapper; - } + _numPatches = {x: 0, y: 0}, + _inputImageWrapper = inputImageWrapper, + _skeletonizer; - _patchSize = calculatePatchSize(_config.patchSize, _currentImageWrapper.size); + initBuffers(); + initCanvas(); - _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0; - _numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0; + function initBuffers() { + var skeletonImageData; - _binaryImageWrapper = new ImageWrapper(_currentImageWrapper.size, undefined, Uint8Array, false); + if (_config.halfSample) { + _currentImageWrapper = new ImageWrapper({ + x: _inputImageWrapper.size.x / 2 | 0, + y: _inputImageWrapper.size.y / 2 | 0 + }); + } else { + _currentImageWrapper = _inputImageWrapper; + } - _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true); + _patchSize = calculatePatchSize(_config.patchSize, _currentImageWrapper.size); - skeletonImageData = new ArrayBuffer(64 * 1024); - _subImageWrapper = new ImageWrapper(_patchSize, - new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y)); - _skelImageWrapper = new ImageWrapper(_patchSize, - new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), - undefined, true); - _skeletonizer = skeletonizer((typeof window !== 'undefined') ? window : (typeof self !== 'undefined') ? self : global, { - size: _patchSize.x - }, skeletonImageData); + _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0; + _numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0; - _imageToPatchGrid = new ImageWrapper({ - x: (_currentImageWrapper.size.x / _subImageWrapper.size.x) | 0, - y: (_currentImageWrapper.size.y / _subImageWrapper.size.y) | 0 - }, undefined, Array, true); - _patchGrid = new ImageWrapper(_imageToPatchGrid.size, undefined, undefined, true); - _patchLabelGrid = new ImageWrapper(_imageToPatchGrid.size, undefined, Int32Array, true); -} + _binaryImageWrapper = new ImageWrapper(_currentImageWrapper.size, undefined, Uint8Array, false); -function initCanvas() { - if (_config.useWorker || typeof document === 'undefined') { - return; - } - _canvasContainer.dom.binary = document.createElement("canvas"); - _canvasContainer.dom.binary.className = "binaryBuffer"; - if (ENV.development && _config.debug.showCanvas === true) { - document.querySelector("#debug").appendChild(_canvasContainer.dom.binary); - } - _canvasContainer.ctx.binary = _canvasContainer.dom.binary.getContext("2d"); - _canvasContainer.dom.binary.width = _binaryImageWrapper.size.x; - _canvasContainer.dom.binary.height = _binaryImageWrapper.size.y; -} + _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true); -/** - * Creates a bounding box which encloses all the given patches - * @returns {Array} The minimal bounding box - */ -function boxFromPatches(patches) { - var overAvg, - i, - j, - patch, - transMat, - minx = - _binaryImageWrapper.size.x, - miny = _binaryImageWrapper.size.y, - maxx = -_binaryImageWrapper.size.x, - maxy = -_binaryImageWrapper.size.y, - box, - scale; - - // draw all patches which are to be taken into consideration - overAvg = 0; - for ( i = 0; i < patches.length; i++) { - patch = patches[i]; - overAvg += patch.rad; - if (ENV.development && _config.debug.showPatches) { - ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "red"}); - } - } + skeletonImageData = new ArrayBuffer(64 * 1024); + _subImageWrapper = new ImageWrapper(_patchSize, + new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y)); + _skelImageWrapper = new ImageWrapper(_patchSize, + new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), + undefined, true); + _skeletonizer = skeletonizer((typeof window !== 'undefined') ? window : (typeof self !== 'undefined') ? self : global, { + size: _patchSize.x + }, skeletonImageData); - overAvg /= patches.length; - overAvg = (overAvg * 180 / Math.PI + 90) % 180 - 90; - if (overAvg < 0) { - overAvg += 180; + _imageToPatchGrid = new ImageWrapper({ + x: (_currentImageWrapper.size.x / _subImageWrapper.size.x) | 0, + y: (_currentImageWrapper.size.y / _subImageWrapper.size.y) | 0 + }, undefined, Array, true); + _patchGrid = new ImageWrapper(_imageToPatchGrid.size, undefined, undefined, true); + _patchLabelGrid = new ImageWrapper(_imageToPatchGrid.size, undefined, Int32Array, true); } - overAvg = (180 - overAvg) * Math.PI / 180; - transMat = mat2.copy(mat2.create(), [Math.cos(overAvg), Math.sin(overAvg), -Math.sin(overAvg), Math.cos(overAvg)]); - - // iterate over patches and rotate by angle - for ( i = 0; i < patches.length; i++) { - patch = patches[i]; - for ( j = 0; j < 4; j++) { - vec2.transformMat2(patch.box[j], patch.box[j], transMat); + function initCanvas() { + if (_config.useWorker || typeof document === 'undefined') { + return; } - - if (ENV.development && _config.debug.boxFromPatches.showTransformed) { - ImageDebug.drawPath(patch.box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#99ff00', lineWidth: 2}); + _canvasContainer.dom.binary = document.createElement("canvas"); + _canvasContainer.dom.binary.className = "binaryBuffer"; + if (ENV.development && _config.debug.showCanvas === true) { + document.querySelector("#debug").appendChild(_canvasContainer.dom.binary); } + _canvasContainer.ctx.binary = _canvasContainer.dom.binary.getContext("2d"); + _canvasContainer.dom.binary.width = _binaryImageWrapper.size.x; + _canvasContainer.dom.binary.height = _binaryImageWrapper.size.y; } - // find bounding box - for ( i = 0; i < patches.length; i++) { - patch = patches[i]; - for ( j = 0; j < 4; j++) { - if (patch.box[j][0] < minx) { - minx = patch.box[j][0]; + /** + * Creates a bounding box which encloses all the given patches + * @returns {Array} The minimal bounding box + */ + function boxFromPatches(patches) { + var overAvg, + i, + j, + patch, + transMat, + minx = + _binaryImageWrapper.size.x, + miny = _binaryImageWrapper.size.y, + maxx = -_binaryImageWrapper.size.x, + maxy = -_binaryImageWrapper.size.y, + box, + scale; + + // draw all patches which are to be taken into consideration + overAvg = 0; + for ( i = 0; i < patches.length; i++) { + patch = patches[i]; + overAvg += patch.rad; + if (ENV.development && _config.debug.showPatches) { + ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "red"}); } - if (patch.box[j][0] > maxx) { - maxx = patch.box[j][0]; + } + + overAvg /= patches.length; + overAvg = (overAvg * 180 / Math.PI + 90) % 180 - 90; + if (overAvg < 0) { + overAvg += 180; + } + + overAvg = (180 - overAvg) * Math.PI / 180; + transMat = mat2.copy(mat2.create(), [Math.cos(overAvg), Math.sin(overAvg), -Math.sin(overAvg), Math.cos(overAvg)]); + + // iterate over patches and rotate by angle + for ( i = 0; i < patches.length; i++) { + patch = patches[i]; + for ( j = 0; j < 4; j++) { + vec2.transformMat2(patch.box[j], patch.box[j], transMat); } - if (patch.box[j][1] < miny) { - miny = patch.box[j][1]; + + if (ENV.development && _config.debug.boxFromPatches.showTransformed) { + ImageDebug.drawPath(patch.box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#99ff00', lineWidth: 2}); } - if (patch.box[j][1] > maxy) { - maxy = patch.box[j][1]; + } + + // find bounding box + for ( i = 0; i < patches.length; i++) { + patch = patches[i]; + for ( j = 0; j < 4; j++) { + if (patch.box[j][0] < minx) { + minx = patch.box[j][0]; + } + if (patch.box[j][0] > maxx) { + maxx = patch.box[j][0]; + } + if (patch.box[j][1] < miny) { + miny = patch.box[j][1]; + } + if (patch.box[j][1] > maxy) { + maxy = patch.box[j][1]; + } } } - } - box = [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]]; + box = [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]]; - if (ENV.development && _config.debug.boxFromPatches.showTransformedBox) { - ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); - } + if (ENV.development && _config.debug.boxFromPatches.showTransformedBox) { + ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); + } - scale = _config.halfSample ? 2 : 1; - // reverse rotation; - transMat = mat2.invert(transMat, transMat); - for ( j = 0; j < 4; j++) { - vec2.transformMat2(box[j], box[j], transMat); - } + scale = _config.halfSample ? 2 : 1; + // reverse rotation; + transMat = mat2.invert(transMat, transMat); + for ( j = 0; j < 4; j++) { + vec2.transformMat2(box[j], box[j], transMat); + } - if (ENV.development && _config.debug.boxFromPatches.showBB) { - ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); - } + if (ENV.development && _config.debug.boxFromPatches.showBB) { + ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); + } - for ( j = 0; j < 4; j++) { - vec2.scale(box[j], box[j], scale); - } + for ( j = 0; j < 4; j++) { + vec2.scale(box[j], box[j], scale); + } - return box; -} + return box; + } -/** - * Creates a binary image of the current image - */ -function binarizeImage() { - otsuThreshold(_currentImageWrapper, _binaryImageWrapper); - _binaryImageWrapper.zeroBorder(); - if (ENV.development && _config.debug.showCanvas) { - _binaryImageWrapper.show(_canvasContainer.dom.binary, 255); + /** + * Creates a binary image of the current image + */ + function binarizeImage() { + otsuThreshold(_currentImageWrapper, _binaryImageWrapper); + _binaryImageWrapper.zeroBorder(); + if (ENV.development && _config.debug.showCanvas) { + _binaryImageWrapper.show(_canvasContainer.dom.binary, 255); + } } -} -/** - * Iterate over the entire image - * extract patches - */ -function findPatches() { - var i, - j, - x, - y, - moments, - patchesFound = [], - rasterizer, - rasterResult, - patch; - for (i = 0; i < _numPatches.x; i++) { - for (j = 0; j < _numPatches.y; j++) { - x = _subImageWrapper.size.x * i; - y = _subImageWrapper.size.y * j; - - // seperate parts - skeletonize(x, y); - - // Rasterize, find individual bars - _skelImageWrapper.zeroBorder(); - ArrayHelper.init(_labelImageWrapper.data, 0); - rasterizer = Rasterizer.create(_skelImageWrapper, _labelImageWrapper); - rasterResult = rasterizer.rasterize(0); - - if (ENV.development && _config.debug.showLabels) { - _labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), - {x: x, y: y}); - } + /** + * Iterate over the entire image + * extract patches + */ + function findPatches() { + var i, + j, + x, + y, + moments, + patchesFound = [], + rasterizer, + rasterResult, + patch; + for (i = 0; i < _numPatches.x; i++) { + for (j = 0; j < _numPatches.y; j++) { + x = _subImageWrapper.size.x * i; + y = _subImageWrapper.size.y * j; + + // seperate parts + skeletonize(x, y); + + // Rasterize, find individual bars + _skelImageWrapper.zeroBorder(); + ArrayHelper.init(_labelImageWrapper.data, 0); + rasterizer = Rasterizer.create(_skelImageWrapper, _labelImageWrapper); + rasterResult = rasterizer.rasterize(0); + + if (ENV.development && _config.debug.showLabels) { + _labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), + {x: x, y: y}); + } - // calculate moments from the skeletonized patch - moments = _labelImageWrapper.moments(rasterResult.count); + // calculate moments from the skeletonized patch + moments = _labelImageWrapper.moments(rasterResult.count); - // extract eligible patches - patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y)); + // extract eligible patches + patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y)); + } } - } - if (ENV.development && _config.debug.showFoundPatches) { - for ( i = 0; i < patchesFound.length; i++) { - patch = patchesFound[i]; - ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, - {color: "#99ff00", lineWidth: 2}); + if (ENV.development && _config.debug.showFoundPatches) { + for ( i = 0; i < patchesFound.length; i++) { + patch = patchesFound[i]; + ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, + {color: "#99ff00", lineWidth: 2}); + } } - } - - return patchesFound; -} -/** - * Finds those connected areas which contain at least 6 patches - * and returns them ordered DESC by the number of contained patches - * @param {Number} maxLabel - */ -function findBiggestConnectedAreas(maxLabel){ - var i, - sum, - labelHist = [], - topLabels = []; - - for ( i = 0; i < maxLabel; i++) { - labelHist.push(0); + return patchesFound; } - sum = _patchLabelGrid.data.length; - while (sum--) { - if (_patchLabelGrid.data[sum] > 0) { - labelHist[_patchLabelGrid.data[sum] - 1]++; + + /** + * Finds those connected areas which contain at least 6 patches + * and returns them ordered DESC by the number of contained patches + * @param {Number} maxLabel + */ + function findBiggestConnectedAreas(maxLabel){ + var i, + sum, + labelHist = [], + topLabels = []; + + for ( i = 0; i < maxLabel; i++) { + labelHist.push(0); + } + sum = _patchLabelGrid.data.length; + while (sum--) { + if (_patchLabelGrid.data[sum] > 0) { + labelHist[_patchLabelGrid.data[sum] - 1]++; + } } - } - labelHist = labelHist.map(function(val, idx) { - return { - val: val, - label: idx + 1 - }; - }); + labelHist = labelHist.map(function(val, idx) { + return { + val: val, + label: idx + 1 + }; + }); - labelHist.sort(function(a, b) { - return b.val - a.val; - }); + labelHist.sort(function(a, b) { + return b.val - a.val; + }); - // extract top areas with at least 6 patches present - topLabels = labelHist.filter(function(el) { - return el.val >= 5; - }); + // extract top areas with at least 6 patches present + topLabels = labelHist.filter(function(el) { + return el.val >= 5; + }); - return topLabels; -} + return topLabels; + } -/** - * - */ -function findBoxes(topLabels, maxLabel) { - var i, - j, - sum, - patches = [], - patch, - box, - boxes = [], - hsv = [0, 1, 1], - rgb = [0, 0, 0]; - - for ( i = 0; i < topLabels.length; i++) { - sum = _patchLabelGrid.data.length; - patches.length = 0; - while (sum--) { - if (_patchLabelGrid.data[sum] === topLabels[i].label) { - patch = _imageToPatchGrid.data[sum]; - patches.push(patch); + /** + * + */ + function findBoxes(topLabels, maxLabel) { + var i, + j, + sum, + patches = [], + patch, + box, + boxes = [], + hsv = [0, 1, 1], + rgb = [0, 0, 0]; + + for ( i = 0; i < topLabels.length; i++) { + sum = _patchLabelGrid.data.length; + patches.length = 0; + while (sum--) { + if (_patchLabelGrid.data[sum] === topLabels[i].label) { + patch = _imageToPatchGrid.data[sum]; + patches.push(patch); + } } - } - box = boxFromPatches(patches); - if (box) { - boxes.push(box); - - // draw patch-labels if requested - if (ENV.development && _config.debug.showRemainingPatchLabels) { - for ( j = 0; j < patches.length; j++) { - patch = patches[j]; - hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360; - hsv2rgb(hsv, rgb); - ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, - {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); + box = boxFromPatches(patches); + if (box) { + boxes.push(box); + + // draw patch-labels if requested + if (ENV.development && _config.debug.showRemainingPatchLabels) { + for ( j = 0; j < patches.length; j++) { + patch = patches[j]; + hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360; + hsv2rgb(hsv, rgb); + ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, + {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); + } } } } + return boxes; } - return boxes; -} -/** - * Find similar moments (via cluster) - * @param {Object} moments - */ -function similarMoments(moments) { - var clusters = cluster(moments, 0.90); - var topCluster = topGeneric(clusters, 1, function(e) { - return e.getPoints().length; - }); - var points = [], result = []; - if (topCluster.length === 1) { - points = topCluster[0].item.getPoints(); - for (var i = 0; i < points.length; i++) { - result.push(points[i].point); + /** + * Find similar moments (via cluster) + * @param {Object} moments + */ + function similarMoments(moments) { + var clusters = cluster(moments, 0.90); + var topCluster = topGeneric(clusters, 1, function(e) { + return e.getPoints().length; + }); + var points = [], result = []; + if (topCluster.length === 1) { + points = topCluster[0].item.getPoints(); + for (var i = 0; i < points.length; i++) { + result.push(points[i].point); + } } + return result; } - return result; -} -function skeletonize(x, y) { - _binaryImageWrapper.subImageAsCopy(_subImageWrapper, imageRef(x, y)); - _skeletonizer.skeletonize(); + function skeletonize(x, y) { + _binaryImageWrapper.subImageAsCopy(_subImageWrapper, imageRef(x, y)); + _skeletonizer.skeletonize(); - // Show skeleton if requested - if (ENV.development && _config.debug.showSkeleton) { - _skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, imageRef(x, y)); + // Show skeleton if requested + if (ENV.development && _config.debug.showSkeleton) { + _skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, imageRef(x, y)); + } } -} -/** - * Extracts and describes those patches which seem to contain a barcode pattern - * @param {Array} moments - * @param {Object} patchPos, - * @param {Number} x - * @param {Number} y - * @returns {Array} list of patches - */ -function describePatch(moments, patchPos, x, y) { - var k, - avg, - eligibleMoments = [], - matchingMoments, - patch, - patchesFound = [], - minComponentWeight = Math.ceil(_patchSize.x / 3); - - if (moments.length >= 2) { - // only collect moments which's area covers at least minComponentWeight pixels. - for ( k = 0; k < moments.length; k++) { - if (moments[k].m00 > minComponentWeight) { - eligibleMoments.push(moments[k]); + /** + * Extracts and describes those patches which seem to contain a barcode pattern + * @param {Array} moments + * @param {Object} patchPos, + * @param {Number} x + * @param {Number} y + * @returns {Array} list of patches + */ + function describePatch(moments, patchPos, x, y) { + var k, + avg, + eligibleMoments = [], + matchingMoments, + patch, + patchesFound = [], + minComponentWeight = Math.ceil(_patchSize.x / 3); + + if (moments.length >= 2) { + // only collect moments which's area covers at least minComponentWeight pixels. + for ( k = 0; k < moments.length; k++) { + if (moments[k].m00 > minComponentWeight) { + eligibleMoments.push(moments[k]); + } } - } - // if at least 2 moments are found which have at least minComponentWeights covered - if (eligibleMoments.length >= 2) { - matchingMoments = similarMoments(eligibleMoments); - avg = 0; - // determine the similarity of the moments - for ( k = 0; k < matchingMoments.length; k++) { - avg += matchingMoments[k].rad; - } + // if at least 2 moments are found which have at least minComponentWeights covered + if (eligibleMoments.length >= 2) { + matchingMoments = similarMoments(eligibleMoments); + avg = 0; + // determine the similarity of the moments + for ( k = 0; k < matchingMoments.length; k++) { + avg += matchingMoments[k].rad; + } - // Only two of the moments are allowed not to fit into the equation - // add the patch to the set - if (matchingMoments.length > 1 - && matchingMoments.length >= (eligibleMoments.length / 4) * 3 - && matchingMoments.length > moments.length / 4) { - avg /= matchingMoments.length; - patch = { - index: patchPos[1] * _numPatches.x + patchPos[0], - pos: { - x: x, - y: y - }, - box: [ - vec2.clone([x, y]), - vec2.clone([x + _subImageWrapper.size.x, y]), - vec2.clone([x + _subImageWrapper.size.x, y + _subImageWrapper.size.y]), - vec2.clone([x, y + _subImageWrapper.size.y]) - ], - moments: matchingMoments, - rad: avg, - vec: vec2.clone([Math.cos(avg), Math.sin(avg)]) - }; - patchesFound.push(patch); + // Only two of the moments are allowed not to fit into the equation + // add the patch to the set + if (matchingMoments.length > 1 + && matchingMoments.length >= (eligibleMoments.length / 4) * 3 + && matchingMoments.length > moments.length / 4) { + avg /= matchingMoments.length; + patch = { + index: patchPos[1] * _numPatches.x + patchPos[0], + pos: { + x: x, + y: y + }, + box: [ + vec2.clone([x, y]), + vec2.clone([x + _subImageWrapper.size.x, y]), + vec2.clone([x + _subImageWrapper.size.x, y + _subImageWrapper.size.y]), + vec2.clone([x, y + _subImageWrapper.size.y]) + ], + moments: matchingMoments, + rad: avg, + vec: vec2.clone([Math.cos(avg), Math.sin(avg)]) + }; + patchesFound.push(patch); + } } } + return patchesFound; } - return patchesFound; -} -/** - * finds patches which are connected and share the same orientation - * @param {Object} patchesFound - */ -function rasterizeAngularSimilarity(patchesFound) { - var label = 0, - threshold = 0.95, - currIdx = 0, - j, - patch, - hsv = [0, 1, 1], - rgb = [0, 0, 0]; - - function notYetProcessed() { - var i; - for ( i = 0; i < _patchLabelGrid.data.length; i++) { - if (_patchLabelGrid.data[i] === 0 && _patchGrid.data[i] === 1) { - return i; + /** + * finds patches which are connected and share the same orientation + * @param {Object} patchesFound + */ + function rasterizeAngularSimilarity(patchesFound) { + var label = 0, + threshold = 0.95, + currIdx = 0, + j, + patch, + hsv = [0, 1, 1], + rgb = [0, 0, 0]; + + function notYetProcessed() { + var i; + for ( i = 0; i < _patchLabelGrid.data.length; i++) { + if (_patchLabelGrid.data[i] === 0 && _patchGrid.data[i] === 1) { + return i; + } } + return _patchLabelGrid.length; } - return _patchLabelGrid.length; - } - function trace(currentIdx) { - var x, - y, - currentPatch, - idx, - dir, - current = { - x: currentIdx % _patchLabelGrid.size.x, - y: (currentIdx / _patchLabelGrid.size.x) | 0 - }, - similarity; - - if (currentIdx < _patchLabelGrid.data.length) { - currentPatch = _imageToPatchGrid.data[currentIdx]; - // assign label - _patchLabelGrid.data[currentIdx] = label; - for ( dir = 0; dir < Tracer.searchDirections.length; dir++) { - y = current.y + Tracer.searchDirections[dir][0]; - x = current.x + Tracer.searchDirections[dir][1]; - idx = y * _patchLabelGrid.size.x + x; - - // continue if patch empty - if (_patchGrid.data[idx] === 0) { - _patchLabelGrid.data[idx] = Number.MAX_VALUE; - continue; - } + function trace(currentIdx) { + var x, + y, + currentPatch, + idx, + dir, + current = { + x: currentIdx % _patchLabelGrid.size.x, + y: (currentIdx / _patchLabelGrid.size.x) | 0 + }, + similarity; + + if (currentIdx < _patchLabelGrid.data.length) { + currentPatch = _imageToPatchGrid.data[currentIdx]; + // assign label + _patchLabelGrid.data[currentIdx] = label; + for ( dir = 0; dir < Tracer.searchDirections.length; dir++) { + y = current.y + Tracer.searchDirections[dir][0]; + x = current.x + Tracer.searchDirections[dir][1]; + idx = y * _patchLabelGrid.size.x + x; + + // continue if patch empty + if (_patchGrid.data[idx] === 0) { + _patchLabelGrid.data[idx] = Number.MAX_VALUE; + continue; + } - if (_patchLabelGrid.data[idx] === 0) { - similarity = Math.abs(vec2.dot(_imageToPatchGrid.data[idx].vec, currentPatch.vec)); - if (similarity > threshold) { - trace(idx); + if (_patchLabelGrid.data[idx] === 0) { + similarity = Math.abs(vec2.dot(_imageToPatchGrid.data[idx].vec, currentPatch.vec)); + if (similarity > threshold) { + trace(idx); + } } } } } - } - // prepare for finding the right patches - ArrayHelper.init(_patchGrid.data, 0); - ArrayHelper.init(_patchLabelGrid.data, 0); - ArrayHelper.init(_imageToPatchGrid.data, null); + // prepare for finding the right patches + ArrayHelper.init(_patchGrid.data, 0); + ArrayHelper.init(_patchLabelGrid.data, 0); + ArrayHelper.init(_imageToPatchGrid.data, null); - for ( j = 0; j < patchesFound.length; j++) { - patch = patchesFound[j]; - _imageToPatchGrid.data[patch.index] = patch; - _patchGrid.data[patch.index] = 1; - } + for ( j = 0; j < patchesFound.length; j++) { + patch = patchesFound[j]; + _imageToPatchGrid.data[patch.index] = patch; + _patchGrid.data[patch.index] = 1; + } - // rasterize the patches found to determine area - _patchGrid.zeroBorder(); + // rasterize the patches found to determine area + _patchGrid.zeroBorder(); - while (( currIdx = notYetProcessed()) < _patchLabelGrid.data.length) { - label++; - trace(currIdx); - } + while (( currIdx = notYetProcessed()) < _patchLabelGrid.data.length) { + label++; + trace(currIdx); + } - // draw patch-labels if requested - if (ENV.development && _config.debug.showPatchLabels) { - for ( j = 0; j < _patchLabelGrid.data.length; j++) { - if (_patchLabelGrid.data[j] > 0 && _patchLabelGrid.data[j] <= label) { - patch = _imageToPatchGrid.data[j]; - hsv[0] = (_patchLabelGrid.data[j] / (label + 1)) * 360; - hsv2rgb(hsv, rgb); - ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, - {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); + // draw patch-labels if requested + if (ENV.development && _config.debug.showPatchLabels) { + for ( j = 0; j < _patchLabelGrid.data.length; j++) { + if (_patchLabelGrid.data[j] > 0 && _patchLabelGrid.data[j] <= label) { + patch = _imageToPatchGrid.data[j]; + hsv[0] = (_patchLabelGrid.data[j] / (label + 1)) * 360; + hsv2rgb(hsv, rgb); + ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, + {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); + } } } - } - return label; -} - -export default { - init: function(inputImageWrapper, config) { - _config = config; - _inputImageWrapper = inputImageWrapper; - - initBuffers(); - initCanvas(); - }, + return label; + } - locate: function() { - var patchesFound, - topLabels, - boxes; + return { + locate: function() { + var patchesFound, + topLabels, + boxes; - if (_config.halfSample) { - halfSample(_inputImageWrapper, _currentImageWrapper); - } + if (_config.halfSample) { + halfSample(_inputImageWrapper, _currentImageWrapper); + } - binarizeImage(); - patchesFound = findPatches(); - // return unless 5% or more patches are found - if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) { - return null; - } + binarizeImage(); + patchesFound = findPatches(); + // return unless 5% or more patches are found + if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) { + return null; + } - // rasterrize area by comparing angular similarity; - var maxLabel = rasterizeAngularSimilarity(patchesFound); - if (maxLabel < 1) { - return null; - } + // rasterrize area by comparing angular similarity; + var maxLabel = rasterizeAngularSimilarity(patchesFound); + if (maxLabel < 1) { + return null; + } - // search for area with the most patches (biggest connected area) - topLabels = findBiggestConnectedAreas(maxLabel); - if (topLabels.length === 0) { - return null; - } + // search for area with the most patches (biggest connected area) + topLabels = findBiggestConnectedAreas(maxLabel); + if (topLabels.length === 0) { + return null; + } - boxes = findBoxes(topLabels, maxLabel); - return boxes; - }, - - checkImageConstraints: function(inputStream, config) { - var patchSize, - width = inputStream.getWidth(), - height = inputStream.getHeight(), - halfSample = config.halfSample ? 0.5 : 1, - size, - area; - - // calculate width and height based on area - if (inputStream.getConfig().area) { - area = computeImageArea(width, height, inputStream.getConfig().area); - inputStream.setTopRight({x: area.sx, y: area.sy}); - inputStream.setCanvasSize({x: width, y: height}); - width = area.sw; - height = area.sh; + boxes = findBoxes(topLabels, maxLabel); + return boxes; } + } +} +export function checkImageConstraints(inputStream, config) { + var patchSize, + width = inputStream.getWidth(), + height = inputStream.getHeight(), + halfSample = config.halfSample ? 0.5 : 1, + size, + area; + + // calculate width and height based on area + if (inputStream.getConfig().area) { + area = computeImageArea(width, height, inputStream.getConfig().area); + inputStream.setTopRight({x: area.sx, y: area.sy}); + inputStream.setCanvasSize({x: width, y: height}); + width = area.sw; + height = area.sh; + } - size = { - x: Math.floor(width * halfSample), - y: Math.floor(height * halfSample) - }; - - patchSize = calculatePatchSize(config.patchSize, size); - if (ENV.development) { - console.log("Patch-Size: " + JSON.stringify(patchSize)); - } + size = { + x: Math.floor(width * halfSample), + y: Math.floor(height * halfSample) + }; - inputStream.setWidth(Math.floor(Math.floor(size.x / patchSize.x) * (1 / halfSample) * patchSize.x)); - inputStream.setHeight(Math.floor(Math.floor(size.y / patchSize.y) * (1 / halfSample) * patchSize.y)); + patchSize = calculatePatchSize(config.patchSize, size); + if (ENV.development) { + console.log("Patch-Size: " + JSON.stringify(patchSize)); + } - if ((inputStream.getWidth() % patchSize.x) === 0 && (inputStream.getHeight() % patchSize.y) === 0) { - return true; - } + inputStream.setWidth(Math.floor(Math.floor(size.x / patchSize.x) * (1 / halfSample) * patchSize.x)); + inputStream.setHeight(Math.floor(Math.floor(size.y / patchSize.y) * (1 / halfSample) * patchSize.y)); - throw new Error("Image dimensions do not comply with the current settings: Width (" + - width + " )and height (" + height + - ") must a multiple of " + patchSize.x); + if ((inputStream.getWidth() % patchSize.x) === 0 && (inputStream.getHeight() % patchSize.y) === 0) { + return true; } -}; + + throw new Error("Image dimensions do not comply with the current settings: Width (" + + width + " )and height (" + height + + ") must a multiple of " + patchSize.x); +} diff --git a/src/quagga.js b/src/quagga.js index cea179e..b79db56 100644 --- a/src/quagga.js +++ b/src/quagga.js @@ -2,7 +2,6 @@ import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars import WebrtcAdapter from 'webrtc-adapter'; // eslint-disable-line no-unused-vars import createScanner from './scanner'; import ImageWrapper from './common/image_wrapper'; -import Events from './common/events'; import ImageDebug from './common/image_debug'; import ResultCollector from './analytics/result_collector'; import Config from './config/config'; @@ -31,28 +30,18 @@ function fromImage(config, imageSrc, imageConfig) { const scanner = createScanner(config); return { addEventListener: (eventType, cb) => { - scanner.init(config, () => { - Events.once(eventType, (result) => { - scanner.stop(); - cb(result); - }, true); - scanner.start(); - }); + scanner.decodeSingle(config, cb); }, removeEventListener(cb) { console.log("Remove listener"); }, toPromise() { return new Promise((resolve, reject) => { - scanner.init(config, () => { - Events.once('processed', (result) => { - scanner.stop(); - if (result.codeResult && result.codeResult.code) { - return resolve(result); - } - return reject(result); - }); - scanner.start(); + scanner.decodeSingle(config, (result) => { + if (result.codeResult && result.codeResult.code) { + return resolve(result); + } + return reject(result); }); }); } @@ -117,9 +106,6 @@ function createApi(configuration = Config) { config(conf) { return createApi(merge({}, configuration, conf)); }, - init: function(config, cb, imageWrapper) { - defaultScanner.init(config, cb, imageWrapper); - }, start: function() { defaultScanner.start(); }, @@ -129,24 +115,9 @@ function createApi(configuration = Config) { pause: function() { defaultScanner.pause(); }, - onDetected: function(callback) { - defaultScanner.onDetected(callback); - }, - offDetected: function(callback) { - defaultScanner.offDetected(callback); - }, - onProcessed: function(callback) { - defaultScanner.onProcessed(callback); - }, - offProcessed: function(callback) { - defaultScanner.offProcessed(callback); - }, registerResultCollector: function(resultCollector) { defaultScanner.registerResultCollector(resultCollector); }, - decodeSingle: function(config, resultCallback) { - defaultScanner.decodeSingle(config, resultCallback); - }, ImageWrapper: ImageWrapper, ImageDebug: ImageDebug, ResultCollector: ResultCollector, diff --git a/src/scanner.js b/src/scanner.js index fe4b784..b19c364 100644 --- a/src/scanner.js +++ b/src/scanner.js @@ -1,7 +1,7 @@ import ImageWrapper from './common/image_wrapper'; -import BarcodeLocator from './locator/barcode_locator'; +import createLocator, {checkImageConstraints} from './locator/barcode_locator'; import BarcodeDecoder from './decoder/barcode_decoder'; -import Events from './common/events'; +import createEventedElement from './common/events'; import CameraAccess from './input/camera_access'; import ImageDebug from './common/image_debug'; import ResultCollector from './analytics/result_collector'; @@ -34,7 +34,9 @@ function createScanner() { _workerPool = [], _onUIThread = true, _resultCollector, - _config = {}; + _config = {}, + _events = createEventedElement(), + _locator; function initializeData(imageWrapper) { initBuffers(imageWrapper); @@ -85,7 +87,7 @@ function createScanner() { } function canRecord(cb) { - BarcodeLocator.checkImageConstraints(_inputStream, _config.locator); + checkImageConstraints(_inputStream, _config.locator); initCanvas(_config); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); @@ -155,12 +157,12 @@ function createScanner() { vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]), vec2.clone([_inputImageWrapper.size.x, 0]) ]; - BarcodeLocator.init(_inputImageWrapper, _config.locator); + _locator = createLocator(_inputImageWrapper, _config.locator); } function getBoundingBoxes() { if (_config.locate) { - return BarcodeLocator.locate(); + return _locator.locate(); } else { return [[ vec2.clone(_boxSize[0]), @@ -245,9 +247,9 @@ function createScanner() { resultToPublish = result.barcodes || result; } - Events.publish("processed", resultToPublish); + _events.publish("processed", resultToPublish); if (hasCodeResult(result)) { - Events.publish("detected", resultToPublish); + _events.publish("detected", resultToPublish); } } @@ -498,16 +500,16 @@ function createScanner() { _stopped = true; }, onDetected: function(callback) { - Events.subscribe("detected", callback); + _events.subscribe("detected", callback); }, offDetected: function(callback) { - Events.unsubscribe("detected", callback); + _events.unsubscribe("detected", callback); }, onProcessed: function(callback) { - Events.subscribe("processed", callback); + _events.subscribe("processed", callback); }, offProcessed: function(callback) { - Events.unsubscribe("processed", callback); + _events.unsubscribe("processed", callback); }, registerResultCollector: function(resultCollector) { if (resultCollector && typeof resultCollector.addResult === 'function') { @@ -516,7 +518,7 @@ function createScanner() { }, decodeSingle: function(config, resultCallback) { this.init(config, () => { - Events.once("processed", (result) => { + _events.once("processed", (result) => { this.stop(); resultCallback.call(null, result); }, true);