mirror of
https://github.com/serratus/quaggaJS.git
synced 2026-08-12 21:21:42 +08:00
@@ -3,3 +3,4 @@ node_modules/
|
||||
coverage/
|
||||
.project
|
||||
_site/
|
||||
.idea/
|
||||
|
||||
+7
-2
@@ -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
|
||||
|
||||
Vendored
+1424
-129
File diff suppressed because it is too large
Load Diff
Vendored
-3
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);
|
||||
});
|
||||
});
|
||||
+38
-33
@@ -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.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.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
|
||||
}];
|
||||
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";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-17
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
+5
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+220
-61
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
+14
-14
@@ -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;
|
||||
}
|
||||
|
||||
Vendored
+1123
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user