Merge pull request #12 from serratus/issue-1a

Issue 1a
pull/14/head
Christoph Oberhofer 11 years ago
commit 1efe8a2426

1
.gitignore vendored

@ -3,3 +3,4 @@ node_modules/
coverage/
.project
_site/
.idea/

@ -48,13 +48,18 @@ module.exports = function(grunt) {
"glMatrixAddon" : {
"deps" : ["glMatrix"],
"exports" : "glMatrixAddon"
},
"async": {
"deps": [],
"exports": "async"
}
},
"paths" : {
"typedefs" : "typedefs",
"glMatrix" : "vendor/glMatrix",
"glMatrixAddon" : "glMatrixAddon"
"glMatrixAddon" : "glMatrixAddon",
"async": "vendor/async"
}
}
}
@ -68,7 +73,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-requirejs');
grunt.loadNpmTasks('grunt-karma');
// Default task(s).
grunt.registerTask('default', ['jshint', 'requirejs', 'uglify']);
grunt.registerTask('default', ['jshint', 'requirejs']);
grunt.registerTask('test', ['karma']);
};

@ -1,6 +1,8 @@
quaggaJS
========
- [Changelog](#changelog) (2015-01-21)
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-time localization and decoding of
various types of barcodes such as __EAN__ and __CODE128__. The library is also capable of using `getUserMedia` to get direct
access to the user's camera stream. Although the code relies on heavy image-processing even recent smartphones are
@ -46,24 +48,23 @@ by simply typing:
You can check out the [examples](http://serratus.github.io/quaggaJS/examples) to get an idea of how to use QuaggaJS.
Basically the library exposes the following API:
### Quagga.init(config)
### Quagga.init(config, callback)
This method initializes the library for a given configuration (see below) which also includes a callback function (`readyFunc`) which is called when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured.
This method initializes the library for a given configuration `config` (see below) and invokes the `callback` when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured.
```javascript
Quagga.init({
inputStream : {
name : "Live",
type : "LiveStream"
},
decoder : {
readers : ["code_128_reader"]
},
readyFunc : function() {
console.log("Initialization finished. Ready to start");
Quagga.start();
}
});
inputStream : {
name : "Live",
type : "LiveStream"
},
decoder : {
readers : ["code_128_reader"]
}
}, function() {
console.log("Initialization finished. Ready to start");
Quagga.start();
});
```
### Quagga.start()
@ -73,18 +74,91 @@ the images.
### Quagga.stop()
If the decoder is currently running, after calling `stop()` the decoder does not process any more images.
If the decoder is currently running, after calling `stop()` the decoder does not process any more images. Additionally, if a camera-stream was requested upon initialization, this operation also disconnects the camera.
### Quagga.onProcessed(callback)
This method registers a `callback(data)` function that is called for each frame after the processing is done. The `data` object contains detailed information about the success/failure of the operation. The output varies, depending whether the detection and/or decoding were successful or not.
### Quagga.onDetected(callback)
Registers a callback function which is triggered whenever a barcode-pattern has been located and decoded
successfully. The callback function is called with the decoded data as the first parameter.
Registers a `callback(data)` function which is triggered whenever a barcode-pattern has been located and decoded
successfully. The passed `data` object contains information about the decoding process including the detected code which can be obtained by calling `data.codeResult.code`.
### Quagga.decodeSingle(config, callback)
In contrast to the calls described above, this method does not rely on `getUserMedia` and operates on a
single image instead. The provided callback is the same as in `onDetected` and contains the decoded data
as first parameter.
single image instead. The provided callback is the same as in `onDetected` and contains the result `data`
object.
## <a name="resultobject">The result object</a>
The callbacks passed into `onProcessed`, `onDetected` and `decodeSingle` receive a `data` object upon execution. The `data` object contains the following information. Depending on the success, some fields may be `undefined` or just empty.
```javascript
{
"codeResult": {
"code": "FANAVF1461710",
"start": 355,
"end": 26,
"codeset": 100,
"startInfo": {
"error": 1.0000000000000002,
"code": 104,
"start": 21,
"end": 41
},
"decodedCodes": [{
"code": 104,
"start": 21,
"end": 41
},
// stripped for brevity
{
"error": 0.8888888888888893,
"code": 106,
"start": 328,
"end": 350
}],
"endInfo": {
"error": 0.8888888888888893,
"code": 106,
"start": 328,
"end": 350
},
"direction": -1
},
"line": [{
"x": 25.97278706156836,
"y": 360.5616435369468
}, {
"x": 401.9220519377024,
"y": 70.87524989906444
}],
"angle": -0.6565217179979483,
"pattern": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, /* ... */ 1],
"box": [
[77.4074243622672, 410.9288668804402],
[0.050203235235130705, 310.53619724086366],
[360.15706727788256, 33.05711026051813],
[437.5142884049146, 133.44977990009465]
],
"boxes": [
[
[77.4074243622672, 410.9288668804402],
[0.050203235235130705, 310.53619724086366],
[360.15706727788256, 33.05711026051813],
[437.5142884049146, 133.44977990009465]
],
[
[248.90769330706507, 415.2041489551161],
[198.9532321622869, 352.62160512937635],
[339.546160777576, 240.3979259789976],
[389.5006219223542, 302.98046980473737]
]
]
}
```
## Config
@ -99,11 +173,13 @@ The default `config` object is set as followed:
debug: false,
controls: false,
locate: true,
numOfWorkers: 4,
scriptName: 'quagga.js',
visual: {
show: true
},
decoder:{
drawBoundingBox: true,
drawBoundingBox: false,
showFrequency: false,
drawScanline: true,
showPattern: false,
@ -112,6 +188,7 @@ The default `config` object is set as followed:
]
},
locator: {
halfSample: true,
showCanvas: false,
showPatches: false,
showFoundPatches: false,
@ -151,7 +228,24 @@ Unit Tests can be run with [Karma][karmaUrl] and written using [Mocha][mochaUrl]
> npm install
> grunt test
```
## Image Debugging
In case you want to take a deeper dive into the inner workings of Quagga, get to know the _debugging_ capabilities of the current implementation. The various flags exposed through the `config` object give you the abilily to visualize almost every step in the processing. Because of the introduction of the web-workers, and their restriction not to have access to the DOM, the configuration must be explicitly set to `config.numOfWorkers = 0` in order to work.
## <a name="changelog">Changelog</a>
### 2015-01-21
- Features
- Added support for web-worker (using 4 workers as default, can be changed through `config.numOfWorkers`)
- Due to the way how web-workers are created, the name of the script file (`config.scriptName`) should be kept in sync with your actual filename
- Removed canvas-overlay for decoding (boxes & scanline) which can now be easily implemented using the existing API (see example)
- API Changes
In the course of implementing web-workers some breaking changes were introduced to the API.
- The `Quagga.init` function no longer receives the callback as part of the config but rather as a second argument: `Quagga.init(config, cb)`
- The callback to `Quagga.onDetected` now receives an object containing much more information in addition to the decoded code. (see [data](#resultobject))
- Added `Quagga.onProcessed(callback)` which provides a way to get information for each image processed.
The callback receives the same `data` object as `Quagga.onDetected` does. Depending on the success of the process the `data` object
might not contain any `resultCode` and/or `box` properties.
[zxing_github]: https://github.com/zxing/zxing
[teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png

1555
dist/quagga.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -27,6 +27,7 @@
It works best if your camera has built-in auto-focus.
</p>
<div class="controls">
<button class="stop">Stop</button>
<fieldset class="reader-group">
<label>Code128</label>
<input type="radio" name="reader" value="code_128" checked />

@ -8,11 +8,10 @@ $(function() {
},
decoder : {
readers : ["code_128_reader"]
},
readyFunc : function() {
App.attachListeners();
Quagga.start();
}
}, function() {
App.attachListeners();
Quagga.start();
});
},
attachListeners : function() {
@ -20,23 +19,55 @@ $(function() {
e.preventDefault();
Quagga.setReaders([e.target.value + "_reader"]);
});
$(".controls").on("click", "button.stop", function(e) {
e.preventDefault();
Quagga.stop();
});
},
detachListeners : function() {
$(".controls .reader-group").off("change", "input");
$(".controls").off("click", "button.stop");
},
lastResult : null
};
App.init();
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});
Quagga.onDetected(function(result) {
if (App.lastResult !== result) {
App.lastResult = result;
var code = result.codeResult.code;
if (App.lastResult !== code) {
App.lastResult = code;
var $node = null, canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(result);
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
}
});

@ -41,6 +41,7 @@
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="debug"></div>
</section>
<footer>
<p>

@ -4,16 +4,15 @@ $(function() {
Quagga.init({
inputStream: { name: "Test",
type: "ImageStream",
src: "/test/fixtures/" + App.config.reader + "/",
src: "../test/fixtures/" + App.config.reader + "/",
length: App.config.length
},
decoder : {
readers : [App.config.reader + "_reader"]
},
readyFunc : function() {
App.attachListeners();
Quagga.start();
}
}, function() {
App.attachListeners();
Quagga.start();
});
},
config: {
@ -42,12 +41,38 @@ $(function() {
App.init();
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});
Quagga.onDetected(function(result) {
var $node = null, canvas = Quagga.canvas.dom.image;
var $node,
canvas = Quagga.canvas.dom.image,
detectedCode = result.codeResult.code;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(result);
$node.find("h4.code").html(detectedCode);
$("#result_strip ul.thumbnails").prepend($node);
});
});

@ -30,30 +30,32 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
initConfig();
function initCanvas() {
var $debug = document.querySelector("#debug.detection");
_canvas.dom.frequency = document.querySelector("canvas.frequency");
if (!_canvas.dom.frequency) {
_canvas.dom.frequency = document.createElement("canvas");
_canvas.dom.frequency.className = "frequency";
if($debug) {
$debug.appendChild(_canvas.dom.frequency);
if (typeof document !== 'undefined') {
var $debug = document.querySelector("#debug.detection");
_canvas.dom.frequency = document.querySelector("canvas.frequency");
if (!_canvas.dom.frequency) {
_canvas.dom.frequency = document.createElement("canvas");
_canvas.dom.frequency.className = "frequency";
if($debug) {
$debug.appendChild(_canvas.dom.frequency);
}
}
}
_canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d");
_canvas.dom.pattern = document.querySelector("canvas.patternBuffer");
if (!_canvas.dom.pattern) {
_canvas.dom.pattern = document.createElement("canvas");
_canvas.dom.pattern.className = "patternBuffer";
if($debug) {
$debug.appendChild(_canvas.dom.pattern);
_canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d");
_canvas.dom.pattern = document.querySelector("canvas.patternBuffer");
if (!_canvas.dom.pattern) {
_canvas.dom.pattern = document.createElement("canvas");
_canvas.dom.pattern.className = "patternBuffer";
if($debug) {
$debug.appendChild(_canvas.dom.pattern);
}
}
}
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
}
}
}
@ -66,20 +68,22 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
}
function initConfig() {
var i,
vis = [{
node : _canvas.dom.frequency,
prop : config.showFrequency
}, {
node : _canvas.dom.pattern,
prop : config.showPattern
}];
for (i = 0; i < vis.length; i++) {
if (vis[i].prop === true) {
vis[i].node.style.display = "block";
} else {
vis[i].node.style.display = "none";
if (typeof document !== 'undefined') {
var i,
vis = [{
node : _canvas.dom.frequency,
prop : config.showFrequency
}, {
node : _canvas.dom.pattern,
prop : config.showPattern
}];
for (i = 0; i < vis.length; i++) {
if (vis[i].prop === true) {
vis[i].node.style.display = "block";
} else {
vis[i].node.style.display = "none";
}
}
}
}
@ -235,6 +239,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
for ( i = 0; i < boxes.length; i++) {
result = decodeFromBoundingBox(boxes[i]);
if (result && result.codeResult) {
result.box = boxes[i];
return result;
}
}

@ -14,7 +14,6 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
_patchLabelGrid,
_imageToPatchGrid,
_binaryImageWrapper,
_halfSample = true,
_patchSize,
_canvasContainer = {
ctx : {
@ -26,12 +25,13 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
},
_numPatches = {x: 0, y: 0},
_inputImageWrapper,
_skeletonizer;
_skeletonizer,
self = this;
function initBuffers() {
var skeletonImageData;
if (_halfSample) {
if (_config.halfSample) {
_currentImageWrapper = new ImageWrapper({
x : _inputImageWrapper.size.x / 2 | 0,
y : _inputImageWrapper.size.y / 2 | 0
@ -41,8 +41,8 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
}
_patchSize = {
x : 16 * ( _halfSample ? 1 : 2),
y : 16 * ( _halfSample ? 1 : 2)
x : 16 * ( _config.halfSample ? 1 : 2),
y : 16 * ( _config.halfSample ? 1 : 2)
};
_numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0;
@ -52,10 +52,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
_labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true);
skeletonImageData = new ArrayBuffer(_patchSize.x * _patchSize.y * 16);
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(window, {
_skeletonizer = skeletonizer(self, {
size : _patchSize.x
}, skeletonImageData);
@ -68,6 +68,9 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
}
function initCanvas() {
if (_config.useWorker || typeof document === 'undefined') {
return;
}
_canvasContainer.dom.binary = document.createElement("canvas");
_canvasContainer.dom.binary.className = "binaryBuffer";
if (_config.showCanvas === true) {
@ -142,7 +145,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2});
}
scale = _halfSample ? 2 : 1;
scale = _config.halfSample ? 2 : 1;
// reverse rotation;
transMat = mat2.inverse(transMat);
for ( j = 0; j < 4; j++) {
@ -472,9 +475,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
}
return {
init : function(config, data) {
init : function(inputImageWrapper, config) {
_config = config;
_inputImageWrapper = data.inputImageWrapper;
_inputImageWrapper = inputImageWrapper;
initBuffers();
initCanvas();
},
@ -483,31 +487,30 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
topLabels = [],
boxes = [];
if (_halfSample) {
if (_config.halfSample) {
CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
}
binarizeImage();
patchesFound = findPatches();
// return unless 5% or more patches are found
if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
return;
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;
}
boxes = findBoxes(topLabels, maxLabel);
return boxes;
}
};

@ -11,19 +11,22 @@ define(function(){
debug: false,
controls: false,
locate: true,
numOfWorkers: 4,
scriptName: 'quagga.js',
visual: {
show: true
},
decoder:{
drawBoundingBox: true,
drawBoundingBox: false,
showFrequency: false,
drawScanline: true,
drawScanline: false,
showPattern: false,
readers: [
'code_128_reader'
]
},
locator: {
halfSample: true,
showCanvas: false,
showPatches: false,
showFoundPatches: false,

@ -23,10 +23,10 @@ define(function() {
function publishSubscription(subscription, data) {
if (subscription.async) {
setTimeout(function() {
subscription.callback.call(null, data);
subscription.callback(data);
}, 4);
} else {
subscription.callback.call(null, data);
subscription.callback(data);
}
}

@ -1,8 +1,8 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2 */
/* global define, vec2, importScripts */
define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access"],
function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess) {
define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access", "async", "image_debug"],
function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess, async, ImageDebug) {
"use strict";
var _inputStream,
@ -21,11 +21,12 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputImageWrapper,
_boxSize,
_decoder,
_initialized = false;
_workerPool = [],
_onUIThread = true;
function initialize(config) {
_config = HtmlUtils.mergeObjects(_config, config);
initInputStream();
function initializeData(imageWrapper) {
initBuffers(imageWrapper);
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
}
function initConfig() {
@ -48,7 +49,7 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
}
}
function initInputStream() {
function initInputStream(cb) {
var video;
if (_config.inputStream.type == "VideoStream") {
video = document.createElement("video");
@ -74,24 +75,30 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputStream.setAttribute("preload", "auto");
_inputStream.setAttribute("autoplay", true);
_inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord);
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
}
function canRecord() {
initBuffers();
function canRecord(cb) {
initCanvas();
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
_framegrabber.attachData(_inputImageWrapper.data);
initConfig();
_inputStream.play();
_initialized = true;
if (_config.readyFunc) {
_config.readyFunc.apply();
if (_config.numOfWorkers > 0) {
initWorkers(function() {
console.log("Workers created");
ready(cb);
});
} else {
initializeData();
ready(cb);
}
}
function ready(cb){
_inputStream.play();
cb();
}
function initCanvas() {
var $viewport = document.querySelector("#interactive.viewport");
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
@ -103,8 +110,8 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
}
}
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.width = _inputImageWrapper.size.x;
_canvasContainer.dom.image.height = _inputImageWrapper.size.y;
_canvasContainer.dom.image.width = _inputStream.getWidth();
_canvasContainer.dom.image.height = _inputStream.getHeight();
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) {
@ -120,52 +127,86 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
}
}
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputImageWrapper.size.x;
_canvasContainer.dom.overlay.height = _inputImageWrapper.size.y;
_canvasContainer.dom.overlay.width = _inputStream.getWidth();
_canvasContainer.dom.overlay.height = _inputStream.getHeight();
}
function initBuffers() {
_inputImageWrapper = new ImageWrapper({
x : _inputStream.getWidth(),
y : _inputStream.getHeight()
});
console.log(_inputStream.getWidth());
console.log(_inputStream.getHeight());
function initBuffers(imageWrapper) {
if (imageWrapper) {
_inputImageWrapper = imageWrapper;
} else {
_inputImageWrapper = new ImageWrapper({
x : _inputStream.getWidth(),
y : _inputStream.getHeight()
});
}
console.log(_inputImageWrapper.size);
_boxSize = [
vec2.create([20, _inputStream.getHeight() / 2 - 100]),
vec2.create([20, _inputStream.getHeight() / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 - 100])
vec2.create([20, _inputImageWrapper.size.y / 2 - 100]),
vec2.create([20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100])
];
BarcodeLocator.init(_config.locator, {
inputImageWrapper : _inputImageWrapper
});
BarcodeLocator.init(_inputImageWrapper, _config.locator);
}
function getBoundingBoxes() {
var boxes;
if (_config.locate) {
boxes = BarcodeLocator.locate();
return BarcodeLocator.locate();
} else {
boxes = [_boxSize];
return [_boxSize];
}
return boxes;
}
function update() {
function locateAndDecode() {
var result,
boxes;
if (_framegrabber.grab()) {
_canvasContainer.ctx.overlay.clearRect(0, 0, _inputImageWrapper.size.x, _inputImageWrapper.size.y);
boxes = getBoundingBoxes();
if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes);
if (result && result.codeResult) {
Events.publish("detected", result.codeResult.code);
boxes = getBoundingBoxes();
if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes);
result = result || {};
result.boxes = boxes;
Events.publish("processed", result);
if (result && result.codeResult) {
Events.publish("detected", result);
}
} else {
Events.publish("processed");
}
}
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();
}
}
@ -173,20 +214,136 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_stopped = false;
( function frame() {
if (!_stopped) {
if (_config.inputStream.type == "LiveStream") {
update();
if (_onUIThread && _config.inputStream.type == "LiveStream") {
window.requestAnimFrame(frame);
}
update();
}
}());
}
function initWorkers(cb) {
_workerPool = [];
async.times(_config.numOfWorkers, function(n, next) {
initWorker(function(workerThread) {
_workerPool.push(workerThread);
next(null);
});
}, cb);
}
function initWorker(cb) {
var blobURL,
workerThread = {
worker: null,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true
};
blobURL = generateWorkerBlob();
workerThread.worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
workerThread.worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
workerThread.busy = false;
workerThread.imageData = new Uint8Array(e.data.imageData);
console.log("Worker initialized");
return cb(workerThread);
} else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false;
Events.publish("processed", e.data.result);
if (e.data.result && e.data.result.codeResult) {
Events.publish("detected", e.data.result);
}
}
};
workerThread.worker.postMessage({
cmd: 'init',
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData,
config: _config
}, [workerThread.imageData.buffer]);
}
function workerInterface(scriptUrl) {
importScripts(scriptUrl);
/* jshint ignore:start */
var imageWrapper;
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 onProcessed(result) {
self.postMessage({'event': 'processed', imageData: imageWrapper.data, result: result}, [imageWrapper.data.buffer]);
}
function ready() {
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
}
/* jshint ignore:end */
}
function generateWorkerBlob() {
var blob,
quaggaAbsoluteUrl,
scripts = document.getElementsByTagName('script'),
regex = new RegExp('\/' + _config.scriptName + '$');
quaggaAbsoluteUrl = Array.prototype.slice.apply(scripts).filter(function(script) {
return script.src && script.src.match(regex);
}).map(function(script) {
return script.src;
})[0];
blob = new Blob(['(' + workerInterface.toString() + ')("' + quaggaAbsoluteUrl + '");'],
{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});
});
}
}
return {
init : function(config) {
initialize(config);
init : function(config, cb, imageWrapper) {
_config = HtmlUtils.mergeObjects(_config, config);
if (imageWrapper) {
_onUIThread = false;
initializeData(imageWrapper);
return cb();
} else {
initInputStream(cb);
}
},
start : function() {
console.log("Start!");
start();
},
stop : function() {
@ -198,11 +355,11 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
onDetected : function(callback) {
Events.subscribe("detected", callback);
},
isInitialized : function() {
return _initialized;
onProcessed: function(callback) {
Events.subscribe("processed", callback);
},
setReaders: function(readers) {
_decoder.setReaders(readers);
setReaders(readers);
},
canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) {
@ -212,18 +369,20 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
sequence : false,
size: 800
};
config.readyFunc = function() {
config.numOfWorkers = 1;
this.init(config, function() {
Events.once("detected", function(result) {
_stopped = true;
resultCallback.call(null, result);
}, true);
start();
};
initialize(config);
});
},
Reader: {
EANReader : EANReader,
Code128Reader : Code128Reader
}
},
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug
};
});

@ -4,18 +4,18 @@
*/
glMatrixArrayType = Float32Array;
if (typeof window !== 'undefined') {
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
window.setTimeout(callback, 1000/60);
};
})();
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
}

1123
src/vendor/async.js vendored

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save