diff --git a/README.md b/README.md
index ccf72806..2883412e 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
quaggaJS
========
-- [Changelog](#changelog) (2015-07-08)
+- [Changelog](#changelog) (2015-07-29)
## What is QuaggaJS?
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-
time localization and decoding of various types of barcodes such as __EAN__,
-__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__ and __CODABAR__.
-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 capable of locating and decoding barcodes in
-real-time.
+__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__ and
+__CODABAR__. 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 capable of locating and decoding
+barcodes in real-time.
Try some [examples](http://serratus.github.io/quaggaJS/examples) and check out
the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how])
@@ -367,6 +367,10 @@ on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
## Changelog
+### 2015-07-29
+- Features
+ - Added basic support for [ITF][i2of5_wiki] barcodes (`i2of5_reader`)
+
### 2015-07-08
- Improvements
- Parameter tweaking to reduce false-positives significantly (for the
@@ -479,3 +483,4 @@ introduced to the API.
[ean_8_wiki]: http://en.wikipedia.org/wiki/EAN-8
[oberhofer_co_how]: http://www.oberhofer.co/how-barcode-localization-works-in-quaggajs/
[github_examples]: http://serratus.github.io/quaggaJS/examples
+[i2of5_wiki]: https://en.wikipedia.org/wiki/Interleaved_2_of_5
diff --git a/bower.json b/bower.json
index 27b7dbb1..e563ced8 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "quagga",
- "version": "0.6.13",
+ "version": "0.6.14",
"description": "An advanced barcode-scanner written in JavaScript",
"main": "dist/quagga.js",
"ignore": [
@@ -58,6 +58,7 @@
"code128",
"code39",
"codabar",
+ "i2of5",
"upc",
"getusermedia",
"imageprocessing"
diff --git a/dist/quagga.js b/dist/quagga.js
index 1436e75c..e755c716 100644
--- a/dist/quagga.js
+++ b/dist/quagga.js
@@ -435,6391 +435,6417 @@ define("almond", function(){});
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
-define(
- 'barcode_reader',[],function() {
- "use strict";
-
- function BarcodeReader() {
- this._row = [];
- return this;
- }
-
- BarcodeReader.prototype._nextUnset = function(line, start) {
- var i;
+define('image_loader',[],function() {
+ "use strict";
+
+ var ImageLoader = {};
+ ImageLoader.load = function(directory, callback, offset, size, sequence) {
+ var htmlImagesSrcArray = new Array(size),
+ htmlImagesArray = new Array(htmlImagesSrcArray.length),
+ i,
+ img,
+ num;
- if (start === undefined) {
- start = 0;
+ if (sequence === false) {
+ htmlImagesSrcArray[0] = directory;
+ } else {
+ for ( i = 0; i < htmlImagesSrcArray.length; i++) {
+ num = (offset + i);
+ htmlImagesSrcArray[i] = directory + "image-" + ("00" + num).slice(-3) + ".jpg";
}
- for (i = start; i < line.length; i++) {
- if (!line[i]) {
- return i;
+ }
+ htmlImagesArray.notLoaded = [];
+ htmlImagesArray.addImage = function(img) {
+ htmlImagesArray.notLoaded.push(img);
+ };
+ htmlImagesArray.loaded = function(loadedImg) {
+ var notloadedImgs = htmlImagesArray.notLoaded;
+ for (var x = 0; x < notloadedImgs.length; x++) {
+ if (notloadedImgs[x] == loadedImg) {
+ notloadedImgs.splice(x, 1);
+ for (var y = 0; y < htmlImagesSrcArray.length; y++) {
+ var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/"));
+ if (loadedImg.src.lastIndexOf(imgName) != -1) {
+ htmlImagesArray[y] = loadedImg;
+ break;
+ }
+ }
+ break;
}
}
- return line.length;
+ if (notloadedImgs.length === 0) {
+ console.log("Images loaded");
+ callback.apply(null, [htmlImagesArray]);
+ }
};
- BarcodeReader.prototype._matchPattern = function(counter, code) {
- var i,
- error = 0,
- singleError = 0,
- modulo = this.MODULO,
- maxSingleError = this.SINGLE_CODE_ERROR || 1;
-
- for (i = 0; i < counter.length; i++) {
- singleError = Math.abs(code[i] - counter[i]);
- if (singleError > maxSingleError) {
- return Number.MAX_VALUE;
- }
- error += singleError;
- }
- return error/modulo;
+ for ( i = 0; i < htmlImagesSrcArray.length; i++) {
+ img = new Image();
+ htmlImagesArray.addImage(img);
+ addOnloadHandler(img, htmlImagesArray);
+ img.src = htmlImagesSrcArray[i];
+ }
+ };
+
+ function addOnloadHandler(img, htmlImagesArray) {
+ img.onload = function() {
+ htmlImagesArray.loaded(this);
};
+ }
- BarcodeReader.prototype._nextSet = function(line, offset) {
- var i;
+ return (ImageLoader);
+});
- offset = offset || 0;
- for (i = offset; i < line.length; i++) {
- if (line[i]) {
- return i;
- }
- }
- return line.length;
- };
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- BarcodeReader.prototype._normalize = function(counter, modulo) {
- var i,
- self = this,
- sum = 0,
- ratio,
- numOnes = 0,
- normalized = [],
- norm = 0;
-
- if (!modulo) {
- modulo = self.MODULO;
- }
- for (i = 0; i < counter.length; i++) {
- if (counter[i] === 1) {
- numOnes++;
- } else {
- sum += counter[i];
- }
- }
- ratio = sum / (modulo - numOnes);
- if (ratio > 1.0) {
- for (i = 0; i < counter.length; i++) {
- norm = counter[i] === 1 ? counter[i] : counter[i] / ratio;
- normalized.push(norm);
- }
- } else {
- ratio = (sum + numOnes)/modulo;
- for (i = 0; i < counter.length; i++) {
- norm = counter[i] / ratio;
- normalized.push(norm);
- }
- }
- return normalized;
- };
+define('input_stream',["image_loader"], function(ImageLoader) {
+ "use strict";
- BarcodeReader.prototype._matchTrace = function(cmpCounter, epsilon) {
- var counter = [],
- i,
- self = this,
- offset = self._nextSet(self._row),
- isWhite = !self._row[offset],
- counterPos = 0,
- bestMatch = {
- error : Number.MAX_VALUE,
- code : -1,
- start : 0
- },
- error;
+ var InputStream = {};
+ InputStream.createVideoStream = function(video) {
+ var that = {},
+ _config = null,
+ _eventNames = ['canrecord', 'ended'],
+ _eventHandlers = {},
+ _calculatedWidth,
+ _calculatedHeight,
+ _topRight = {x: 0, y: 0},
+ _canvasSize = {x: 0, y: 0};
- if (cmpCounter) {
- for ( i = 0; i < cmpCounter.length; i++) {
- counter.push(0);
- }
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- if (counterPos === counter.length - 1) {
- error = self._matchPattern(counter, cmpCounter);
+ function initSize() {
+ var width = video.videoWidth,
+ height = video.videoHeight;
- if (error < epsilon) {
- bestMatch.start = i - offset;
- bestMatch.end = i;
- bestMatch.counter = counter;
- return bestMatch;
- } else {
- return null;
- }
- } else {
- counterPos++;
- }
- counter[counterPos] = 1;
- isWhite = !isWhite;
- }
- }
- } else {
- counter.push(0);
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- counterPos++;
- counter.push(0);
- counter[counterPos] = 1;
- isWhite = !isWhite;
- }
- }
- }
+ _calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
+ _calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
- // if cmpCounter was not given
- bestMatch.start = offset;
- bestMatch.end = self._row.length - 1;
- bestMatch.counter = counter;
- return bestMatch;
- };
-
- BarcodeReader.prototype.decodePattern = function(pattern) {
- var self = this,
- result;
-
- self._row = pattern;
- result = self._decode();
- if (result === null) {
- self._row.reverse();
- result = self._decode();
- if (result) {
- result.direction = BarcodeReader.DIRECTION.REVERSE;
- result.start = self._row.length - result.start;
- result.end = self._row.length - result.end;
- }
- } else {
- result.direction = BarcodeReader.DIRECTION.FORWARD;
- }
- if (result) {
- result.format = self.FORMAT;
- }
- return result;
+ _canvasSize.x = _calculatedWidth;
+ _canvasSize.y = _calculatedHeight;
+ }
+
+ that.getRealWidth = function() {
+ return video.videoWidth;
};
- BarcodeReader.prototype._matchRange = function(start, end, value) {
- var i;
+ that.getRealHeight = function() {
+ return video.videoHeight;
+ };
- start = start < 0 ? 0 : start;
- for (i = start; i < end; i++) {
- if (this._row[i] !== value) {
- return false;
- }
- }
- return true;
+ that.getWidth = function() {
+ return _calculatedWidth;
};
- Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
- value: 'unknown',
- writeable: false
- });
-
- BarcodeReader.DIRECTION = {
- FORWARD : 1,
- REVERSE : -1
+ that.getHeight = function() {
+ return _calculatedHeight;
};
-
- BarcodeReader.Exception = {
- StartNotFoundException : "Start-Info was not found!",
- CodeNotFoundException : "Code could not be found!",
- PatternNotFoundException : "Pattern could not be found!"
+
+ that.setWidth = function(width) {
+ _calculatedWidth = width;
};
-
- return (BarcodeReader);
- }
-);
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+ that.setHeight = function(height) {
+ _calculatedHeight = height;
+ };
-define(
- 'code_128_reader',[
- "./barcode_reader"
- ],
- function(BarcodeReader) {
- "use strict";
-
- function Code128Reader() {
- BarcodeReader.call(this);
- }
-
- var properties = {
- CODE_SHIFT : {value: 98},
- CODE_C : {value: 99},
- CODE_B : {value: 100},
- CODE_A : {value: 101},
- START_CODE_A : {value: 103},
- START_CODE_B : {value: 104},
- START_CODE_C : {value: 105},
- STOP_CODE : {value: 106},
- MODULO : {value: 11},
- CODE_PATTERN : {value: [
- [2, 1, 2, 2, 2, 2],
- [2, 2, 2, 1, 2, 2],
- [2, 2, 2, 2, 2, 1],
- [1, 2, 1, 2, 2, 3],
- [1, 2, 1, 3, 2, 2],
- [1, 3, 1, 2, 2, 2],
- [1, 2, 2, 2, 1, 3],
- [1, 2, 2, 3, 1, 2],
- [1, 3, 2, 2, 1, 2],
- [2, 2, 1, 2, 1, 3],
- [2, 2, 1, 3, 1, 2],
- [2, 3, 1, 2, 1, 2],
- [1, 1, 2, 2, 3, 2],
- [1, 2, 2, 1, 3, 2],
- [1, 2, 2, 2, 3, 1],
- [1, 1, 3, 2, 2, 2],
- [1, 2, 3, 1, 2, 2],
- [1, 2, 3, 2, 2, 1],
- [2, 2, 3, 2, 1, 1],
- [2, 2, 1, 1, 3, 2],
- [2, 2, 1, 2, 3, 1],
- [2, 1, 3, 2, 1, 2],
- [2, 2, 3, 1, 1, 2],
- [3, 1, 2, 1, 3, 1],
- [3, 1, 1, 2, 2, 2],
- [3, 2, 1, 1, 2, 2],
- [3, 2, 1, 2, 2, 1],
- [3, 1, 2, 2, 1, 2],
- [3, 2, 2, 1, 1, 2],
- [3, 2, 2, 2, 1, 1],
- [2, 1, 2, 1, 2, 3],
- [2, 1, 2, 3, 2, 1],
- [2, 3, 2, 1, 2, 1],
- [1, 1, 1, 3, 2, 3],
- [1, 3, 1, 1, 2, 3],
- [1, 3, 1, 3, 2, 1],
- [1, 1, 2, 3, 1, 3],
- [1, 3, 2, 1, 1, 3],
- [1, 3, 2, 3, 1, 1],
- [2, 1, 1, 3, 1, 3],
- [2, 3, 1, 1, 1, 3],
- [2, 3, 1, 3, 1, 1],
- [1, 1, 2, 1, 3, 3],
- [1, 1, 2, 3, 3, 1],
- [1, 3, 2, 1, 3, 1],
- [1, 1, 3, 1, 2, 3],
- [1, 1, 3, 3, 2, 1],
- [1, 3, 3, 1, 2, 1],
- [3, 1, 3, 1, 2, 1],
- [2, 1, 1, 3, 3, 1],
- [2, 3, 1, 1, 3, 1],
- [2, 1, 3, 1, 1, 3],
- [2, 1, 3, 3, 1, 1],
- [2, 1, 3, 1, 3, 1],
- [3, 1, 1, 1, 2, 3],
- [3, 1, 1, 3, 2, 1],
- [3, 3, 1, 1, 2, 1],
- [3, 1, 2, 1, 1, 3],
- [3, 1, 2, 3, 1, 1],
- [3, 3, 2, 1, 1, 1],
- [3, 1, 4, 1, 1, 1],
- [2, 2, 1, 4, 1, 1],
- [4, 3, 1, 1, 1, 1],
- [1, 1, 1, 2, 2, 4],
- [1, 1, 1, 4, 2, 2],
- [1, 2, 1, 1, 2, 4],
- [1, 2, 1, 4, 2, 1],
- [1, 4, 1, 1, 2, 2],
- [1, 4, 1, 2, 2, 1],
- [1, 1, 2, 2, 1, 4],
- [1, 1, 2, 4, 1, 2],
- [1, 2, 2, 1, 1, 4],
- [1, 2, 2, 4, 1, 1],
- [1, 4, 2, 1, 1, 2],
- [1, 4, 2, 2, 1, 1],
- [2, 4, 1, 2, 1, 1],
- [2, 2, 1, 1, 1, 4],
- [4, 1, 3, 1, 1, 1],
- [2, 4, 1, 1, 1, 2],
- [1, 3, 4, 1, 1, 1],
- [1, 1, 1, 2, 4, 2],
- [1, 2, 1, 1, 4, 2],
- [1, 2, 1, 2, 4, 1],
- [1, 1, 4, 2, 1, 2],
- [1, 2, 4, 1, 1, 2],
- [1, 2, 4, 2, 1, 1],
- [4, 1, 1, 2, 1, 2],
- [4, 2, 1, 1, 1, 2],
- [4, 2, 1, 2, 1, 1],
- [2, 1, 2, 1, 4, 1],
- [2, 1, 4, 1, 2, 1],
- [4, 1, 2, 1, 2, 1],
- [1, 1, 1, 1, 4, 3],
- [1, 1, 1, 3, 4, 1],
- [1, 3, 1, 1, 4, 1],
- [1, 1, 4, 1, 1, 3],
- [1, 1, 4, 3, 1, 1],
- [4, 1, 1, 1, 1, 3],
- [4, 1, 1, 3, 1, 1],
- [1, 1, 3, 1, 4, 1],
- [1, 1, 4, 1, 3, 1],
- [3, 1, 1, 1, 4, 1],
- [4, 1, 1, 1, 3, 1],
- [2, 1, 1, 4, 1, 2],
- [2, 1, 1, 2, 1, 4],
- [2, 1, 1, 2, 3, 2],
- [2, 3, 3, 1, 1, 1, 2]
- ]},
- SINGLE_CODE_ERROR: {value: 1},
- AVG_CODE_ERROR: {value: 0.5},
- FORMAT: {value: "code_128", writeable: false}
+ that.setInputStream = function(config) {
+ _config = config;
+ video.src = (typeof config.src !== 'undefined') ? config.src : '';
};
-
- Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
- Code128Reader.prototype.constructor = Code128Reader;
-
- Code128Reader.prototype._decodeCode = function(start) {
- var counter = [0, 0, 0, 0, 0, 0],
- i,
- self = this,
- offset = start,
- isWhite = !self._row[offset],
- counterPos = 0,
- bestMatch = {
- error : Number.MAX_VALUE,
- code : -1,
- start : start,
- end : start
- },
- code,
- error,
- normalized;
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- if (counterPos === counter.length - 1) {
- normalized = self._normalize(counter);
- if (normalized) {
- for (code = 0; code < self.CODE_PATTERN.length; code++) {
- error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
- if (error < bestMatch.error) {
- bestMatch.code = code;
- bestMatch.error = error;
- }
- }
- bestMatch.end = i;
- return bestMatch;
- }
- } else {
- counterPos++;
- }
- counter[counterPos] = 1;
- isWhite = !isWhite;
+ that.ended = function() {
+ return video.ended;
+ };
+
+ that.getConfig = function() {
+ return _config;
+ };
+
+ that.setAttribute = function(name, value) {
+ video.setAttribute(name, value);
+ };
+
+ that.pause = function() {
+ video.pause();
+ };
+
+ that.play = function() {
+ video.play();
+ };
+
+ that.setCurrentTime = function(time) {
+ if (_config.type !== "LiveStream")
+ video.currentTime = time;
+ };
+
+ that.addEventListener = function(event, f, bool) {
+ if (_eventNames.indexOf(event) !== -1) {
+ if (!_eventHandlers[event]) {
+ _eventHandlers[event] = [];
}
+ _eventHandlers[event].push(f);
+ } else {
+ video.addEventListener(event, f, bool);
}
- return null;
};
- Code128Reader.prototype._findStart = function() {
- var counter = [0, 0, 0, 0, 0, 0],
- i,
- self = this,
- offset = self._nextSet(self._row),
- isWhite = false,
- counterPos = 0,
- bestMatch = {
- error : Number.MAX_VALUE,
- code : -1,
- start : 0,
- end : 0
- },
- code,
- error,
- j,
- sum,
- normalized;
-
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- if (counterPos === counter.length - 1) {
- sum = 0;
- for ( j = 0; j < counter.length; j++) {
- sum += counter[j];
- }
- normalized = self._normalize(counter);
- if (normalized) {
- for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) {
- error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
- if (error < bestMatch.error) {
- bestMatch.code = code;
- bestMatch.error = error;
- }
- }
- if (bestMatch.error < self.AVG_CODE_ERROR) {
- bestMatch.start = i - sum;
- bestMatch.end = i;
- return bestMatch;
- }
- }
-
- for ( j = 0; j < 4; j++) {
- counter[j] = counter[j + 2];
- }
- counter[4] = 0;
- counter[5] = 0;
- counterPos--;
- } else {
- counterPos++;
- }
- counter[counterPos] = 1;
- isWhite = !isWhite;
+ that.clearEventHandlers = function() {
+ _eventNames.forEach(function(eventName) {
+ var handlers = _eventHandlers[eventName];
+ if (handlers && handlers.length > 0) {
+ handlers.forEach(function(handler) {
+ video.removeEventListener(eventName, handler);
+ });
}
- }
- return null;
+ });
};
- Code128Reader.prototype._decode = function() {
- var self = this,
- startInfo = self._findStart(),
- code = null,
- done = false,
- result = [],
- multiplier = 0,
- checksum = 0,
- codeset,
- rawResult = [],
- decodedCodes = [],
- shiftNext = false,
- unshift,
- lastCharacterWasPrintable;
+ that.trigger = function(eventName, args) {
+ var j,
+ handlers = _eventHandlers[eventName];
- if (startInfo === null) {
- return null;
- }
- code = {
- code : startInfo.code,
- start : startInfo.start,
- end : startInfo.end
- };
- decodedCodes.push(code);
- checksum = code.code;
- switch(code.code) {
- case self.START_CODE_A:
- codeset = self.CODE_A;
- break;
- case self.START_CODE_B:
- codeset = self.CODE_B;
- break;
- case self.START_CODE_C:
- codeset = self.CODE_C;
- break;
- default:
- return null;
+ if (eventName === 'canrecord') {
+ initSize();
}
-
- while (!done) {
- unshift = shiftNext;
- shiftNext = false;
- code = self._decodeCode(code.end);
- if (code !== null) {
- if (code.code !== self.STOP_CODE) {
- rawResult.push(code.code);
- multiplier++;
- checksum += multiplier * code.code;
- }
- decodedCodes.push(code);
-
- switch(codeset) {
- case self.CODE_A:
- if (code.code < 64) {
- result.push(String.fromCharCode(32 + code.code));
- } else if (code.code < 96) {
- result.push(String.fromCharCode(code.code - 64));
- } else {
- switch (code.code) {
- case self.CODE_SHIFT:
- shiftNext = true;
- codeset = self.CODE_B;
- break;
- case self.CODE_B:
- codeset = self.CODE_B;
- break;
- case self.CODE_C:
- codeset = self.CODE_C;
- break;
- case self.STOP_CODE:
- done = true;
- break;
- }
- }
- break;
- case self.CODE_B:
- if (code.code < 96) {
- result.push(String.fromCharCode(32 + code.code));
- } else {
- if (code.code != self.STOP_CODE) {
- lastCharacterWasPrintable = false;
- }
- switch (code.code) {
- case self.CODE_SHIFT:
- shiftNext = true;
- codeset = self.CODE_A;
- break;
- case self.CODE_A:
- codeset = self.CODE_A;
- break;
- case self.CODE_C:
- codeset = self.CODE_C;
- break;
- case self.STOP_CODE:
- done = true;
- break;
- }
- }
- break;
- case self.CODE_C:
- if (code.code < 100) {
- result.push(code.code < 10 ? "0" + code.code : code.code);
- }
- switch (code.code) {
- case self.CODE_A:
- codeset = self.CODE_A;
- break;
- case self.CODE_B:
- codeset = self.CODE_B;
- break;
- case self.STOP_CODE:
- done = true;
- break;
- }
- break;
- }
- } else {
- done = true;
- }
- if (unshift) {
- codeset = codeset == self.CODE_A ? self.CODE_B : self.CODE_A;
+ if (handlers && handlers.length > 0) {
+ for ( j = 0; j < handlers.length; j++) {
+ handlers[j].apply(that, args);
}
}
+ };
- if (code === null) {
- return null;
- }
+ that.setTopRight = function(topRight) {
+ _topRight.x = topRight.x;
+ _topRight.y = topRight.y;
+ };
- // find end bar
- code.end = self._nextUnset(self._row, code.end);
- if(!self._verifyTrailingWhitespace(code)){
- return null;
- }
+ that.getTopRight = function() {
+ return _topRight;
+ };
- // checksum
- // Does not work correctly yet!!! startcode - endcode?
- checksum -= multiplier * rawResult[rawResult.length - 1];
- if (checksum % 103 != rawResult[rawResult.length - 1]) {
- return null;
- }
+ that.setCanvasSize = function(size) {
+ _canvasSize.x = size.x;
+ _canvasSize.y = size.y;
+ };
- if (!result.length) {
- return null;
- }
+ that.getCanvasSize = function() {
+ return _canvasSize;
+ };
- // remove last code from result (checksum)
- result.splice(result.length - 1, 1);
+ that.getFrame = function() {
+ return video;
+ };
+ return that;
+ };
+ InputStream.createLiveStream = function(video) {
+ video.setAttribute("autoplay", true);
+ var that = InputStream.createVideoStream(video);
- return {
- code : result.join(""),
- start : startInfo.start,
- end : code.end,
- codeset : codeset,
- startInfo : startInfo,
- decodedCodes : decodedCodes,
- endInfo : code
- };
+ that.ended = function() {
+ return false;
};
+ return that;
+ };
- BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
- var self = this,
- trailingWhitespaceEnd;
+ InputStream.createImageStream = function() {
+ var that = {};
+ var _config = null;
- trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
- if (trailingWhitespaceEnd < self._row.length) {
- if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
- return endInfo;
- }
- }
- return null;
- };
-
- return (Code128Reader);
- }
-);
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+ var width = 0,
+ height = 0,
+ frameIdx = 0,
+ paused = true,
+ loaded = false,
+ imgArray = null,
+ size = 0,
+ offset = 1,
+ baseUrl = null,
+ ended = false,
+ calculatedWidth,
+ calculatedHeight,
+ _eventNames = ['canrecord', 'ended'],
+ _eventHandlers = {},
+ _topRight = {x: 0, y: 0},
+ _canvasSize = {x: 0, y: 0};
-define(
- 'ean_reader',[
- "./barcode_reader"
- ],
- function(BarcodeReader) {
- "use strict";
-
- function EANReader(opts) {
- BarcodeReader.call(this, opts);
+ function loadImages() {
+ loaded = false;
+ ImageLoader.load(baseUrl, function(imgs) {
+ imgArray = imgs;
+ width = imgs[0].width;
+ height = imgs[0].height;
+ calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
+ calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
+ _canvasSize.x = calculatedWidth;
+ _canvasSize.y = calculatedHeight;
+ loaded = true;
+ frameIdx = 0;
+ setTimeout(function() {
+ publishEvent("canrecord", []);
+ }, 0);
+ }, offset, size, _config.sequence);
}
-
- var properties = {
- CODE_L_START : {value: 0},
- MODULO : {value: 7},
- CODE_G_START : {value: 10},
- START_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
- STOP_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
- MIDDLE_PATTERN : {value: [1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7]},
- CODE_PATTERN : {value: [
- [3, 2, 1, 1],
- [2, 2, 2, 1],
- [2, 1, 2, 2],
- [1, 4, 1, 1],
- [1, 1, 3, 2],
- [1, 2, 3, 1],
- [1, 1, 1, 4],
- [1, 3, 1, 2],
- [1, 2, 1, 3],
- [3, 1, 1, 2],
- [1, 1, 2, 3],
- [1, 2, 2, 2],
- [2, 2, 1, 2],
- [1, 1, 4, 1],
- [2, 3, 1, 1],
- [1, 3, 2, 1],
- [4, 1, 1, 1],
- [2, 1, 3, 1],
- [3, 1, 2, 1],
- [2, 1, 1, 3]
- ]},
- CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
- SINGLE_CODE_ERROR: {value: 0.67},
- AVG_CODE_ERROR: {value: 0.27},
- FORMAT: {value: "ean_13", writeable: false}
- };
-
- EANReader.prototype = Object.create(BarcodeReader.prototype, properties);
- EANReader.prototype.constructor = EANReader;
-
- EANReader.prototype._decodeCode = function(start, coderange) {
- var counter = [0, 0, 0, 0],
- i,
- self = this,
- offset = start,
- isWhite = !self._row[offset],
- counterPos = 0,
- bestMatch = {
- error : Number.MAX_VALUE,
- code : -1,
- start : start,
- end : start
- },
- code,
- error,
- normalized;
-
- if (!coderange) {
- coderange = self.CODE_PATTERN.length;
- }
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- if (counterPos === counter.length - 1) {
- normalized = self._normalize(counter);
- if (normalized) {
- for (code = 0; code < coderange; code++) {
- error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
- if (error < bestMatch.error) {
- bestMatch.code = code;
- bestMatch.error = error;
- }
- }
- bestMatch.end = i;
- if (bestMatch.error > self.AVG_CODE_ERROR) {
- return null;
- }
- return bestMatch;
- }
- } else {
- counterPos++;
- }
- counter[counterPos] = 1;
- isWhite = !isWhite;
+ function publishEvent(eventName, args) {
+ var j,
+ handlers = _eventHandlers[eventName];
+
+ if (handlers && handlers.length > 0) {
+ for ( j = 0; j < handlers.length; j++) {
+ handlers[j].apply(that, args);
}
}
- return null;
- };
-
- EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder, epsilon) {
- var counter = [],
- self = this,
- i,
- counterPos = 0,
- bestMatch = {
- error : Number.MAX_VALUE,
- code : -1,
- start : 0,
- end : 0
- },
- error,
- j,
- sum,
- normalized;
+ }
- if (!offset) {
- offset = self._nextSet(self._row);
- }
- if (isWhite === undefined) {
- isWhite = false;
- }
+ that.trigger = publishEvent;
- if (tryHarder === undefined) {
- tryHarder = true;
- }
+ that.getWidth = function() {
+ return calculatedWidth;
+ };
- if ( epsilon === undefined) {
- epsilon = self.AVG_CODE_ERROR;
- }
+ that.getHeight = function() {
+ return calculatedHeight;
+ };
- for ( i = 0; i < pattern.length; i++) {
- counter[i] = 0;
- }
+ that.setWidth = function(width) {
+ calculatedWidth = width;
+ };
- for ( i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- counter[counterPos]++;
- } else {
- if (counterPos === counter.length - 1) {
- sum = 0;
- for ( j = 0; j < counter.length; j++) {
- sum += counter[j];
- }
- normalized = self._normalize(counter);
- if (normalized) {
- error = self._matchPattern(normalized, pattern);
+ that.setHeight = function(height) {
+ calculatedHeight = height;
+ };
- if (error < epsilon) {
- bestMatch.error = error;
- bestMatch.start = i - sum;
- bestMatch.end = i;
- return bestMatch;
- }
- }
- if (tryHarder) {
- for ( j = 0; j < counter.length - 2; j++) {
- counter[j] = counter[j + 2];
- }
- counter[counter.length - 2] = 0;
- counter[counter.length - 1] = 0;
- counterPos--;
- } else {
- return null;
- }
- } else {
- counterPos++;
- }
- counter[counterPos] = 1;
- isWhite = !isWhite;
- }
- }
- return null;
+ that.getRealWidth = function() {
+ return width;
};
- EANReader.prototype._findStart = function() {
- var self = this,
- leadingWhitespaceStart,
- offset = self._nextSet(self._row),
- startInfo;
+ that.getRealHeight = function() {
+ return height;
+ };
- while(!startInfo) {
- startInfo = self._findPattern(self.START_PATTERN, offset);
- if (!startInfo) {
- return null;
- }
- leadingWhitespaceStart = startInfo.start - (startInfo.end - startInfo.start);
- if (leadingWhitespaceStart >= 0) {
- if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
- return startInfo;
- }
- }
- offset = startInfo.end;
- startInfo = null;
+ that.setInputStream = function(stream) {
+ _config = stream;
+ if (stream.sequence === false) {
+ baseUrl = stream.src;
+ size = 1;
+ } else {
+ baseUrl = stream.src;
+ size = stream.length;
}
+ loadImages();
};
- EANReader.prototype._verifyTrailingWhitespace = function(endInfo) {
- var self = this,
- trailingWhitespaceEnd;
-
- trailingWhitespaceEnd = endInfo.end + (endInfo.end - endInfo.start);
- if (trailingWhitespaceEnd < self._row.length) {
- if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
- return endInfo;
- }
- }
- return null;
+ that.ended = function() {
+ return ended;
};
- EANReader.prototype._findEnd = function(offset, isWhite) {
- var self = this,
- endInfo = self._findPattern(self.STOP_PATTERN, offset, isWhite, false);
+ that.setAttribute = function() {};
- return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
+ that.getConfig = function() {
+ return _config;
};
- EANReader.prototype._calculateFirstDigit = function(codeFrequency) {
- var i,
- self = this;
+ that.pause = function() {
+ paused = true;
+ };
- for ( i = 0; i < self.CODE_FREQUENCY.length; i++) {
- if (codeFrequency === self.CODE_FREQUENCY[i]) {
- return i;
- }
- }
- return null;
+ that.play = function() {
+ paused = false;
};
- EANReader.prototype._decodePayload = function(code, result, decodedCodes) {
- var i,
- self = this,
- codeFrequency = 0x0,
- firstDigit;
-
- for ( i = 0; i < 6; i++) {
- code = self._decodeCode(code.end);
- if (!code) {
- return null;
- }
- if (code.code >= self.CODE_G_START) {
- code.code = code.code - self.CODE_G_START;
- codeFrequency |= 1 << (5 - i);
- } else {
- codeFrequency |= 0 << (5 - i);
- }
- result.push(code.code);
- decodedCodes.push(code);
- }
-
- firstDigit = self._calculateFirstDigit(codeFrequency);
- if (firstDigit === null) {
- return null;
- }
- result.unshift(firstDigit);
-
- code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
- if (code === null) {
- return null;
- }
- decodedCodes.push(code);
+ that.setCurrentTime = function(time) {
+ frameIdx = time;
+ };
- for ( i = 0; i < 6; i++) {
- code = self._decodeCode(code.end, self.CODE_G_START);
- if (!code) {
- return null;
+ that.addEventListener = function(event, f) {
+ if (_eventNames.indexOf(event) !== -1) {
+ if (!_eventHandlers[event]) {
+ _eventHandlers[event] = [];
}
- decodedCodes.push(code);
- result.push(code.code);
+ _eventHandlers[event].push(f);
}
-
- return code;
};
- EANReader.prototype._decode = function() {
- var startInfo,
- self = this,
- code,
- result = [],
- decodedCodes = [];
-
- startInfo = self._findStart();
- if (!startInfo) {
- return null;
- }
- code = {
- code : startInfo.code,
- start : startInfo.start,
- end : startInfo.end
- };
- decodedCodes.push(code);
- code = self._decodePayload(code, result, decodedCodes);
- if (!code) {
- return null;
- }
- code = self._findEnd(code.end, false);
- if (!code){
- return null;
- }
-
- decodedCodes.push(code);
-
- // Checksum
- if (!self._checksum(result)) {
- return null;
- }
-
- return {
- code : result.join(""),
- start : startInfo.start,
- end : code.end,
- codeset : "",
- startInfo : startInfo,
- decodedCodes : decodedCodes
- };
+ that.setTopRight = function(topRight) {
+ _topRight.x = topRight.x;
+ _topRight.y = topRight.y;
};
- EANReader.prototype._checksum = function(result) {
- var sum = 0, i;
+ that.getTopRight = function() {
+ return _topRight;
+ };
- for ( i = result.length - 2; i >= 0; i -= 2) {
- sum += result[i];
- }
- sum *= 3;
- for ( i = result.length - 1; i >= 0; i -= 2) {
- sum += result[i];
- }
- return sum % 10 === 0;
+ that.setCanvasSize = function(size) {
+ _canvasSize.x = size.x;
+ _canvasSize.y = size.y;
};
-
- return (EANReader);
- }
-);
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-define('image_loader',[],function() {
- "use strict";
+ that.getCanvasSize = function() {
+ return _canvasSize;
+ };
- var ImageLoader = {};
- ImageLoader.load = function(directory, callback, offset, size, sequence) {
- var htmlImagesSrcArray = new Array(size),
- htmlImagesArray = new Array(htmlImagesSrcArray.length),
- i,
- img,
- num;
+ that.getFrame = function() {
+ var frame;
- if (sequence === false) {
- htmlImagesSrcArray[0] = directory;
- } else {
- for ( i = 0; i < htmlImagesSrcArray.length; i++) {
- num = (offset + i);
- htmlImagesSrcArray[i] = directory + "image-" + ("00" + num).slice(-3) + ".jpg";
+ if (!loaded){
+ return null;
}
- }
- htmlImagesArray.notLoaded = [];
- htmlImagesArray.addImage = function(img) {
- htmlImagesArray.notLoaded.push(img);
- };
- htmlImagesArray.loaded = function(loadedImg) {
- var notloadedImgs = htmlImagesArray.notLoaded;
- for (var x = 0; x < notloadedImgs.length; x++) {
- if (notloadedImgs[x] == loadedImg) {
- notloadedImgs.splice(x, 1);
- for (var y = 0; y < htmlImagesSrcArray.length; y++) {
- var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/"));
- if (loadedImg.src.lastIndexOf(imgName) != -1) {
- htmlImagesArray[y] = loadedImg;
- break;
- }
- }
- break;
+ if (!paused) {
+ frame = imgArray[frameIdx];
+ if (frameIdx < (size - 1)) {
+ frameIdx++;
+ } else {
+ setTimeout(function() {
+ ended = true;
+ publishEvent("ended", []);
+ }, 0);
}
}
- if (notloadedImgs.length === 0) {
- console.log("Images loaded");
- callback.apply(null, [htmlImagesArray]);
- }
+ return frame;
};
-
- for ( i = 0; i < htmlImagesSrcArray.length; i++) {
- img = new Image();
- htmlImagesArray.addImage(img);
- addOnloadHandler(img, htmlImagesArray);
- img.src = htmlImagesSrcArray[i];
- }
+
+ return that;
};
-
- function addOnloadHandler(img, htmlImagesArray) {
- img.onload = function() {
- htmlImagesArray.loaded(this);
- };
- }
- return (ImageLoader);
+ return (InputStream);
});
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-
-define('input_stream',["image_loader"], function(ImageLoader) {
- "use strict";
-
- var InputStream = {};
- InputStream.createVideoStream = function(video) {
- var that = {},
- _config = null,
- _eventNames = ['canrecord', 'ended'],
- _eventHandlers = {},
- _calculatedWidth,
- _calculatedHeight,
- _topRight = {x: 0, y: 0},
- _canvasSize = {x: 0, y: 0};
-
- function initSize() {
- var width = video.videoWidth,
- height = video.videoHeight;
+/*
+ * typedefs.js
+ * Normalizes browser-specific prefixes
+ */
- _calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
- _calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
+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);
+ };
+ })();
- _canvasSize.x = _calculatedWidth;
- _canvasSize.y = _calculatedHeight;
- }
+ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
+ window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
+}
- that.getRealWidth = function() {
- return video.videoWidth;
- };
+define("typedefs", (function (global) {
+ return function () {
+ var ret, fn;
+ return ret || global.typedefs;
+ };
+}(this)));
- that.getRealHeight = function() {
- return video.videoHeight;
- };
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- that.getWidth = function() {
- return _calculatedWidth;
- };
+define('subImage',["typedefs"], function() {
+ "use strict";
- that.getHeight = function() {
- return _calculatedHeight;
- };
-
- that.setWidth = function(width) {
- _calculatedWidth = width;
- };
-
- that.setHeight = function(height) {
- _calculatedHeight = height;
- };
+ /**
+ * Construct representing a part of another {ImageWrapper}. Shares data
+ * between the parent and the child.
+ * @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
+ * @param size {ImageRef} The size of the resulting image
+ * @param I {ImageWrapper} The {ImageWrapper} to share from
+ * @returns {SubImage} A shared part of the original image
+ */
+ function SubImage(from, size, I) {
+ if (!I) {
+ I = {
+ data : null,
+ size : size
+ };
+ }
+ this.data = I.data;
+ this.originalSize = I.size;
+ this.I = I;
- that.setInputStream = function(config) {
- _config = config;
- video.src = (typeof config.src !== 'undefined') ? config.src : '';
- };
+ this.from = from;
+ this.size = size;
+ }
- that.ended = function() {
- return video.ended;
- };
+ /**
+ * Displays the {SubImage} in a given canvas
+ * @param canvas {Canvas} The canvas element to write to
+ * @param scale {Number} Scale which is applied to each pixel-value
+ */
+ SubImage.prototype.show = function(canvas, scale) {
+ var ctx,
+ frame,
+ data,
+ current,
+ y,
+ x,
+ pixel;
+
+ if (!scale) {
+ scale = 1.0;
+ }
+ ctx = canvas.getContext('2d');
+ canvas.width = this.size.x;
+ canvas.height = this.size.y;
+ frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ data = frame.data;
+ current = 0;
+ for (y = 0; y < this.size.y; y++) {
+ for (x = 0; x < this.size.x; x++) {
+ pixel = y * this.size.x + x;
+ current = this.get(x, y) * scale;
+ data[pixel * 4 + 0] = current;
+ data[pixel * 4 + 1] = current;
+ data[pixel * 4 + 2] = current;
+ data[pixel * 4 + 3] = 255;
+ }
+ }
+ frame.data = data;
+ ctx.putImageData(frame, 0, 0);
+ };
- that.getConfig = function() {
- return _config;
- };
+ /**
+ * Retrieves a given pixel position from the {SubImage}
+ * @param x {Number} The x-position
+ * @param y {Number} The y-position
+ * @returns {Number} The grayscale value at the pixel-position
+ */
+ SubImage.prototype.get = function(x, y) {
+ return this.data[(this.from.y + y) * this.originalSize.x + this.from.x + x];
+ };
- that.setAttribute = function(name, value) {
- video.setAttribute(name, value);
- };
+ /**
+ * Updates the underlying data from a given {ImageWrapper}
+ * @param image {ImageWrapper} The updated image
+ */
+ SubImage.prototype.updateData = function(image) {
+ this.originalSize = image.size;
+ this.data = image.data;
+ };
- that.pause = function() {
- video.pause();
- };
+ /**
+ * Updates the position of the shared area
+ * @param from {x,y} The new location
+ * @returns {SubImage} returns {this} for possible chaining
+ */
+ SubImage.prototype.updateFrom = function(from) {
+ this.from = from;
+ return this;
+ };
+
+ return (SubImage);
+});
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define, vec2 */
- that.play = function() {
- video.play();
- };
+define('cluster',[],function() {
+ "use strict";
+
+ /**
+ * Creates a cluster for grouping similar orientations of datapoints
+ */
+ var Cluster = {
+ create : function(point, threshold) {
+ var points = [], center = {
+ rad : 0,
+ vec : vec2.create([0, 0])
+ }, pointMap = {};
- that.setCurrentTime = function(time) {
- if (_config.type !== "LiveStream")
- video.currentTime = time;
- };
+ function init() {
+ add(point);
+ updateCenter();
+ }
- that.addEventListener = function(event, f, bool) {
- if (_eventNames.indexOf(event) !== -1) {
- if (!_eventHandlers[event]) {
- _eventHandlers[event] = [];
- }
- _eventHandlers[event].push(f);
- } else {
- video.addEventListener(event, f, bool);
+ function add(point) {
+ pointMap[point.id] = point;
+ points.push(point);
}
- };
- that.clearEventHandlers = function() {
- _eventNames.forEach(function(eventName) {
- var handlers = _eventHandlers[eventName];
- if (handlers && handlers.length > 0) {
- handlers.forEach(function(handler) {
- video.removeEventListener(eventName, handler);
- });
+ function updateCenter() {
+ var i, sum = 0;
+ for ( i = 0; i < points.length; i++) {
+ sum += points[i].rad;
}
- });
- };
+ center.rad = sum / points.length;
+ center.vec = vec2.create([Math.cos(center.rad), Math.sin(center.rad)]);
+ }
- that.trigger = function(eventName, args) {
- var j,
- handlers = _eventHandlers[eventName];
+ init();
- if (eventName === 'canrecord') {
- initSize();
- }
- if (handlers && handlers.length > 0) {
- for ( j = 0; j < handlers.length; j++) {
- handlers[j].apply(that, args);
+ return {
+ add : function(point) {
+ if (!pointMap[point.id]) {
+ add(point);
+ updateCenter();
+ }
+ },
+ fits : function(point) {
+ // check cosine similarity to center-angle
+ var similarity = Math.abs(vec2.dot(point.point.vec, center.vec));
+ if (similarity > threshold) {
+ return true;
+ }
+ return false;
+ },
+ getPoints : function() {
+ return points;
+ },
+ getCenter : function() {
+ return center;
}
- }
- };
-
- that.setTopRight = function(topRight) {
- _topRight.x = topRight.x;
- _topRight.y = topRight.y;
- };
+ };
+ },
+ createPoint : function(point, id, property) {
+ return {
+ rad : point[property],
+ point : point,
+ id : id
+ };
+ }
+ };
- that.getTopRight = function() {
- return _topRight;
- };
+ return (Cluster);
+});
- that.setCanvasSize = function(size) {
- _canvasSize.x = size.x;
- _canvasSize.y = size.y;
- };
+/*
+ * glMatrix.js - High performance matrix and vector operations for WebGL
+ * version 0.9.6
+ */
+
+/*
+ * Copyright (c) 2011 Brandon Jones
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not
+ * be misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ */
- that.getCanvasSize = function() {
- return _canvasSize;
- };
- that.getFrame = function() {
- return video;
- };
- return that;
- };
-
- InputStream.createLiveStream = function(video) {
- video.setAttribute("autoplay", true);
- var that = InputStream.createVideoStream(video);
-
- that.ended = function() {
- return false;
- };
-
- return that;
- };
-
- InputStream.createImageStream = function() {
- var that = {};
- var _config = null;
-
- var width = 0,
- height = 0,
- frameIdx = 0,
- paused = true,
- loaded = false,
- imgArray = null,
- size = 0,
- offset = 1,
- baseUrl = null,
- ended = false,
- calculatedWidth,
- calculatedHeight,
- _eventNames = ['canrecord', 'ended'],
- _eventHandlers = {},
- _topRight = {x: 0, y: 0},
- _canvasSize = {x: 0, y: 0};
-
- function loadImages() {
- loaded = false;
- ImageLoader.load(baseUrl, function(imgs) {
- imgArray = imgs;
- width = imgs[0].width;
- height = imgs[0].height;
- calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
- calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
- _canvasSize.x = calculatedWidth;
- _canvasSize.y = calculatedHeight;
- loaded = true;
- frameIdx = 0;
- setTimeout(function() {
- publishEvent("canrecord", []);
- }, 0);
- }, offset, size, _config.sequence);
- }
-
- function publishEvent(eventName, args) {
- var j,
- handlers = _eventHandlers[eventName];
-
- if (handlers && handlers.length > 0) {
- for ( j = 0; j < handlers.length; j++) {
- handlers[j].apply(that, args);
- }
- }
- }
-
-
- that.trigger = publishEvent;
-
- that.getWidth = function() {
- return calculatedWidth;
- };
-
- that.getHeight = function() {
- return calculatedHeight;
- };
-
- that.setWidth = function(width) {
- calculatedWidth = width;
- };
-
- that.setHeight = function(height) {
- calculatedHeight = height;
- };
-
- that.getRealWidth = function() {
- return width;
- };
-
- that.getRealHeight = function() {
- return height;
- };
-
- that.setInputStream = function(stream) {
- _config = stream;
- if (stream.sequence === false) {
- baseUrl = stream.src;
- size = 1;
- } else {
- baseUrl = stream.src;
- size = stream.length;
- }
- loadImages();
- };
-
- that.ended = function() {
- return ended;
- };
-
- that.setAttribute = function() {};
-
- that.getConfig = function() {
- return _config;
- };
-
- that.pause = function() {
- paused = true;
- };
-
- that.play = function() {
- paused = false;
- };
-
- that.setCurrentTime = function(time) {
- frameIdx = time;
- };
-
- that.addEventListener = function(event, f) {
- if (_eventNames.indexOf(event) !== -1) {
- if (!_eventHandlers[event]) {
- _eventHandlers[event] = [];
- }
- _eventHandlers[event].push(f);
- }
- };
-
- that.setTopRight = function(topRight) {
- _topRight.x = topRight.x;
- _topRight.y = topRight.y;
- };
-
- that.getTopRight = function() {
- return _topRight;
- };
+/*
+ * vec3 - 3 Dimensional Vector
+ */
+var vec3 = {};
- that.setCanvasSize = function(size) {
- _canvasSize.x = size.x;
- _canvasSize.y = size.y;
- };
+/*
+ * vec3.create
+ * Creates a new instance of a vec3 using the default array type
+ * Any javascript array containing at least 3 numeric elements can serve as a vec3
+ *
+ * Params:
+ * vec - Optional, vec3 containing values to initialize with
+ *
+ * Returns:
+ * New vec3
+ */
+vec3.create = function(vec) {
+ var dest;
+ if(vec) {
+ dest = new glMatrixArrayType(3);
+ dest[0] = vec[0];
+ dest[1] = vec[1];
+ dest[2] = vec[2];
+ } else {
+ if(glMatrixArrayType === Array)
+ dest = new glMatrixArrayType([0,0,0]);
+ else
+ dest = new glMatrixArrayType(3);
+ }
+
+ return dest;
+};
- that.getCanvasSize = function() {
- return _canvasSize;
- };
+/*
+ * vec3.set
+ * Copies the values of one vec3 to another
+ *
+ * Params:
+ * vec - vec3 containing values to copy
+ * dest - vec3 receiving copied values
+ *
+ * Returns:
+ * dest
+ */
+vec3.set = function(vec, dest) {
+ dest[0] = vec[0];
+ dest[1] = vec[1];
+ dest[2] = vec[2];
+
+ return dest;
+};
- that.getFrame = function() {
- var frame;
-
- if (!loaded){
- return null;
- }
- if (!paused) {
- frame = imgArray[frameIdx];
- if (frameIdx < (size - 1)) {
- frameIdx++;
- } else {
- setTimeout(function() {
- ended = true;
- publishEvent("ended", []);
- }, 0);
- }
- }
- return frame;
- };
+/*
+ * vec3.add
+ * Performs a vector addition
+ *
+ * Params:
+ * vec - vec3, first operand
+ * vec2 - vec3, second operand
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.add = function(vec, vec2, dest) {
+ if(!dest || vec == dest) {
+ vec[0] += vec2[0];
+ vec[1] += vec2[1];
+ vec[2] += vec2[2];
+ return vec;
+ }
+
+ dest[0] = vec[0] + vec2[0];
+ dest[1] = vec[1] + vec2[1];
+ dest[2] = vec[2] + vec2[2];
+ return dest;
+};
- return that;
- };
+/*
+ * vec3.subtract
+ * Performs a vector subtraction
+ *
+ * Params:
+ * vec - vec3, first operand
+ * vec2 - vec3, second operand
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.subtract = function(vec, vec2, dest) {
+ if(!dest || vec == dest) {
+ vec[0] -= vec2[0];
+ vec[1] -= vec2[1];
+ vec[2] -= vec2[2];
+ return vec;
+ }
+
+ dest[0] = vec[0] - vec2[0];
+ dest[1] = vec[1] - vec2[1];
+ dest[2] = vec[2] - vec2[2];
+ return dest;
+};
- return (InputStream);
-});
+/*
+ * vec3.negate
+ * Negates the components of a vec3
+ *
+ * Params:
+ * vec - vec3 to negate
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.negate = function(vec, dest) {
+ if(!dest) { dest = vec; }
+
+ dest[0] = -vec[0];
+ dest[1] = -vec[1];
+ dest[2] = -vec[2];
+ return dest;
+};
/*
- * typedefs.js
- * Normalizes browser-specific prefixes
+ * vec3.scale
+ * Multiplies the components of a vec3 by a scalar value
+ *
+ * Params:
+ * vec - vec3 to scale
+ * val - Numeric value to scale by
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
*/
+vec3.scale = function(vec, val, dest) {
+ if(!dest || vec == dest) {
+ vec[0] *= val;
+ vec[1] *= val;
+ vec[2] *= val;
+ return vec;
+ }
+
+ dest[0] = vec[0]*val;
+ dest[1] = vec[1]*val;
+ dest[2] = vec[2]*val;
+ return dest;
+};
-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);
- };
- })();
-
- navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
- window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
-}
-
-define("typedefs", (function (global) {
- return function () {
- var ret, fn;
- return ret || global.typedefs;
- };
-}(this)));
+/*
+ * vec3.normalize
+ * Generates a unit vector of the same direction as the provided vec3
+ * If vector length is 0, returns [0, 0, 0]
+ *
+ * Params:
+ * vec - vec3 to normalize
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.normalize = function(vec, dest) {
+ if(!dest) { dest = vec; }
+
+ var x = vec[0], y = vec[1], z = vec[2];
+ var len = Math.sqrt(x*x + y*y + z*z);
+
+ if (!len) {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ return dest;
+ } else if (len == 1) {
+ dest[0] = x;
+ dest[1] = y;
+ dest[2] = z;
+ return dest;
+ }
+
+ len = 1 / len;
+ dest[0] = x*len;
+ dest[1] = y*len;
+ dest[2] = z*len;
+ return dest;
+};
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+/*
+ * vec3.cross
+ * Generates the cross product of two vec3s
+ *
+ * Params:
+ * vec - vec3, first operand
+ * vec2 - vec3, second operand
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.cross = function(vec, vec2, dest){
+ if(!dest) { dest = vec; }
+
+ var x = vec[0], y = vec[1], z = vec[2];
+ var x2 = vec2[0], y2 = vec2[1], z2 = vec2[2];
+
+ dest[0] = y*z2 - z*y2;
+ dest[1] = z*x2 - x*z2;
+ dest[2] = x*y2 - y*x2;
+ return dest;
+};
-define('subImage',["typedefs"], function() {
- "use strict";
+/*
+ * vec3.length
+ * Caclulates the length of a vec3
+ *
+ * Params:
+ * vec - vec3 to calculate length of
+ *
+ * Returns:
+ * Length of vec
+ */
+vec3.length = function(vec){
+ var x = vec[0], y = vec[1], z = vec[2];
+ return Math.sqrt(x*x + y*y + z*z);
+};
- /**
- * Construct representing a part of another {ImageWrapper}. Shares data
- * between the parent and the child.
- * @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
- * @param size {ImageRef} The size of the resulting image
- * @param I {ImageWrapper} The {ImageWrapper} to share from
- * @returns {SubImage} A shared part of the original image
- */
- function SubImage(from, size, I) {
- if (!I) {
- I = {
- data : null,
- size : size
- };
- }
- this.data = I.data;
- this.originalSize = I.size;
- this.I = I;
+/*
+ * vec3.dot
+ * Caclulates the dot product of two vec3s
+ *
+ * Params:
+ * vec - vec3, first operand
+ * vec2 - vec3, second operand
+ *
+ * Returns:
+ * Dot product of vec and vec2
+ */
+vec3.dot = function(vec, vec2){
+ return vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2];
+};
- this.from = from;
- this.size = size;
- }
+/*
+ * vec3.direction
+ * Generates a unit vector pointing from one vector to another
+ *
+ * Params:
+ * vec - origin vec3
+ * vec2 - vec3 to point to
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.direction = function(vec, vec2, dest) {
+ if(!dest) { dest = vec; }
+
+ var x = vec[0] - vec2[0];
+ var y = vec[1] - vec2[1];
+ var z = vec[2] - vec2[2];
+
+ var len = Math.sqrt(x*x + y*y + z*z);
+ if (!len) {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ return dest;
+ }
+
+ len = 1 / len;
+ dest[0] = x * len;
+ dest[1] = y * len;
+ dest[2] = z * len;
+ return dest;
+};
- /**
- * Displays the {SubImage} in a given canvas
- * @param canvas {Canvas} The canvas element to write to
- * @param scale {Number} Scale which is applied to each pixel-value
- */
- SubImage.prototype.show = function(canvas, scale) {
- var ctx,
- frame,
- data,
- current,
- y,
- x,
- pixel;
-
- if (!scale) {
- scale = 1.0;
- }
- ctx = canvas.getContext('2d');
- canvas.width = this.size.x;
- canvas.height = this.size.y;
- frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
- data = frame.data;
- current = 0;
- for (y = 0; y < this.size.y; y++) {
- for (x = 0; x < this.size.x; x++) {
- pixel = y * this.size.x + x;
- current = this.get(x, y) * scale;
- data[pixel * 4 + 0] = current;
- data[pixel * 4 + 1] = current;
- data[pixel * 4 + 2] = current;
- data[pixel * 4 + 3] = 255;
- }
- }
- frame.data = data;
- ctx.putImageData(frame, 0, 0);
- };
+/*
+ * vec3.lerp
+ * Performs a linear interpolation between two vec3
+ *
+ * Params:
+ * vec - vec3, first vector
+ * vec2 - vec3, second vector
+ * lerp - interpolation amount between the two inputs
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ *
+ * Returns:
+ * dest if specified, vec otherwise
+ */
+vec3.lerp = function(vec, vec2, lerp, dest){
+ if(!dest) { dest = vec; }
+
+ dest[0] = vec[0] + lerp * (vec2[0] - vec[0]);
+ dest[1] = vec[1] + lerp * (vec2[1] - vec[1]);
+ dest[2] = vec[2] + lerp * (vec2[2] - vec[2]);
+
+ return dest;
+};
- /**
- * Retrieves a given pixel position from the {SubImage}
- * @param x {Number} The x-position
- * @param y {Number} The y-position
- * @returns {Number} The grayscale value at the pixel-position
- */
- SubImage.prototype.get = function(x, y) {
- return this.data[(this.from.y + y) * this.originalSize.x + this.from.x + x];
- };
+/*
+ * vec3.str
+ * Returns a string representation of a vector
+ *
+ * Params:
+ * vec - vec3 to represent as a string
+ *
+ * Returns:
+ * string representation of vec
+ */
+vec3.str = function(vec) {
+ return '[' + vec[0] + ', ' + vec[1] + ', ' + vec[2] + ']';
+};
- /**
- * Updates the underlying data from a given {ImageWrapper}
- * @param image {ImageWrapper} The updated image
- */
- SubImage.prototype.updateData = function(image) {
- this.originalSize = image.size;
- this.data = image.data;
- };
+/*
+ * mat3 - 3x3 Matrix
+ */
+var mat3 = {};
- /**
- * Updates the position of the shared area
- * @param from {x,y} The new location
- * @returns {SubImage} returns {this} for possible chaining
- */
- SubImage.prototype.updateFrom = function(from) {
- this.from = from;
- return this;
- };
-
- return (SubImage);
-});
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define, vec2 */
+/*
+ * mat3.create
+ * Creates a new instance of a mat3 using the default array type
+ * Any javascript array containing at least 9 numeric elements can serve as a mat3
+ *
+ * Params:
+ * mat - Optional, mat3 containing values to initialize with
+ *
+ * Returns:
+ * New mat3
+ */
+mat3.create = function(mat) {
+ var dest;
+
+ if(mat) {
+ dest = new glMatrixArrayType(9);
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+ dest[8] = mat[8];
+ } else {
+ if(glMatrixArrayType === Array)
+ dest = new glMatrixArrayType([0,0,0,0,0,0,0,0,0]);
+ else
+ dest = new glMatrixArrayType(9);
+ }
+
+ return dest;
+};
-define('cluster',[],function() {
- "use strict";
-
- /**
- * Creates a cluster for grouping similar orientations of datapoints
- */
- var Cluster = {
- create : function(point, threshold) {
- var points = [], center = {
- rad : 0,
- vec : vec2.create([0, 0])
- }, pointMap = {};
+/*
+ * mat3.set
+ * Copies the values of one mat3 to another
+ *
+ * Params:
+ * mat - mat3 containing values to copy
+ * dest - mat3 receiving copied values
+ *
+ * Returns:
+ * dest
+ */
+mat3.set = function(mat, dest) {
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+ dest[8] = mat[8];
+ return dest;
+};
- function init() {
- add(point);
- updateCenter();
- }
+/*
+ * mat3.identity
+ * Sets a mat3 to an identity matrix
+ *
+ * Params:
+ * dest - mat3 to set
+ *
+ * Returns:
+ * dest
+ */
+mat3.identity = function(dest) {
+ dest[0] = 1;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ dest[4] = 1;
+ dest[5] = 0;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 1;
+ return dest;
+};
- function add(point) {
- pointMap[point.id] = point;
- points.push(point);
- }
+/*
+ * mat4.transpose
+ * Transposes a mat3 (flips the values over the diagonal)
+ *
+ * Params:
+ * mat - mat3 to transpose
+ * dest - Optional, mat3 receiving transposed values. If not specified result is written to mat
+ *
+ * Returns:
+ * dest is specified, mat otherwise
+ */
+mat3.transpose = function(mat, dest) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if(!dest || mat == dest) {
+ var a01 = mat[1], a02 = mat[2];
+ var a12 = mat[5];
+
+ mat[1] = mat[3];
+ mat[2] = mat[6];
+ mat[3] = a01;
+ mat[5] = mat[7];
+ mat[6] = a02;
+ mat[7] = a12;
+ return mat;
+ }
+
+ dest[0] = mat[0];
+ dest[1] = mat[3];
+ dest[2] = mat[6];
+ dest[3] = mat[1];
+ dest[4] = mat[4];
+ dest[5] = mat[7];
+ dest[6] = mat[2];
+ dest[7] = mat[5];
+ dest[8] = mat[8];
+ return dest;
+};
- function updateCenter() {
- var i, sum = 0;
- for ( i = 0; i < points.length; i++) {
- sum += points[i].rad;
- }
- center.rad = sum / points.length;
- center.vec = vec2.create([Math.cos(center.rad), Math.sin(center.rad)]);
- }
+/*
+ * mat3.toMat4
+ * Copies the elements of a mat3 into the upper 3x3 elements of a mat4
+ *
+ * Params:
+ * mat - mat3 containing values to copy
+ * dest - Optional, mat4 receiving copied values
+ *
+ * Returns:
+ * dest if specified, a new mat4 otherwise
+ */
+mat3.toMat4 = function(mat, dest) {
+ if(!dest) { dest = mat4.create(); }
+
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = 0;
- init();
+ dest[4] = mat[3];
+ dest[5] = mat[4];
+ dest[6] = mat[5];
+ dest[7] = 0;
- return {
- add : function(point) {
- if (!pointMap[point.id]) {
- add(point);
- updateCenter();
- }
- },
- fits : function(point) {
- // check cosine similarity to center-angle
- var similarity = Math.abs(vec2.dot(point.point.vec, center.vec));
- if (similarity > threshold) {
- return true;
- }
- return false;
- },
- getPoints : function() {
- return points;
- },
- getCenter : function() {
- return center;
- }
- };
- },
- createPoint : function(point, id, property) {
- return {
- rad : point[property],
- point : point,
- id : id
- };
- }
- };
+ dest[8] = mat[6];
+ dest[9] = mat[7];
+ dest[10] = mat[8];
+ dest[11] = 0;
- return (Cluster);
-});
+ dest[12] = 0;
+ dest[13] = 0;
+ dest[14] = 0;
+ dest[15] = 1;
+
+ return dest;
+};
-/*
- * glMatrix.js - High performance matrix and vector operations for WebGL
- * version 0.9.6
- */
-
/*
- * Copyright (c) 2011 Brandon Jones
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
+ * mat3.str
+ * Returns a string representation of a mat3
*
- * 2. Altered source versions must be plainly marked as such, and must not
- * be misrepresented as being the original software.
+ * Params:
+ * mat - mat3 to represent as a string
*
- * 3. This notice may not be removed or altered from any source
- * distribution.
+ * Returns:
+ * string representation of mat
*/
-
-
+mat3.str = function(mat) {
+ return '[' + mat[0] + ', ' + mat[1] + ', ' + mat[2] +
+ ', ' + mat[3] + ', '+ mat[4] + ', ' + mat[5] +
+ ', ' + mat[6] + ', ' + mat[7] + ', '+ mat[8] + ']';
+};
/*
- * vec3 - 3 Dimensional Vector
+ * mat4 - 4x4 Matrix
*/
-var vec3 = {};
+var mat4 = {};
/*
- * vec3.create
- * Creates a new instance of a vec3 using the default array type
- * Any javascript array containing at least 3 numeric elements can serve as a vec3
+ * mat4.create
+ * Creates a new instance of a mat4 using the default array type
+ * Any javascript array containing at least 16 numeric elements can serve as a mat4
*
* Params:
- * vec - Optional, vec3 containing values to initialize with
+ * mat - Optional, mat4 containing values to initialize with
*
* Returns:
- * New vec3
+ * New mat4
*/
-vec3.create = function(vec) {
+mat4.create = function(mat) {
var dest;
- if(vec) {
- dest = new glMatrixArrayType(3);
- dest[0] = vec[0];
- dest[1] = vec[1];
- dest[2] = vec[2];
+
+ if(mat) {
+ dest = new glMatrixArrayType(16);
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+ dest[8] = mat[8];
+ dest[9] = mat[9];
+ dest[10] = mat[10];
+ dest[11] = mat[11];
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
} else {
if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0]);
+ dest = new glMatrixArrayType([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
else
- dest = new glMatrixArrayType(3);
+ dest = new glMatrixArrayType(16);
+ }
+
+ return dest;
+};
+
+/*
+ * mat4.set
+ * Copies the values of one mat4 to another
+ *
+ * Params:
+ * mat - mat4 containing values to copy
+ * dest - mat4 receiving copied values
+ *
+ * Returns:
+ * dest
+ */
+mat4.set = function(mat, dest) {
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+ dest[8] = mat[8];
+ dest[9] = mat[9];
+ dest[10] = mat[10];
+ dest[11] = mat[11];
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
+ return dest;
+};
+
+/*
+ * mat4.identity
+ * Sets a mat4 to an identity matrix
+ *
+ * Params:
+ * dest - mat4 to set
+ *
+ * Returns:
+ * dest
+ */
+mat4.identity = function(dest) {
+ dest[0] = 1;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ dest[4] = 0;
+ dest[5] = 1;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 0;
+ dest[9] = 0;
+ dest[10] = 1;
+ dest[11] = 0;
+ dest[12] = 0;
+ dest[13] = 0;
+ dest[14] = 0;
+ dest[15] = 1;
+ return dest;
+};
+
+/*
+ * mat4.transpose
+ * Transposes a mat4 (flips the values over the diagonal)
+ *
+ * Params:
+ * mat - mat4 to transpose
+ * dest - Optional, mat4 receiving transposed values. If not specified result is written to mat
+ *
+ * Returns:
+ * dest is specified, mat otherwise
+ */
+mat4.transpose = function(mat, dest) {
+ // If we are transposing ourselves we can skip a few steps but have to cache some values
+ if(!dest || mat == dest) {
+ var a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a12 = mat[6], a13 = mat[7];
+ var a23 = mat[11];
+
+ mat[1] = mat[4];
+ mat[2] = mat[8];
+ mat[3] = mat[12];
+ mat[4] = a01;
+ mat[6] = mat[9];
+ mat[7] = mat[13];
+ mat[8] = a02;
+ mat[9] = a12;
+ mat[11] = mat[14];
+ mat[12] = a03;
+ mat[13] = a13;
+ mat[14] = a23;
+ return mat;
}
+ dest[0] = mat[0];
+ dest[1] = mat[4];
+ dest[2] = mat[8];
+ dest[3] = mat[12];
+ dest[4] = mat[1];
+ dest[5] = mat[5];
+ dest[6] = mat[9];
+ dest[7] = mat[13];
+ dest[8] = mat[2];
+ dest[9] = mat[6];
+ dest[10] = mat[10];
+ dest[11] = mat[14];
+ dest[12] = mat[3];
+ dest[13] = mat[7];
+ dest[14] = mat[11];
+ dest[15] = mat[15];
+ return dest;
+};
+
+/*
+ * mat4.determinant
+ * Calculates the determinant of a mat4
+ *
+ * Params:
+ * mat - mat4 to calculate determinant of
+ *
+ * Returns:
+ * determinant of mat
+ */
+mat4.determinant = function(mat) {
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+ var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
+
+ return a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 +
+ a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 +
+ a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 +
+ a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 +
+ a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 +
+ a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33;
+};
+
+/*
+ * mat4.inverse
+ * Calculates the inverse matrix of a mat4
+ *
+ * Params:
+ * mat - mat4 to calculate inverse of
+ * dest - Optional, mat4 receiving inverse matrix. If not specified result is written to mat
+ *
+ * Returns:
+ * dest is specified, mat otherwise
+ */
+mat4.inverse = function(mat, dest) {
+ if(!dest) { dest = mat; }
+
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+ var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
+
+ var b00 = a00*a11 - a01*a10;
+ var b01 = a00*a12 - a02*a10;
+ var b02 = a00*a13 - a03*a10;
+ var b03 = a01*a12 - a02*a11;
+ var b04 = a01*a13 - a03*a11;
+ var b05 = a02*a13 - a03*a12;
+ var b06 = a20*a31 - a21*a30;
+ var b07 = a20*a32 - a22*a30;
+ var b08 = a20*a33 - a23*a30;
+ var b09 = a21*a32 - a22*a31;
+ var b10 = a21*a33 - a23*a31;
+ var b11 = a22*a33 - a23*a32;
+
+ // Calculate the determinant (inlined to avoid double-caching)
+ var invDet = 1/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06);
+
+ dest[0] = (a11*b11 - a12*b10 + a13*b09)*invDet;
+ dest[1] = (-a01*b11 + a02*b10 - a03*b09)*invDet;
+ dest[2] = (a31*b05 - a32*b04 + a33*b03)*invDet;
+ dest[3] = (-a21*b05 + a22*b04 - a23*b03)*invDet;
+ dest[4] = (-a10*b11 + a12*b08 - a13*b07)*invDet;
+ dest[5] = (a00*b11 - a02*b08 + a03*b07)*invDet;
+ dest[6] = (-a30*b05 + a32*b02 - a33*b01)*invDet;
+ dest[7] = (a20*b05 - a22*b02 + a23*b01)*invDet;
+ dest[8] = (a10*b10 - a11*b08 + a13*b06)*invDet;
+ dest[9] = (-a00*b10 + a01*b08 - a03*b06)*invDet;
+ dest[10] = (a30*b04 - a31*b02 + a33*b00)*invDet;
+ dest[11] = (-a20*b04 + a21*b02 - a23*b00)*invDet;
+ dest[12] = (-a10*b09 + a11*b07 - a12*b06)*invDet;
+ dest[13] = (a00*b09 - a01*b07 + a02*b06)*invDet;
+ dest[14] = (-a30*b03 + a31*b01 - a32*b00)*invDet;
+ dest[15] = (a20*b03 - a21*b01 + a22*b00)*invDet;
+
return dest;
};
/*
- * vec3.set
- * Copies the values of one vec3 to another
+ * mat4.toRotationMat
+ * Copies the upper 3x3 elements of a mat4 into another mat4
*
* Params:
- * vec - vec3 containing values to copy
- * dest - vec3 receiving copied values
+ * mat - mat4 containing values to copy
+ * dest - Optional, mat4 receiving copied values
*
* Returns:
- * dest
+ * dest is specified, a new mat4 otherwise
*/
-vec3.set = function(vec, dest) {
- dest[0] = vec[0];
- dest[1] = vec[1];
- dest[2] = vec[2];
+mat4.toRotationMat = function(mat, dest) {
+ if(!dest) { dest = mat4.create(); }
+
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+ dest[8] = mat[8];
+ dest[9] = mat[9];
+ dest[10] = mat[10];
+ dest[11] = mat[11];
+ dest[12] = 0;
+ dest[13] = 0;
+ dest[14] = 0;
+ dest[15] = 1;
return dest;
};
/*
- * vec3.add
- * Performs a vector addition
+ * mat4.toMat3
+ * Copies the upper 3x3 elements of a mat4 into a mat3
*
* Params:
- * vec - vec3, first operand
- * vec2 - vec3, second operand
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 containing values to copy
+ * dest - Optional, mat3 receiving copied values
*
* Returns:
- * dest if specified, vec otherwise
+ * dest is specified, a new mat3 otherwise
*/
-vec3.add = function(vec, vec2, dest) {
- if(!dest || vec == dest) {
- vec[0] += vec2[0];
- vec[1] += vec2[1];
- vec[2] += vec2[2];
- return vec;
- }
+mat4.toMat3 = function(mat, dest) {
+ if(!dest) { dest = mat3.create(); }
+
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[4];
+ dest[4] = mat[5];
+ dest[5] = mat[6];
+ dest[6] = mat[8];
+ dest[7] = mat[9];
+ dest[8] = mat[10];
- dest[0] = vec[0] + vec2[0];
- dest[1] = vec[1] + vec2[1];
- dest[2] = vec[2] + vec2[2];
return dest;
};
/*
- * vec3.subtract
- * Performs a vector subtraction
+ * mat4.toInverseMat3
+ * Calculates the inverse of the upper 3x3 elements of a mat4 and copies the result into a mat3
+ * The resulting matrix is useful for calculating transformed normals
*
* Params:
- * vec - vec3, first operand
- * vec2 - vec3, second operand
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 containing values to invert and copy
+ * dest - Optional, mat3 receiving values
*
* Returns:
- * dest if specified, vec otherwise
+ * dest is specified, a new mat3 otherwise
*/
-vec3.subtract = function(vec, vec2, dest) {
- if(!dest || vec == dest) {
- vec[0] -= vec2[0];
- vec[1] -= vec2[1];
- vec[2] -= vec2[2];
- return vec;
- }
+mat4.toInverseMat3 = function(mat, dest) {
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10];
+
+ var b01 = a22*a11-a12*a21;
+ var b11 = -a22*a10+a12*a20;
+ var b21 = a21*a10-a11*a20;
+
+ var d = a00*b01 + a01*b11 + a02*b21;
+ if (!d) { return null; }
+ var id = 1/d;
+
+ if(!dest) { dest = mat3.create(); }
+
+ dest[0] = b01*id;
+ dest[1] = (-a22*a01 + a02*a21)*id;
+ dest[2] = (a12*a01 - a02*a11)*id;
+ dest[3] = b11*id;
+ dest[4] = (a22*a00 - a02*a20)*id;
+ dest[5] = (-a12*a00 + a02*a10)*id;
+ dest[6] = b21*id;
+ dest[7] = (-a21*a00 + a01*a20)*id;
+ dest[8] = (a11*a00 - a01*a10)*id;
- dest[0] = vec[0] - vec2[0];
- dest[1] = vec[1] - vec2[1];
- dest[2] = vec[2] - vec2[2];
return dest;
};
/*
- * vec3.negate
- * Negates the components of a vec3
+ * mat4.multiply
+ * Performs a matrix multiplication
*
* Params:
- * vec - vec3 to negate
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4, first operand
+ * mat2 - mat4, second operand
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, mat otherwise
*/
-vec3.negate = function(vec, dest) {
- if(!dest) { dest = vec; }
+mat4.multiply = function(mat, mat2, dest) {
+ if(!dest) { dest = mat; }
+
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+ var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
+
+ var b00 = mat2[0], b01 = mat2[1], b02 = mat2[2], b03 = mat2[3];
+ var b10 = mat2[4], b11 = mat2[5], b12 = mat2[6], b13 = mat2[7];
+ var b20 = mat2[8], b21 = mat2[9], b22 = mat2[10], b23 = mat2[11];
+ var b30 = mat2[12], b31 = mat2[13], b32 = mat2[14], b33 = mat2[15];
+
+ dest[0] = b00*a00 + b01*a10 + b02*a20 + b03*a30;
+ dest[1] = b00*a01 + b01*a11 + b02*a21 + b03*a31;
+ dest[2] = b00*a02 + b01*a12 + b02*a22 + b03*a32;
+ dest[3] = b00*a03 + b01*a13 + b02*a23 + b03*a33;
+ dest[4] = b10*a00 + b11*a10 + b12*a20 + b13*a30;
+ dest[5] = b10*a01 + b11*a11 + b12*a21 + b13*a31;
+ dest[6] = b10*a02 + b11*a12 + b12*a22 + b13*a32;
+ dest[7] = b10*a03 + b11*a13 + b12*a23 + b13*a33;
+ dest[8] = b20*a00 + b21*a10 + b22*a20 + b23*a30;
+ dest[9] = b20*a01 + b21*a11 + b22*a21 + b23*a31;
+ dest[10] = b20*a02 + b21*a12 + b22*a22 + b23*a32;
+ dest[11] = b20*a03 + b21*a13 + b22*a23 + b23*a33;
+ dest[12] = b30*a00 + b31*a10 + b32*a20 + b33*a30;
+ dest[13] = b30*a01 + b31*a11 + b32*a21 + b33*a31;
+ dest[14] = b30*a02 + b31*a12 + b32*a22 + b33*a32;
+ dest[15] = b30*a03 + b31*a13 + b32*a23 + b33*a33;
- dest[0] = -vec[0];
- dest[1] = -vec[1];
- dest[2] = -vec[2];
return dest;
};
/*
- * vec3.scale
- * Multiplies the components of a vec3 by a scalar value
+ * mat4.multiplyVec3
+ * Transforms a vec3 with the given matrix
+ * 4th vector component is implicitly '1'
*
* Params:
- * vec - vec3 to scale
- * val - Numeric value to scale by
+ * mat - mat4 to transform the vector with
+ * vec - vec3 to transform
* dest - Optional, vec3 receiving operation result. If not specified result is written to vec
*
* Returns:
* dest if specified, vec otherwise
*/
-vec3.scale = function(vec, val, dest) {
- if(!dest || vec == dest) {
- vec[0] *= val;
- vec[1] *= val;
- vec[2] *= val;
- return vec;
- }
+mat4.multiplyVec3 = function(mat, vec, dest) {
+ if(!dest) { dest = vec; }
+
+ var x = vec[0], y = vec[1], z = vec[2];
+
+ dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12];
+ dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13];
+ dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14];
- dest[0] = vec[0]*val;
- dest[1] = vec[1]*val;
- dest[2] = vec[2]*val;
return dest;
};
/*
- * vec3.normalize
- * Generates a unit vector of the same direction as the provided vec3
- * If vector length is 0, returns [0, 0, 0]
+ * mat4.multiplyVec4
+ * Transforms a vec4 with the given matrix
*
* Params:
- * vec - vec3 to normalize
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 to transform the vector with
+ * vec - vec4 to transform
+ * dest - Optional, vec4 receiving operation result. If not specified result is written to vec
*
* Returns:
* dest if specified, vec otherwise
*/
-vec3.normalize = function(vec, dest) {
+mat4.multiplyVec4 = function(mat, vec, dest) {
if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1], z = vec[2];
- var len = Math.sqrt(x*x + y*y + z*z);
+ var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
- if (!len) {
- dest[0] = 0;
- dest[1] = 0;
- dest[2] = 0;
- return dest;
- } else if (len == 1) {
- dest[0] = x;
- dest[1] = y;
- dest[2] = z;
- return dest;
- }
+ dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12]*w;
+ dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13]*w;
+ dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14]*w;
+ dest[3] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15]*w;
- len = 1 / len;
- dest[0] = x*len;
- dest[1] = y*len;
- dest[2] = z*len;
return dest;
};
/*
- * vec3.cross
- * Generates the cross product of two vec3s
+ * mat4.translate
+ * Translates a matrix by the given vector
*
* Params:
- * vec - vec3, first operand
- * vec2 - vec3, second operand
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 to translate
+ * vec - vec3 specifying the translation
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, mat otherwise
*/
-vec3.cross = function(vec, vec2, dest){
- if(!dest) { dest = vec; }
-
+mat4.translate = function(mat, vec, dest) {
var x = vec[0], y = vec[1], z = vec[2];
- var x2 = vec2[0], y2 = vec2[1], z2 = vec2[2];
- dest[0] = y*z2 - z*y2;
- dest[1] = z*x2 - x*z2;
- dest[2] = x*y2 - y*x2;
+ if(!dest || mat == dest) {
+ mat[12] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12];
+ mat[13] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13];
+ mat[14] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14];
+ mat[15] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15];
+ return mat;
+ }
+
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+
+ dest[0] = a00;
+ dest[1] = a01;
+ dest[2] = a02;
+ dest[3] = a03;
+ dest[4] = a10;
+ dest[5] = a11;
+ dest[6] = a12;
+ dest[7] = a13;
+ dest[8] = a20;
+ dest[9] = a21;
+ dest[10] = a22;
+ dest[11] = a23;
+
+ dest[12] = a00*x + a10*y + a20*z + mat[12];
+ dest[13] = a01*x + a11*y + a21*z + mat[13];
+ dest[14] = a02*x + a12*y + a22*z + mat[14];
+ dest[15] = a03*x + a13*y + a23*z + mat[15];
return dest;
};
/*
- * vec3.length
- * Caclulates the length of a vec3
+ * mat4.scale
+ * Scales a matrix by the given vector
*
* Params:
- * vec - vec3 to calculate length of
+ * mat - mat4 to scale
+ * vec - vec3 specifying the scale for each axis
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * Length of vec
+ * dest if specified, mat otherwise
*/
-vec3.length = function(vec){
+mat4.scale = function(mat, vec, dest) {
var x = vec[0], y = vec[1], z = vec[2];
- return Math.sqrt(x*x + y*y + z*z);
+
+ if(!dest || mat == dest) {
+ mat[0] *= x;
+ mat[1] *= x;
+ mat[2] *= x;
+ mat[3] *= x;
+ mat[4] *= y;
+ mat[5] *= y;
+ mat[6] *= y;
+ mat[7] *= y;
+ mat[8] *= z;
+ mat[9] *= z;
+ mat[10] *= z;
+ mat[11] *= z;
+ return mat;
+ }
+
+ dest[0] = mat[0]*x;
+ dest[1] = mat[1]*x;
+ dest[2] = mat[2]*x;
+ dest[3] = mat[3]*x;
+ dest[4] = mat[4]*y;
+ dest[5] = mat[5]*y;
+ dest[6] = mat[6]*y;
+ dest[7] = mat[7]*y;
+ dest[8] = mat[8]*z;
+ dest[9] = mat[9]*z;
+ dest[10] = mat[10]*z;
+ dest[11] = mat[11]*z;
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
+ return dest;
};
/*
- * vec3.dot
- * Caclulates the dot product of two vec3s
+ * mat4.rotate
+ * Rotates a matrix by the given angle around the specified axis
+ * If rotating around a primary axis (X,Y,Z) one of the specialized rotation functions should be used instead for performance
*
* Params:
- * vec - vec3, first operand
- * vec2 - vec3, second operand
+ * mat - mat4 to rotate
+ * angle - angle (in radians) to rotate
+ * axis - vec3 representing the axis to rotate around
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * Dot product of vec and vec2
+ * dest if specified, mat otherwise
*/
-vec3.dot = function(vec, vec2){
- return vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2];
+mat4.rotate = function(mat, angle, axis, dest) {
+ var x = axis[0], y = axis[1], z = axis[2];
+ var len = Math.sqrt(x*x + y*y + z*z);
+ if (!len) { return null; }
+ if (len != 1) {
+ len = 1 / len;
+ x *= len;
+ y *= len;
+ z *= len;
+ }
+
+ var s = Math.sin(angle);
+ var c = Math.cos(angle);
+ var t = 1-c;
+
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+
+ // Construct the elements of the rotation matrix
+ var b00 = x*x*t + c, b01 = y*x*t + z*s, b02 = z*x*t - y*s;
+ var b10 = x*y*t - z*s, b11 = y*y*t + c, b12 = z*y*t + x*s;
+ var b20 = x*z*t + y*s, b21 = y*z*t - x*s, b22 = z*z*t + c;
+
+ if(!dest) {
+ dest = mat;
+ } else if(mat != dest) { // If the source and destination differ, copy the unchanged last row
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
+ }
+
+ // Perform rotation-specific matrix multiplication
+ dest[0] = a00*b00 + a10*b01 + a20*b02;
+ dest[1] = a01*b00 + a11*b01 + a21*b02;
+ dest[2] = a02*b00 + a12*b01 + a22*b02;
+ dest[3] = a03*b00 + a13*b01 + a23*b02;
+
+ dest[4] = a00*b10 + a10*b11 + a20*b12;
+ dest[5] = a01*b10 + a11*b11 + a21*b12;
+ dest[6] = a02*b10 + a12*b11 + a22*b12;
+ dest[7] = a03*b10 + a13*b11 + a23*b12;
+
+ dest[8] = a00*b20 + a10*b21 + a20*b22;
+ dest[9] = a01*b20 + a11*b21 + a21*b22;
+ dest[10] = a02*b20 + a12*b21 + a22*b22;
+ dest[11] = a03*b20 + a13*b21 + a23*b22;
+ return dest;
};
/*
- * vec3.direction
- * Generates a unit vector pointing from one vector to another
+ * mat4.rotateX
+ * Rotates a matrix by the given angle around the X axis
*
* Params:
- * vec - origin vec3
- * vec2 - vec3 to point to
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 to rotate
+ * angle - angle (in radians) to rotate
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, mat otherwise
*/
-vec3.direction = function(vec, vec2, dest) {
- if(!dest) { dest = vec; }
-
- var x = vec[0] - vec2[0];
- var y = vec[1] - vec2[1];
- var z = vec[2] - vec2[2];
+mat4.rotateX = function(mat, angle, dest) {
+ var s = Math.sin(angle);
+ var c = Math.cos(angle);
- var len = Math.sqrt(x*x + y*y + z*z);
- if (!len) {
- dest[0] = 0;
- dest[1] = 0;
- dest[2] = 0;
- return dest;
+ // Cache the matrix values (makes for huge speed increases!)
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+
+ if(!dest) {
+ dest = mat;
+ } else if(mat != dest) { // If the source and destination differ, copy the unchanged rows
+ dest[0] = mat[0];
+ dest[1] = mat[1];
+ dest[2] = mat[2];
+ dest[3] = mat[3];
+
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
}
- len = 1 / len;
- dest[0] = x * len;
- dest[1] = y * len;
- dest[2] = z * len;
- return dest;
+ // Perform axis-specific matrix multiplication
+ dest[4] = a10*c + a20*s;
+ dest[5] = a11*c + a21*s;
+ dest[6] = a12*c + a22*s;
+ dest[7] = a13*c + a23*s;
+
+ dest[8] = a10*-s + a20*c;
+ dest[9] = a11*-s + a21*c;
+ dest[10] = a12*-s + a22*c;
+ dest[11] = a13*-s + a23*c;
+ return dest;
};
/*
- * vec3.lerp
- * Performs a linear interpolation between two vec3
+ * mat4.rotateY
+ * Rotates a matrix by the given angle around the Y axis
*
* Params:
- * vec - vec3, first vector
- * vec2 - vec3, second vector
- * lerp - interpolation amount between the two inputs
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * mat - mat4 to rotate
+ * angle - angle (in radians) to rotate
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, mat otherwise
*/
-vec3.lerp = function(vec, vec2, lerp, dest){
- if(!dest) { dest = vec; }
-
- dest[0] = vec[0] + lerp * (vec2[0] - vec[0]);
- dest[1] = vec[1] + lerp * (vec2[1] - vec[1]);
- dest[2] = vec[2] + lerp * (vec2[2] - vec[2]);
-
- return dest;
+mat4.rotateY = function(mat, angle, dest) {
+ var s = Math.sin(angle);
+ var c = Math.cos(angle);
+
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+
+ if(!dest) {
+ dest = mat;
+ } else if(mat != dest) { // If the source and destination differ, copy the unchanged rows
+ dest[4] = mat[4];
+ dest[5] = mat[5];
+ dest[6] = mat[6];
+ dest[7] = mat[7];
+
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
+ }
+
+ // Perform axis-specific matrix multiplication
+ dest[0] = a00*c + a20*-s;
+ dest[1] = a01*c + a21*-s;
+ dest[2] = a02*c + a22*-s;
+ dest[3] = a03*c + a23*-s;
+
+ dest[8] = a00*s + a20*c;
+ dest[9] = a01*s + a21*c;
+ dest[10] = a02*s + a22*c;
+ dest[11] = a03*s + a23*c;
+ return dest;
};
/*
- * vec3.str
- * Returns a string representation of a vector
+ * mat4.rotateZ
+ * Rotates a matrix by the given angle around the Z axis
*
* Params:
- * vec - vec3 to represent as a string
+ * mat - mat4 to rotate
+ * angle - angle (in radians) to rotate
+ * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
*
* Returns:
- * string representation of vec
+ * dest if specified, mat otherwise
*/
-vec3.str = function(vec) {
- return '[' + vec[0] + ', ' + vec[1] + ', ' + vec[2] + ']';
+mat4.rotateZ = function(mat, angle, dest) {
+ var s = Math.sin(angle);
+ var c = Math.cos(angle);
+
+ // Cache the matrix values (makes for huge speed increases!)
+ var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
+ var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
+
+ if(!dest) {
+ dest = mat;
+ } else if(mat != dest) { // If the source and destination differ, copy the unchanged last row
+ dest[8] = mat[8];
+ dest[9] = mat[9];
+ dest[10] = mat[10];
+ dest[11] = mat[11];
+
+ dest[12] = mat[12];
+ dest[13] = mat[13];
+ dest[14] = mat[14];
+ dest[15] = mat[15];
+ }
+
+ // Perform axis-specific matrix multiplication
+ dest[0] = a00*c + a10*s;
+ dest[1] = a01*c + a11*s;
+ dest[2] = a02*c + a12*s;
+ dest[3] = a03*c + a13*s;
+
+ dest[4] = a00*-s + a10*c;
+ dest[5] = a01*-s + a11*c;
+ dest[6] = a02*-s + a12*c;
+ dest[7] = a03*-s + a13*c;
+
+ return dest;
};
/*
- * mat3 - 3x3 Matrix
- */
-var mat3 = {};
-
-/*
- * mat3.create
- * Creates a new instance of a mat3 using the default array type
- * Any javascript array containing at least 9 numeric elements can serve as a mat3
+ * mat4.frustum
+ * Generates a frustum matrix with the given bounds
*
* Params:
- * mat - Optional, mat3 containing values to initialize with
+ * left, right - scalar, left and right bounds of the frustum
+ * bottom, top - scalar, bottom and top bounds of the frustum
+ * near, far - scalar, near and far bounds of the frustum
+ * dest - Optional, mat4 frustum matrix will be written into
*
* Returns:
- * New mat3
+ * dest if specified, a new mat4 otherwise
*/
-mat3.create = function(mat) {
- var dest;
-
- if(mat) {
- dest = new glMatrixArrayType(9);
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
- dest[8] = mat[8];
- } else {
- if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0,0,0,0,0,0,0]);
- else
- dest = new glMatrixArrayType(9);
- }
-
+mat4.frustum = function(left, right, bottom, top, near, far, dest) {
+ if(!dest) { dest = mat4.create(); }
+ var rl = (right - left);
+ var tb = (top - bottom);
+ var fn = (far - near);
+ dest[0] = (near*2) / rl;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ dest[4] = 0;
+ dest[5] = (near*2) / tb;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = (right + left) / rl;
+ dest[9] = (top + bottom) / tb;
+ dest[10] = -(far + near) / fn;
+ dest[11] = -1;
+ dest[12] = 0;
+ dest[13] = 0;
+ dest[14] = -(far*near*2) / fn;
+ dest[15] = 0;
return dest;
};
/*
- * mat3.set
- * Copies the values of one mat3 to another
+ * mat4.perspective
+ * Generates a perspective projection matrix with the given bounds
*
* Params:
- * mat - mat3 containing values to copy
- * dest - mat3 receiving copied values
+ * fovy - scalar, vertical field of view
+ * aspect - scalar, aspect ratio. typically viewport width/height
+ * near, far - scalar, near and far bounds of the frustum
+ * dest - Optional, mat4 frustum matrix will be written into
*
* Returns:
- * dest
+ * dest if specified, a new mat4 otherwise
*/
-mat3.set = function(mat, dest) {
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
- dest[8] = mat[8];
- return dest;
+mat4.perspective = function(fovy, aspect, near, far, dest) {
+ var top = near*Math.tan(fovy*Math.PI / 360.0);
+ var right = top*aspect;
+ return mat4.frustum(-right, right, -top, top, near, far, dest);
};
/*
- * mat3.identity
- * Sets a mat3 to an identity matrix
+ * mat4.ortho
+ * Generates a orthogonal projection matrix with the given bounds
*
* Params:
- * dest - mat3 to set
+ * left, right - scalar, left and right bounds of the frustum
+ * bottom, top - scalar, bottom and top bounds of the frustum
+ * near, far - scalar, near and far bounds of the frustum
+ * dest - Optional, mat4 frustum matrix will be written into
*
* Returns:
- * dest
+ * dest if specified, a new mat4 otherwise
*/
-mat3.identity = function(dest) {
- dest[0] = 1;
+mat4.ortho = function(left, right, bottom, top, near, far, dest) {
+ if(!dest) { dest = mat4.create(); }
+ var rl = (right - left);
+ var tb = (top - bottom);
+ var fn = (far - near);
+ dest[0] = 2 / rl;
dest[1] = 0;
dest[2] = 0;
dest[3] = 0;
- dest[4] = 1;
- dest[5] = 0;
+ dest[4] = 0;
+ dest[5] = 2 / tb;
dest[6] = 0;
dest[7] = 0;
- dest[8] = 1;
+ dest[8] = 0;
+ dest[9] = 0;
+ dest[10] = -2 / fn;
+ dest[11] = 0;
+ dest[12] = -(left + right) / rl;
+ dest[13] = -(top + bottom) / tb;
+ dest[14] = -(far + near) / fn;
+ dest[15] = 1;
return dest;
};
/*
- * mat4.transpose
- * Transposes a mat3 (flips the values over the diagonal)
+ * mat4.ortho
+ * Generates a look-at matrix with the given eye position, focal point, and up axis
*
* Params:
- * mat - mat3 to transpose
- * dest - Optional, mat3 receiving transposed values. If not specified result is written to mat
+ * eye - vec3, position of the viewer
+ * center - vec3, point the viewer is looking at
+ * up - vec3 pointing "up"
+ * dest - Optional, mat4 frustum matrix will be written into
*
* Returns:
- * dest is specified, mat otherwise
+ * dest if specified, a new mat4 otherwise
*/
-mat3.transpose = function(mat, dest) {
- // If we are transposing ourselves we can skip a few steps but have to cache some values
- if(!dest || mat == dest) {
- var a01 = mat[1], a02 = mat[2];
- var a12 = mat[5];
-
- mat[1] = mat[3];
- mat[2] = mat[6];
- mat[3] = a01;
- mat[5] = mat[7];
- mat[6] = a02;
- mat[7] = a12;
- return mat;
+mat4.lookAt = function(eye, center, up, dest) {
+ if(!dest) { dest = mat4.create(); }
+
+ var eyex = eye[0],
+ eyey = eye[1],
+ eyez = eye[2],
+ upx = up[0],
+ upy = up[1],
+ upz = up[2],
+ centerx = center[0],
+ centery = center[1],
+ centerz = center[2];
+
+ if (eyex == centerx && eyey == centery && eyez == centerz) {
+ return mat4.identity(dest);
+ }
+
+ var z0,z1,z2,x0,x1,x2,y0,y1,y2,len;
+
+ //vec3.direction(eye, center, z);
+ z0 = eyex - center[0];
+ z1 = eyey - center[1];
+ z2 = eyez - center[2];
+
+ // normalize (no check needed for 0 because of early return)
+ len = 1/Math.sqrt(z0*z0 + z1*z1 + z2*z2);
+ z0 *= len;
+ z1 *= len;
+ z2 *= len;
+
+ //vec3.normalize(vec3.cross(up, z, x));
+ x0 = upy*z2 - upz*z1;
+ x1 = upz*z0 - upx*z2;
+ x2 = upx*z1 - upy*z0;
+ len = Math.sqrt(x0*x0 + x1*x1 + x2*x2);
+ if (!len) {
+ x0 = 0;
+ x1 = 0;
+ x2 = 0;
+ } else {
+ len = 1/len;
+ x0 *= len;
+ x1 *= len;
+ x2 *= len;
+ };
+
+ //vec3.normalize(vec3.cross(z, x, y));
+ y0 = z1*x2 - z2*x1;
+ y1 = z2*x0 - z0*x2;
+ y2 = z0*x1 - z1*x0;
+
+ len = Math.sqrt(y0*y0 + y1*y1 + y2*y2);
+ if (!len) {
+ y0 = 0;
+ y1 = 0;
+ y2 = 0;
+ } else {
+ len = 1/len;
+ y0 *= len;
+ y1 *= len;
+ y2 *= len;
}
- dest[0] = mat[0];
- dest[1] = mat[3];
- dest[2] = mat[6];
- dest[3] = mat[1];
- dest[4] = mat[4];
- dest[5] = mat[7];
- dest[6] = mat[2];
- dest[7] = mat[5];
- dest[8] = mat[8];
- return dest;
-};
-
-/*
- * mat3.toMat4
- * Copies the elements of a mat3 into the upper 3x3 elements of a mat4
- *
- * Params:
- * mat - mat3 containing values to copy
- * dest - Optional, mat4 receiving copied values
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
- */
-mat3.toMat4 = function(mat, dest) {
- if(!dest) { dest = mat4.create(); }
-
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
+ dest[0] = x0;
+ dest[1] = y0;
+ dest[2] = z0;
dest[3] = 0;
-
- dest[4] = mat[3];
- dest[5] = mat[4];
- dest[6] = mat[5];
+ dest[4] = x1;
+ dest[5] = y1;
+ dest[6] = z1;
dest[7] = 0;
-
- dest[8] = mat[6];
- dest[9] = mat[7];
- dest[10] = mat[8];
+ dest[8] = x2;
+ dest[9] = y2;
+ dest[10] = z2;
dest[11] = 0;
-
- dest[12] = 0;
- dest[13] = 0;
- dest[14] = 0;
+ dest[12] = -(x0*eyex + x1*eyey + x2*eyez);
+ dest[13] = -(y0*eyex + y1*eyey + y2*eyez);
+ dest[14] = -(z0*eyex + z1*eyey + z2*eyez);
dest[15] = 1;
return dest;
};
/*
- * mat3.str
- * Returns a string representation of a mat3
+ * mat4.str
+ * Returns a string representation of a mat4
*
* Params:
- * mat - mat3 to represent as a string
+ * mat - mat4 to represent as a string
*
* Returns:
* string representation of mat
*/
-mat3.str = function(mat) {
- return '[' + mat[0] + ', ' + mat[1] + ', ' + mat[2] +
- ', ' + mat[3] + ', '+ mat[4] + ', ' + mat[5] +
- ', ' + mat[6] + ', ' + mat[7] + ', '+ mat[8] + ']';
+mat4.str = function(mat) {
+ return '[' + mat[0] + ', ' + mat[1] + ', ' + mat[2] + ', ' + mat[3] +
+ ',\n '+ mat[4] + ', ' + mat[5] + ', ' + mat[6] + ', ' + mat[7] +
+ ',\n '+ mat[8] + ', ' + mat[9] + ', ' + mat[10] + ', ' + mat[11] +
+ ',\n '+ mat[12] + ', ' + mat[13] + ', ' + mat[14] + ', ' + mat[15] + ']';
};
/*
- * mat4 - 4x4 Matrix
+ * quat4 - Quaternions
*/
-var mat4 = {};
+quat4 = {};
/*
- * mat4.create
- * Creates a new instance of a mat4 using the default array type
- * Any javascript array containing at least 16 numeric elements can serve as a mat4
+ * quat4.create
+ * Creates a new instance of a quat4 using the default array type
+ * Any javascript array containing at least 4 numeric elements can serve as a quat4
*
* Params:
- * mat - Optional, mat4 containing values to initialize with
+ * quat - Optional, quat4 containing values to initialize with
*
* Returns:
- * New mat4
+ * New quat4
*/
-mat4.create = function(mat) {
+quat4.create = function(quat) {
var dest;
- if(mat) {
- dest = new glMatrixArrayType(16);
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
- dest[8] = mat[8];
- dest[9] = mat[9];
- dest[10] = mat[10];
- dest[11] = mat[11];
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+ if(quat) {
+ dest = new glMatrixArrayType(4);
+ dest[0] = quat[0];
+ dest[1] = quat[1];
+ dest[2] = quat[2];
+ dest[3] = quat[3];
} else {
if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
+ dest = new glMatrixArrayType([0,0,0,0]);
else
- dest = new glMatrixArrayType(16);
+ dest = new glMatrixArrayType(4);
}
return dest;
};
/*
- * mat4.set
- * Copies the values of one mat4 to another
+ * quat4.set
+ * Copies the values of one quat4 to another
*
* Params:
- * mat - mat4 containing values to copy
- * dest - mat4 receiving copied values
+ * quat - quat4 containing values to copy
+ * dest - quat4 receiving copied values
*
* Returns:
* dest
*/
-mat4.set = function(mat, dest) {
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
- dest[8] = mat[8];
- dest[9] = mat[9];
- dest[10] = mat[10];
- dest[11] = mat[11];
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+quat4.set = function(quat, dest) {
+ dest[0] = quat[0];
+ dest[1] = quat[1];
+ dest[2] = quat[2];
+ dest[3] = quat[3];
+
return dest;
};
/*
- * mat4.identity
- * Sets a mat4 to an identity matrix
+ * quat4.calculateW
+ * Calculates the W component of a quat4 from the X, Y, and Z components.
+ * Assumes that quaternion is 1 unit in length.
+ * Any existing W component will be ignored.
*
* Params:
- * dest - mat4 to set
+ * quat - quat4 to calculate W component of
+ * dest - Optional, quat4 receiving calculated values. If not specified result is written to quat
*
* Returns:
- * dest
+ * dest if specified, quat otherwise
*/
-mat4.identity = function(dest) {
- dest[0] = 1;
- dest[1] = 0;
- dest[2] = 0;
- dest[3] = 0;
- dest[4] = 0;
- dest[5] = 1;
- dest[6] = 0;
- dest[7] = 0;
- dest[8] = 0;
- dest[9] = 0;
- dest[10] = 1;
- dest[11] = 0;
- dest[12] = 0;
- dest[13] = 0;
- dest[14] = 0;
- dest[15] = 1;
- return dest;
-};
+quat4.calculateW = function(quat, dest) {
+ var x = quat[0], y = quat[1], z = quat[2];
-/*
- * mat4.transpose
- * Transposes a mat4 (flips the values over the diagonal)
- *
- * Params:
- * mat - mat4 to transpose
- * dest - Optional, mat4 receiving transposed values. If not specified result is written to mat
- *
- * Returns:
- * dest is specified, mat otherwise
- */
-mat4.transpose = function(mat, dest) {
- // If we are transposing ourselves we can skip a few steps but have to cache some values
- if(!dest || mat == dest) {
- var a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a12 = mat[6], a13 = mat[7];
- var a23 = mat[11];
-
- mat[1] = mat[4];
- mat[2] = mat[8];
- mat[3] = mat[12];
- mat[4] = a01;
- mat[6] = mat[9];
- mat[7] = mat[13];
- mat[8] = a02;
- mat[9] = a12;
- mat[11] = mat[14];
- mat[12] = a03;
- mat[13] = a13;
- mat[14] = a23;
- return mat;
+ if(!dest || quat == dest) {
+ quat[3] = -Math.sqrt(Math.abs(1.0 - x*x - y*y - z*z));
+ return quat;
}
-
- dest[0] = mat[0];
- dest[1] = mat[4];
- dest[2] = mat[8];
- dest[3] = mat[12];
- dest[4] = mat[1];
- dest[5] = mat[5];
- dest[6] = mat[9];
- dest[7] = mat[13];
- dest[8] = mat[2];
- dest[9] = mat[6];
- dest[10] = mat[10];
- dest[11] = mat[14];
- dest[12] = mat[3];
- dest[13] = mat[7];
- dest[14] = mat[11];
- dest[15] = mat[15];
+ dest[0] = x;
+ dest[1] = y;
+ dest[2] = z;
+ dest[3] = -Math.sqrt(Math.abs(1.0 - x*x - y*y - z*z));
return dest;
};
/*
- * mat4.determinant
- * Calculates the determinant of a mat4
+ * quat4.inverse
+ * Calculates the inverse of a quat4
*
* Params:
- * mat - mat4 to calculate determinant of
+ * quat - quat4 to calculate inverse of
+ * dest - Optional, quat4 receiving inverse values. If not specified result is written to quat
*
* Returns:
- * determinant of mat
- */
-mat4.determinant = function(mat) {
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
- var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
-
- return a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 +
- a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 +
- a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 +
- a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 +
- a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 +
- a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33;
+ * dest if specified, quat otherwise
+ */
+quat4.inverse = function(quat, dest) {
+ if(!dest || quat == dest) {
+ quat[0] *= -1;
+ quat[1] *= -1;
+ quat[2] *= -1;
+ return quat;
+ }
+ dest[0] = -quat[0];
+ dest[1] = -quat[1];
+ dest[2] = -quat[2];
+ dest[3] = quat[3];
+ return dest;
};
/*
- * mat4.inverse
- * Calculates the inverse matrix of a mat4
+ * quat4.length
+ * Calculates the length of a quat4
*
* Params:
- * mat - mat4 to calculate inverse of
- * dest - Optional, mat4 receiving inverse matrix. If not specified result is written to mat
+ * quat - quat4 to calculate length of
*
* Returns:
- * dest is specified, mat otherwise
+ * Length of quat
*/
-mat4.inverse = function(mat, dest) {
- if(!dest) { dest = mat; }
-
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
- var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
-
- var b00 = a00*a11 - a01*a10;
- var b01 = a00*a12 - a02*a10;
- var b02 = a00*a13 - a03*a10;
- var b03 = a01*a12 - a02*a11;
- var b04 = a01*a13 - a03*a11;
- var b05 = a02*a13 - a03*a12;
- var b06 = a20*a31 - a21*a30;
- var b07 = a20*a32 - a22*a30;
- var b08 = a20*a33 - a23*a30;
- var b09 = a21*a32 - a22*a31;
- var b10 = a21*a33 - a23*a31;
- var b11 = a22*a33 - a23*a32;
-
- // Calculate the determinant (inlined to avoid double-caching)
- var invDet = 1/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06);
-
- dest[0] = (a11*b11 - a12*b10 + a13*b09)*invDet;
- dest[1] = (-a01*b11 + a02*b10 - a03*b09)*invDet;
- dest[2] = (a31*b05 - a32*b04 + a33*b03)*invDet;
- dest[3] = (-a21*b05 + a22*b04 - a23*b03)*invDet;
- dest[4] = (-a10*b11 + a12*b08 - a13*b07)*invDet;
- dest[5] = (a00*b11 - a02*b08 + a03*b07)*invDet;
- dest[6] = (-a30*b05 + a32*b02 - a33*b01)*invDet;
- dest[7] = (a20*b05 - a22*b02 + a23*b01)*invDet;
- dest[8] = (a10*b10 - a11*b08 + a13*b06)*invDet;
- dest[9] = (-a00*b10 + a01*b08 - a03*b06)*invDet;
- dest[10] = (a30*b04 - a31*b02 + a33*b00)*invDet;
- dest[11] = (-a20*b04 + a21*b02 - a23*b00)*invDet;
- dest[12] = (-a10*b09 + a11*b07 - a12*b06)*invDet;
- dest[13] = (a00*b09 - a01*b07 + a02*b06)*invDet;
- dest[14] = (-a30*b03 + a31*b01 - a32*b00)*invDet;
- dest[15] = (a20*b03 - a21*b01 + a22*b00)*invDet;
-
- return dest;
+quat4.length = function(quat) {
+ var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+ return Math.sqrt(x*x + y*y + z*z + w*w);
};
/*
- * mat4.toRotationMat
- * Copies the upper 3x3 elements of a mat4 into another mat4
+ * quat4.normalize
+ * Generates a unit quaternion of the same direction as the provided quat4
+ * If quaternion length is 0, returns [0, 0, 0, 0]
*
* Params:
- * mat - mat4 containing values to copy
- * dest - Optional, mat4 receiving copied values
+ * quat - quat4 to normalize
+ * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
*
* Returns:
- * dest is specified, a new mat4 otherwise
+ * dest if specified, quat otherwise
*/
-mat4.toRotationMat = function(mat, dest) {
- if(!dest) { dest = mat4.create(); }
+quat4.normalize = function(quat, dest) {
+ if(!dest) { dest = quat; }
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
- dest[8] = mat[8];
- dest[9] = mat[9];
- dest[10] = mat[10];
- dest[11] = mat[11];
- dest[12] = 0;
- dest[13] = 0;
- dest[14] = 0;
- dest[15] = 1;
+ var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+ var len = Math.sqrt(x*x + y*y + z*z + w*w);
+ if(len == 0) {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ return dest;
+ }
+ len = 1/len;
+ dest[0] = x * len;
+ dest[1] = y * len;
+ dest[2] = z * len;
+ dest[3] = w * len;
return dest;
};
/*
- * mat4.toMat3
- * Copies the upper 3x3 elements of a mat4 into a mat3
+ * quat4.multiply
+ * Performs a quaternion multiplication
*
* Params:
- * mat - mat4 containing values to copy
- * dest - Optional, mat3 receiving copied values
+ * quat - quat4, first operand
+ * quat2 - quat4, second operand
+ * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
*
* Returns:
- * dest is specified, a new mat3 otherwise
+ * dest if specified, quat otherwise
*/
-mat4.toMat3 = function(mat, dest) {
- if(!dest) { dest = mat3.create(); }
+quat4.multiply = function(quat, quat2, dest) {
+ if(!dest) { dest = quat; }
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[4];
- dest[4] = mat[5];
- dest[5] = mat[6];
- dest[6] = mat[8];
- dest[7] = mat[9];
- dest[8] = mat[10];
+ var qax = quat[0], qay = quat[1], qaz = quat[2], qaw = quat[3];
+ var qbx = quat2[0], qby = quat2[1], qbz = quat2[2], qbw = quat2[3];
+
+ dest[0] = qax*qbw + qaw*qbx + qay*qbz - qaz*qby;
+ dest[1] = qay*qbw + qaw*qby + qaz*qbx - qax*qbz;
+ dest[2] = qaz*qbw + qaw*qbz + qax*qby - qay*qbx;
+ dest[3] = qaw*qbw - qax*qbx - qay*qby - qaz*qbz;
return dest;
};
/*
- * mat4.toInverseMat3
- * Calculates the inverse of the upper 3x3 elements of a mat4 and copies the result into a mat3
- * The resulting matrix is useful for calculating transformed normals
+ * quat4.multiplyVec3
+ * Transforms a vec3 with the given quaternion
*
* Params:
- * mat - mat4 containing values to invert and copy
- * dest - Optional, mat3 receiving values
+ * quat - quat4 to transform the vector with
+ * vec - vec3 to transform
+ * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
*
* Returns:
- * dest is specified, a new mat3 otherwise
+ * dest if specified, vec otherwise
*/
-mat4.toInverseMat3 = function(mat, dest) {
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10];
-
- var b01 = a22*a11-a12*a21;
- var b11 = -a22*a10+a12*a20;
- var b21 = a21*a10-a11*a20;
-
- var d = a00*b01 + a01*b11 + a02*b21;
- if (!d) { return null; }
- var id = 1/d;
+quat4.multiplyVec3 = function(quat, vec, dest) {
+ if(!dest) { dest = vec; }
- if(!dest) { dest = mat3.create(); }
+ var x = vec[0], y = vec[1], z = vec[2];
+ var qx = quat[0], qy = quat[1], qz = quat[2], qw = quat[3];
+
+ // calculate quat * vec
+ var ix = qw*x + qy*z - qz*y;
+ var iy = qw*y + qz*x - qx*z;
+ var iz = qw*z + qx*y - qy*x;
+ var iw = -qx*x - qy*y - qz*z;
- dest[0] = b01*id;
- dest[1] = (-a22*a01 + a02*a21)*id;
- dest[2] = (a12*a01 - a02*a11)*id;
- dest[3] = b11*id;
- dest[4] = (a22*a00 - a02*a20)*id;
- dest[5] = (-a12*a00 + a02*a10)*id;
- dest[6] = b21*id;
- dest[7] = (-a21*a00 + a01*a20)*id;
- dest[8] = (a11*a00 - a01*a10)*id;
+ // calculate result * inverse quat
+ dest[0] = ix*qw + iw*-qx + iy*-qz - iz*-qy;
+ dest[1] = iy*qw + iw*-qy + iz*-qx - ix*-qz;
+ dest[2] = iz*qw + iw*-qz + ix*-qy - iy*-qx;
return dest;
};
/*
- * mat4.multiply
- * Performs a matrix multiplication
+ * quat4.toMat3
+ * Calculates a 3x3 matrix from the given quat4
*
* Params:
- * mat - mat4, first operand
- * mat2 - mat4, second operand
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
+ * quat - quat4 to create matrix from
+ * dest - Optional, mat3 receiving operation result
*
* Returns:
- * dest if specified, mat otherwise
+ * dest if specified, a new mat3 otherwise
*/
-mat4.multiply = function(mat, mat2, dest) {
- if(!dest) { dest = mat; }
-
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
- var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15];
-
- var b00 = mat2[0], b01 = mat2[1], b02 = mat2[2], b03 = mat2[3];
- var b10 = mat2[4], b11 = mat2[5], b12 = mat2[6], b13 = mat2[7];
- var b20 = mat2[8], b21 = mat2[9], b22 = mat2[10], b23 = mat2[11];
- var b30 = mat2[12], b31 = mat2[13], b32 = mat2[14], b33 = mat2[15];
+quat4.toMat3 = function(quat, dest) {
+ if(!dest) { dest = mat3.create(); }
- dest[0] = b00*a00 + b01*a10 + b02*a20 + b03*a30;
- dest[1] = b00*a01 + b01*a11 + b02*a21 + b03*a31;
- dest[2] = b00*a02 + b01*a12 + b02*a22 + b03*a32;
- dest[3] = b00*a03 + b01*a13 + b02*a23 + b03*a33;
- dest[4] = b10*a00 + b11*a10 + b12*a20 + b13*a30;
- dest[5] = b10*a01 + b11*a11 + b12*a21 + b13*a31;
- dest[6] = b10*a02 + b11*a12 + b12*a22 + b13*a32;
- dest[7] = b10*a03 + b11*a13 + b12*a23 + b13*a33;
- dest[8] = b20*a00 + b21*a10 + b22*a20 + b23*a30;
- dest[9] = b20*a01 + b21*a11 + b22*a21 + b23*a31;
- dest[10] = b20*a02 + b21*a12 + b22*a22 + b23*a32;
- dest[11] = b20*a03 + b21*a13 + b22*a23 + b23*a33;
- dest[12] = b30*a00 + b31*a10 + b32*a20 + b33*a30;
- dest[13] = b30*a01 + b31*a11 + b32*a21 + b33*a31;
- dest[14] = b30*a02 + b31*a12 + b32*a22 + b33*a32;
- dest[15] = b30*a03 + b31*a13 + b32*a23 + b33*a33;
+ var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+
+ var xx = x*x2;
+ var xy = x*y2;
+ var xz = x*z2;
+
+ var yy = y*y2;
+ var yz = y*z2;
+ var zz = z*z2;
+
+ var wx = w*x2;
+ var wy = w*y2;
+ var wz = w*z2;
+
+ dest[0] = 1 - (yy + zz);
+ dest[1] = xy - wz;
+ dest[2] = xz + wy;
+
+ dest[3] = xy + wz;
+ dest[4] = 1 - (xx + zz);
+ dest[5] = yz - wx;
+
+ dest[6] = xz - wy;
+ dest[7] = yz + wx;
+ dest[8] = 1 - (xx + yy);
return dest;
};
/*
- * mat4.multiplyVec3
- * Transforms a vec3 with the given matrix
- * 4th vector component is implicitly '1'
+ * quat4.toMat4
+ * Calculates a 4x4 matrix from the given quat4
*
* Params:
- * mat - mat4 to transform the vector with
- * vec - vec3 to transform
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
+ * quat - quat4 to create matrix from
+ * dest - Optional, mat4 receiving operation result
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, a new mat4 otherwise
*/
-mat4.multiplyVec3 = function(mat, vec, dest) {
- if(!dest) { dest = vec; }
-
- var x = vec[0], y = vec[1], z = vec[2];
+quat4.toMat4 = function(quat, dest) {
+ if(!dest) { dest = mat4.create(); }
- dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12];
- dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13];
- dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14];
+ var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+
+ var x2 = x + x;
+ var y2 = y + y;
+ var z2 = z + z;
+
+ var xx = x*x2;
+ var xy = x*y2;
+ var xz = x*z2;
+
+ var yy = y*y2;
+ var yz = y*z2;
+ var zz = z*z2;
+
+ var wx = w*x2;
+ var wy = w*y2;
+ var wz = w*z2;
+
+ dest[0] = 1 - (yy + zz);
+ dest[1] = xy - wz;
+ dest[2] = xz + wy;
+ dest[3] = 0;
+
+ dest[4] = xy + wz;
+ dest[5] = 1 - (xx + zz);
+ dest[6] = yz - wx;
+ dest[7] = 0;
+
+ dest[8] = xz - wy;
+ dest[9] = yz + wx;
+ dest[10] = 1 - (xx + yy);
+ dest[11] = 0;
+
+ dest[12] = 0;
+ dest[13] = 0;
+ dest[14] = 0;
+ dest[15] = 1;
return dest;
};
/*
- * mat4.multiplyVec4
- * Transforms a vec4 with the given matrix
+ * quat4.slerp
+ * Performs a spherical linear interpolation between two quat4
*
* Params:
- * mat - mat4 to transform the vector with
- * vec - vec4 to transform
- * dest - Optional, vec4 receiving operation result. If not specified result is written to vec
+ * quat - quat4, first quaternion
+ * quat2 - quat4, second quaternion
+ * slerp - interpolation amount between the two inputs
+ * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
*
* Returns:
- * dest if specified, vec otherwise
+ * dest if specified, quat otherwise
*/
-mat4.multiplyVec4 = function(mat, vec, dest) {
- if(!dest) { dest = vec; }
+quat4.slerp = function(quat, quat2, slerp, dest) {
+ if(!dest) { dest = quat; }
+
+ var cosHalfTheta = quat[0]*quat2[0] + quat[1]*quat2[1] + quat[2]*quat2[2] + quat[3]*quat2[3];
- var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+ if (Math.abs(cosHalfTheta) >= 1.0){
+ if(dest != quat) {
+ dest[0] = quat[0];
+ dest[1] = quat[1];
+ dest[2] = quat[2];
+ dest[3] = quat[3];
+ }
+ return dest;
+ }
- dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12]*w;
- dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13]*w;
- dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14]*w;
- dest[3] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15]*w;
+ var halfTheta = Math.acos(cosHalfTheta);
+ var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta*cosHalfTheta);
+
+ if (Math.abs(sinHalfTheta) < 0.001){
+ dest[0] = (quat[0]*0.5 + quat2[0]*0.5);
+ dest[1] = (quat[1]*0.5 + quat2[1]*0.5);
+ dest[2] = (quat[2]*0.5 + quat2[2]*0.5);
+ dest[3] = (quat[3]*0.5 + quat2[3]*0.5);
+ return dest;
+ }
+
+ var ratioA = Math.sin((1 - slerp)*halfTheta) / sinHalfTheta;
+ var ratioB = Math.sin(slerp*halfTheta) / sinHalfTheta;
+
+ dest[0] = (quat[0]*ratioA + quat2[0]*ratioB);
+ dest[1] = (quat[1]*ratioA + quat2[1]*ratioB);
+ dest[2] = (quat[2]*ratioA + quat2[2]*ratioB);
+ dest[3] = (quat[3]*ratioA + quat2[3]*ratioB);
return dest;
};
+
/*
- * mat4.translate
- * Translates a matrix by the given vector
+ * quat4.str
+ * Returns a string representation of a quaternion
*
* Params:
- * mat - mat4 to translate
- * vec - vec3 specifying the translation
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
+ * quat - quat4 to represent as a string
*
* Returns:
- * dest if specified, mat otherwise
+ * string representation of quat
*/
-mat4.translate = function(mat, vec, dest) {
- var x = vec[0], y = vec[1], z = vec[2];
+quat4.str = function(quat) {
+ return '[' + quat[0] + ', ' + quat[1] + ', ' + quat[2] + ', ' + quat[3] + ']';
+};
+
+
+define("glMatrix", ["typedefs"], (function (global) {
+ return function () {
+ var ret, fn;
+ return ret || global.glMatrix;
+ };
+}(this)));
+
+/*
+ * glMatrixAddon.js
+ * Extension to the glMatrix library. The original glMatrix library
+ * was created by Brandon Jones.
+ */
+
+
+mat4.xVec4 = function(mat, vec, dest){
+ if(!dest) { dest = vec; }
+ var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+
+ dest[0] = mat[0]*x + mat[1]*y + mat[2]*z + mat[3]*w;
+ dest[1] = mat[4]*x + mat[5]*y + mat[6]*z + mat[7]*w;
+ dest[2] = mat[8]*x + mat[9]*y + mat[10]*z + mat[11]*w;
+ dest[3] = mat[12]*x + mat[13]*y + mat[14]*z + mat[15]*w;
+ return dest;
+};
+
+mat3.scale = function(mat, scalar, dest){
if(!dest || mat == dest) {
- mat[12] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12];
- mat[13] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13];
- mat[14] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14];
- mat[15] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15];
+ mat[0] *= scalar;
+ mat[1] *= scalar;
+ mat[2] *= scalar;
+ mat[3] *= scalar;
+ mat[4] *= scalar;
+ mat[5] *= scalar;
+ mat[6] *= scalar;
+ mat[7] *= scalar;
+ mat[8] *= scalar;
return mat;
}
+ dest = mat3.create();
+ dest[0] = mat[0]*scalar;
+ dest[1] = mat[1]*scalar;
+ dest[2] = mat[2]*scalar;
+ dest[3] = mat[3]*scalar;
+ dest[4] = mat[4]*scalar;
+ dest[5] = mat[5]*scalar;
+ dest[6] = mat[6]*scalar;
+ dest[7] = mat[7]*scalar;
+ dest[8] = mat[8]*scalar;
+ return dest;
+};
+
+mat3.inverse = function(mat, dest){
+ if(!dest) { dest = mat; }
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
-
- dest[0] = a00;
- dest[1] = a01;
- dest[2] = a02;
- dest[3] = a03;
- dest[4] = a10;
- dest[5] = a11;
- dest[6] = a12;
- dest[7] = a13;
- dest[8] = a20;
- dest[9] = a21;
- dest[10] = a22;
- dest[11] = a23;
+ var ha00 = mat[0], ha01 = mat[1], ha02 = mat[2];
+ var ha10 = mat[3], ha11 = mat[4], ha12 = mat[5];
+ var ha20 = mat[6], ha21 = mat[7], ha22 = mat[8];
- dest[12] = a00*x + a10*y + a20*z + mat[12];
- dest[13] = a01*x + a11*y + a21*z + mat[13];
- dest[14] = a02*x + a12*y + a22*z + mat[14];
- dest[15] = a03*x + a13*y + a23*z + mat[15];
+ var invDetA = 1/(ha00*ha11*ha22 + ha01*ha12*ha20 + ha02*ha10*ha21 - ha02*ha11*ha20 - ha01*ha10*ha22 - ha00*ha12*ha21);
+ dest[0] = (ha11*ha22 - ha12*ha21)*invDetA;
+ dest[1] = (ha02*ha21 - ha01*ha22)*invDetA;
+ dest[2] = (ha01*ha12 - ha02*ha11)*invDetA;
+ dest[3] = (ha12*ha20 - ha10*ha22)*invDetA;
+ dest[4] = (ha00*ha22 - ha02*ha20)*invDetA;
+ dest[5] = (ha02*ha10 - ha00*ha12)*invDetA;
+ dest[6] = (ha10*ha21 - ha11*ha20)*invDetA;
+ dest[7] = (ha01*ha20 - ha00*ha21)*invDetA;
+ dest[8] = (ha00*ha11 - ha01*ha10)*invDetA;
return dest;
};
-/*
- * mat4.scale
- * Scales a matrix by the given vector
- *
- * Params:
- * mat - mat4 to scale
- * vec - vec3 specifying the scale for each axis
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
- *
- * Returns:
- * dest if specified, mat otherwise
- */
-mat4.scale = function(mat, vec, dest) {
+mat3.multiply = function(mat, mat2, dest) {
+ if(!dest) { dest = mat; }
+
+ var ha00 = mat[0], ha01 = mat[1], ha02 = mat[2];
+ var ha10 = mat[3], ha11 = mat[4], ha12 = mat[5];
+ var ha20 = mat[6], ha21 = mat[7], ha22 = mat[8];
+
+ var hb00 = mat2[0], hb01 = mat2[1], hb02 = mat2[2];
+ var hb10 = mat2[3], hb11 = mat2[4], hb12 = mat2[5];
+ var hb20 = mat2[6], hb21 = mat2[7], hb22 = mat2[8];
+
+ dest[0] = ha00*hb00 + ha01*hb10 + ha02*hb20;
+ dest[1] = ha00*hb01 + ha01*hb11 + ha02*hb21;
+ dest[2] = ha00*hb02 + ha01*hb12 + ha02*hb22;
+
+ dest[3] = ha10*hb00 + ha11*hb10 + ha12*hb20;
+ dest[4] = ha10*hb01 + ha11*hb11 + ha12*hb21;
+ dest[5] = ha10*hb02 + ha11*hb12 + ha12*hb22;
+
+ dest[6] = ha20*hb00 + ha21*hb10 + ha22*hb20;
+ dest[7] = ha20*hb01 + ha21*hb11 + ha22*hb21;
+ dest[8] = ha20*hb02 + ha21*hb12 + ha22*hb22;
+ return dest;
+};
+
+mat3.xVec3 = function(mat, vec, dest){
+ if(!dest) { dest = vec; }
var x = vec[0], y = vec[1], z = vec[2];
- if(!dest || mat == dest) {
- mat[0] *= x;
- mat[1] *= x;
- mat[2] *= x;
- mat[3] *= x;
- mat[4] *= y;
- mat[5] *= y;
- mat[6] *= y;
- mat[7] *= y;
- mat[8] *= z;
- mat[9] *= z;
- mat[10] *= z;
- mat[11] *= z;
- return mat;
- }
+ dest[0] = mat[0]*x + mat[1]*y + mat[2]*z;
+ dest[1] = mat[3]*x + mat[4]*y + mat[5]*z;
+ dest[2] = mat[6]*x + mat[7]*y + mat[8]*z;
- dest[0] = mat[0]*x;
- dest[1] = mat[1]*x;
- dest[2] = mat[2]*x;
- dest[3] = mat[3]*x;
- dest[4] = mat[4]*y;
- dest[5] = mat[5]*y;
- dest[6] = mat[6]*y;
- dest[7] = mat[7]*y;
- dest[8] = mat[8]*z;
- dest[9] = mat[9]*z;
- dest[10] = mat[10]*z;
- dest[11] = mat[11]*z;
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
return dest;
};
-/*
- * mat4.rotate
- * Rotates a matrix by the given angle around the specified axis
- * If rotating around a primary axis (X,Y,Z) one of the specialized rotation functions should be used instead for performance
- *
- * Params:
- * mat - mat4 to rotate
- * angle - angle (in radians) to rotate
- * axis - vec3 representing the axis to rotate around
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
- *
- * Returns:
- * dest if specified, mat otherwise
- */
-mat4.rotate = function(mat, angle, axis, dest) {
- var x = axis[0], y = axis[1], z = axis[2];
- var len = Math.sqrt(x*x + y*y + z*z);
- if (!len) { return null; }
- if (len != 1) {
- len = 1 / len;
- x *= len;
- y *= len;
- z *= len;
- }
-
- var s = Math.sin(angle);
- var c = Math.cos(angle);
- var t = 1-c;
+var vec4={};
+
+vec4.create = function(vec){
+ var dest;
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+ if(vec) {
+ dest = new glMatrixArrayType(4);
+ dest[0] = vec[0];
+ dest[1] = vec[1];
+ dest[2] = vec[2];
+ dest[3] = vec[3];
+ } else {
+ if(glMatrixArrayType === Array)
+ dest = new glMatrixArrayType([0,0,0,0]);
+ else
+ dest = new glMatrixArrayType(4);
+ }
- // Construct the elements of the rotation matrix
- var b00 = x*x*t + c, b01 = y*x*t + z*s, b02 = z*x*t - y*s;
- var b10 = x*y*t - z*s, b11 = y*y*t + c, b12 = z*y*t + x*s;
- var b20 = x*z*t + y*s, b21 = y*z*t - x*s, b22 = z*z*t + c;
+ return dest;
+};
+
+vec4.project = function(vec, dest){
+ if(!dest) { dest = vec; }
- if(!dest) {
- dest = mat;
- } else if(mat != dest) { // If the source and destination differ, copy the unchanged last row
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+ dest[0] = vec[0]/vec[3];
+ dest[1] = vec[1]/vec[3];
+ dest[2] = vec[2]/vec[3];
+ return dest;
+};
+
+vec4.scale = function(vec, val, dest){
+ if(!dest || vec == dest) {
+ vec[0] *= val;
+ vec[1] *= val;
+ vec[2] *= val;
+ vec[4] *= val;
+ return vec;
}
- // Perform rotation-specific matrix multiplication
- dest[0] = a00*b00 + a10*b01 + a20*b02;
- dest[1] = a01*b00 + a11*b01 + a21*b02;
- dest[2] = a02*b00 + a12*b01 + a22*b02;
- dest[3] = a03*b00 + a13*b01 + a23*b02;
+ dest[0] = vec[0]*val;
+ dest[1] = vec[1]*val;
+ dest[2] = vec[2]*val;
+ dest[3] = vec[3]*val;
+ return dest;
+};
+
+vec4.xMat4 = function(vec, mat, dest){
+ if(!dest) { dest = vec; }
- dest[4] = a00*b10 + a10*b11 + a20*b12;
- dest[5] = a01*b10 + a11*b11 + a21*b12;
- dest[6] = a02*b10 + a12*b11 + a22*b12;
- dest[7] = a03*b10 + a13*b11 + a23*b12;
+ var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+
+ dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12]*w;
+ dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13]*w;
+ dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14]*w;
+ dest[3] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15]*w;
- dest[8] = a00*b20 + a10*b21 + a20*b22;
- dest[9] = a01*b20 + a11*b21 + a21*b22;
- dest[10] = a02*b20 + a12*b21 + a22*b22;
- dest[11] = a03*b20 + a13*b21 + a23*b22;
return dest;
};
-/*
- * mat4.rotateX
- * Rotates a matrix by the given angle around the X axis
- *
- * Params:
- * mat - mat4 to rotate
- * angle - angle (in radians) to rotate
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
- *
- * Returns:
- * dest if specified, mat otherwise
- */
-mat4.rotateX = function(mat, angle, dest) {
- var s = Math.sin(angle);
- var c = Math.cos(angle);
-
- // Cache the matrix values (makes for huge speed increases!)
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
- if(!dest) {
- dest = mat;
- } else if(mat != dest) { // If the source and destination differ, copy the unchanged rows
+var mat2 = {};
+
+mat2.create = function(mat){
+ var dest;
+
+ if(mat) {
+ dest = new glMatrixArrayType(4);
dest[0] = mat[0];
dest[1] = mat[1];
dest[2] = mat[2];
dest[3] = mat[3];
-
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+ } else {
+ if(glMatrixArrayType === Array)
+ dest = new glMatrixArrayType([0,0,0,0]);
+ else
+ dest = new glMatrixArrayType(4);
}
- // Perform axis-specific matrix multiplication
- dest[4] = a10*c + a20*s;
- dest[5] = a11*c + a21*s;
- dest[6] = a12*c + a22*s;
- dest[7] = a13*c + a23*s;
+ return dest;
+};
+
+mat2.xVec2 = function(mat, vec, dest){
+ if(!dest) { dest = vec; }
+ var x = vec[0], y = vec[1];
+
+ dest[0] = mat[0]*x + mat[1]*y;
+ dest[1] = mat[2]*x + mat[3]*y;
- dest[8] = a10*-s + a20*c;
- dest[9] = a11*-s + a21*c;
- dest[10] = a12*-s + a22*c;
- dest[11] = a13*-s + a23*c;
return dest;
};
-/*
- * mat4.rotateY
- * Rotates a matrix by the given angle around the Y axis
- *
- * Params:
- * mat - mat4 to rotate
- * angle - angle (in radians) to rotate
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
- *
- * Returns:
- * dest if specified, mat otherwise
- */
-mat4.rotateY = function(mat, angle, dest) {
- var s = Math.sin(angle);
- var c = Math.cos(angle);
+mat2.scale = function(mat, scale, dest){
+ if(!dest || mat == dest) {
+ mat[0] *= scale;
+ mat[1] *= scale;
+ mat[2] *= scale;
+ mat[3] *= scale;
+ return mat;
+ }
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11];
+ dest[0] = mat[0]*scale;
+ dest[1] = mat[1]*scale;
+ dest[2] = mat[2]*scale;
+ dest[3] = mat[3]*scale;
+ return dest;
+};
+
+mat2.determinant = function(mat){
+ return mat[0]*mat[3] - mat[1]*mat[2];
+};
+
+mat2.inverse = function(mat){
+ var scale = 1/(mat2.determinant(mat));
+ var a = mat[3]*scale,
+ b = -mat[1]*scale,
+ c = -mat[2]*scale,
+ d = mat[0];
+ mat[0] = a;
+ mat[1] = b;
+ mat[2] = c;
+ mat[3] = d;
+ return mat;
+};
+
+var vec2 = {};
+vec2.create = function(vec){
+ var dest;
- if(!dest) {
- dest = mat;
- } else if(mat != dest) { // If the source and destination differ, copy the unchanged rows
- dest[4] = mat[4];
- dest[5] = mat[5];
- dest[6] = mat[6];
- dest[7] = mat[7];
-
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+ if(vec) {
+ dest = new glMatrixArrayType(2);
+ dest[0] = vec[0];
+ dest[1] = vec[1];
+ } else {
+ if(glMatrixArrayType === Array)
+ dest = new glMatrixArrayType([0,0]);
+ else
+ dest = new glMatrixArrayType(2);
}
- // Perform axis-specific matrix multiplication
- dest[0] = a00*c + a20*-s;
- dest[1] = a01*c + a21*-s;
- dest[2] = a02*c + a22*-s;
- dest[3] = a03*c + a23*-s;
-
- dest[8] = a00*s + a20*c;
- dest[9] = a01*s + a21*c;
- dest[10] = a02*s + a22*c;
- dest[11] = a03*s + a23*c;
return dest;
};
-/*
- * mat4.rotateZ
- * Rotates a matrix by the given angle around the Z axis
- *
- * Params:
- * mat - mat4 to rotate
- * angle - angle (in radians) to rotate
- * dest - Optional, mat4 receiving operation result. If not specified result is written to mat
- *
- * Returns:
- * dest if specified, mat otherwise
- */
-mat4.rotateZ = function(mat, angle, dest) {
- var s = Math.sin(angle);
- var c = Math.cos(angle);
-
- // Cache the matrix values (makes for huge speed increases!)
- var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3];
- var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7];
-
- if(!dest) {
- dest = mat;
- } else if(mat != dest) { // If the source and destination differ, copy the unchanged last row
- dest[8] = mat[8];
- dest[9] = mat[9];
- dest[10] = mat[10];
- dest[11] = mat[11];
-
- dest[12] = mat[12];
- dest[13] = mat[13];
- dest[14] = mat[14];
- dest[15] = mat[15];
+vec2.subtract = function(vec, vec2, dest) {
+ if(!dest || vec == dest) {
+ vec[0] -= vec2[0];
+ vec[1] -= vec2[1];
+ return vec;
}
- // Perform axis-specific matrix multiplication
- dest[0] = a00*c + a10*s;
- dest[1] = a01*c + a11*s;
- dest[2] = a02*c + a12*s;
- dest[3] = a03*c + a13*s;
-
- dest[4] = a00*-s + a10*c;
- dest[5] = a01*-s + a11*c;
- dest[6] = a02*-s + a12*c;
- dest[7] = a03*-s + a13*c;
+ dest[0] = vec[0] - vec2[0];
+ dest[1] = vec[1] - vec2[1];
+ return dest;
+};
+
+vec2.add = function(vec, vec2, dest) {
+ if(!dest || vec == dest) {
+ vec[0] += vec2[0];
+ vec[1] += vec2[1];
+ return vec;
+ }
+ dest[0] = vec[0] + vec2[0];
+ dest[1] = vec[1] + vec2[1];
return dest;
};
-/*
- * mat4.frustum
- * Generates a frustum matrix with the given bounds
- *
- * Params:
- * left, right - scalar, left and right bounds of the frustum
- * bottom, top - scalar, bottom and top bounds of the frustum
- * near, far - scalar, near and far bounds of the frustum
- * dest - Optional, mat4 frustum matrix will be written into
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
- */
-mat4.frustum = function(left, right, bottom, top, near, far, dest) {
- if(!dest) { dest = mat4.create(); }
- var rl = (right - left);
- var tb = (top - bottom);
- var fn = (far - near);
- dest[0] = (near*2) / rl;
- dest[1] = 0;
- dest[2] = 0;
- dest[3] = 0;
- dest[4] = 0;
- dest[5] = (near*2) / tb;
- dest[6] = 0;
- dest[7] = 0;
- dest[8] = (right + left) / rl;
- dest[9] = (top + bottom) / tb;
- dest[10] = -(far + near) / fn;
- dest[11] = -1;
- dest[12] = 0;
- dest[13] = 0;
- dest[14] = -(far*near*2) / fn;
- dest[15] = 0;
+vec2.scale = function(vec, val, dest) {
+ if(!dest || vec == dest) {
+ vec[0] *= val;
+ vec[1] *= val;
+ return vec;
+ }
+
+ dest[0] = vec[0]*val;
+ dest[1] = vec[1]*val;
return dest;
};
-/*
- * mat4.perspective
- * Generates a perspective projection matrix with the given bounds
- *
- * Params:
- * fovy - scalar, vertical field of view
- * aspect - scalar, aspect ratio. typically viewport width/height
- * near, far - scalar, near and far bounds of the frustum
- * dest - Optional, mat4 frustum matrix will be written into
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
+vec2.normalize = function(vec, dest) {
+ if(!dest) { dest = vec; }
+
+ var x = vec[0], y = vec[1];
+ var len = Math.sqrt(x*x + y*y);
+
+ if (!len) {
+ dest[0] = 0;
+ dest[1] = 0;
+ return dest;
+ } else if (len == 1) {
+ dest[0] = x;
+ dest[1] = y;
+ return dest;
+ }
+
+ len = 1 / len;
+ dest[0] = x*len;
+ dest[1] = y*len;
+ return dest;
+};
+
+vec2.dot = function(vec, vec2){
+ return vec[0]*vec2[0] + vec[1]*vec2[1];
+};
+
+vec2.multiply = function(vec, vec2, dest){
+ if(!dest) { dest = vec; }
+
+ dest[0] = vec[0]*vec2[0];
+ dest[1] = vec[1]*vec2[1];
+ return dest;
+};
+
+/**
+ * @param vec vec2 to be unprojected [x,y] -> [x,y,1]
+ * @returns vec3 unprojected vector
*/
-mat4.perspective = function(fovy, aspect, near, far, dest) {
- var top = near*Math.tan(fovy*Math.PI / 360.0);
- var right = top*aspect;
- return mat4.frustum(-right, right, -top, top, near, far, dest);
+vec2.unproject = function(vec){
+ return vec3.create([vec[0], vec[1], 1]);
};
-/*
- * mat4.ortho
- * Generates a orthogonal projection matrix with the given bounds
- *
- * Params:
- * left, right - scalar, left and right bounds of the frustum
- * bottom, top - scalar, bottom and top bounds of the frustum
- * near, far - scalar, near and far bounds of the frustum
- * dest - Optional, mat4 frustum matrix will be written into
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
+vec2.length = function(vec){
+ return Math.sqrt(vec[0]*vec[0] + vec[1]*vec[1]);
+};
+
+vec2.perspectiveProject = function(vec){
+ var result = vec2.create(vec);
+ return vec2.scale(result, 1/vec[2]);
+};
+
+/**
+ * @param vec vec3 to be projected [x,y,z] -> [x/z,y/z]
+ * @returns vec2 projected vector
*/
-mat4.ortho = function(left, right, bottom, top, near, far, dest) {
- if(!dest) { dest = mat4.create(); }
- var rl = (right - left);
- var tb = (top - bottom);
- var fn = (far - near);
- dest[0] = 2 / rl;
- dest[1] = 0;
- dest[2] = 0;
- dest[3] = 0;
- dest[4] = 0;
- dest[5] = 2 / tb;
- dest[6] = 0;
- dest[7] = 0;
- dest[8] = 0;
- dest[9] = 0;
- dest[10] = -2 / fn;
- dest[11] = 0;
- dest[12] = -(left + right) / rl;
- dest[13] = -(top + bottom) / tb;
- dest[14] = -(far + near) / fn;
- dest[15] = 1;
+vec3.project = function(vec){
+ return vec2.scale(vec2.create(vec), 1/vec[2]);
+};
+
+var vec6 = {};
+vec6.scale = function(vec, val, dest){
+ if(!dest || vec == dest) {
+ vec[0] *= val;
+ vec[1] *= val;
+ vec[2] *= val;
+ vec[3] *= val;
+ vec[4] *= val;
+ vec[5] *= val;
+ return vec;
+ }
+
+ dest[0] = vec[0]*val;
+ dest[1] = vec[1]*val;
+ dest[2] = vec[2]*val;
+ dest[3] = vec[3]*val;
+ dest[4] = vec[4]*val;
+ dest[5] = vec[5]*val;
return dest;
};
-/*
- * mat4.ortho
- * Generates a look-at matrix with the given eye position, focal point, and up axis
- *
- * Params:
- * eye - vec3, position of the viewer
- * center - vec3, point the viewer is looking at
- * up - vec3 pointing "up"
- * dest - Optional, mat4 frustum matrix will be written into
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
- */
-mat4.lookAt = function(eye, center, up, dest) {
- if(!dest) { dest = mat4.create(); }
-
- var eyex = eye[0],
- eyey = eye[1],
- eyez = eye[2],
- upx = up[0],
- upy = up[1],
- upz = up[2],
- centerx = center[0],
- centery = center[1],
- centerz = center[2];
-
- if (eyex == centerx && eyey == centery && eyez == centerz) {
- return mat4.identity(dest);
+vec6.subtract = function(vec, vec2, dest){
+ if(!dest || vec == dest) {
+ vec[0] -= vec2[0];
+ vec[1] -= vec2[1];
+ vec[2] -= vec2[2];
+ vec[3] -= vec2[3];
+ vec[4] -= vec2[4];
+ vec[5] -= vec2[5];
+ return vec;
}
- var z0,z1,z2,x0,x1,x2,y0,y1,y2,len;
-
- //vec3.direction(eye, center, z);
- z0 = eyex - center[0];
- z1 = eyey - center[1];
- z2 = eyez - center[2];
-
- // normalize (no check needed for 0 because of early return)
- len = 1/Math.sqrt(z0*z0 + z1*z1 + z2*z2);
- z0 *= len;
- z1 *= len;
- z2 *= len;
-
- //vec3.normalize(vec3.cross(up, z, x));
- x0 = upy*z2 - upz*z1;
- x1 = upz*z0 - upx*z2;
- x2 = upx*z1 - upy*z0;
- len = Math.sqrt(x0*x0 + x1*x1 + x2*x2);
- if (!len) {
- x0 = 0;
- x1 = 0;
- x2 = 0;
- } else {
- len = 1/len;
- x0 *= len;
- x1 *= len;
- x2 *= len;
- };
+ dest[0] = vec[0] - vec2[0];
+ dest[1] = vec[1] - vec2[1];
+ dest[2] = vec[2] - vec2[2];
+ dest[3] = vec[3] - vec2[3];
+ dest[4] = vec[4] - vec2[4];
+ dest[5] = vec[5] - vec2[5];
+ return dest;
+};
+
+vec6.dot = function(vec, vec2){
+ return vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2] + vec[3]*vec2[3] + vec[4]*vec2[4] + vec[5]*vec2[5];
+};
+
+var mat6 = {};
+mat6.xVec6 = function(mat, vec, dest){
+ if(!dest) { dest = vec; }
+ var x = vec[0], y = vec[1], z = vec[2], u = vec[3], w = vec[4], v = vec[5];
- //vec3.normalize(vec3.cross(z, x, y));
- y0 = z1*x2 - z2*x1;
- y1 = z2*x0 - z0*x2;
- y2 = z0*x1 - z1*x0;
+ dest[0] = mat[0]*x + mat[1]*y + mat[2]*z + mat[3]*u + mat[4]*w + mat[5]*v;
+ dest[1] = mat[6]*x + mat[7]*y + mat[8]*z + mat[9]*u + mat[10]*w + mat[11]*v;
+ dest[2] = mat[12]*x + mat[13]*y + mat[14]*z + mat[15]*u + mat[16]*w + mat[17]*v;
+ dest[3] = mat[18]*x + mat[19]*y + mat[20]*z + mat[21]*u + mat[22]*w + mat[23]*v;
+ dest[4] = mat[24]*x + mat[25]*y + mat[26]*z + mat[27]*u + mat[28]*w + mat[29]*v;
+ dest[5] = mat[30]*x + mat[31]*y + mat[32]*z + mat[33]*u + mat[34]*w + mat[35]*v;
- len = Math.sqrt(y0*y0 + y1*y1 + y2*y2);
- if (!len) {
- y0 = 0;
- y1 = 0;
- y2 = 0;
- } else {
- len = 1/len;
- y0 *= len;
- y1 *= len;
- y2 *= len;
- }
+ return dest;
+};
+
+mat3.xVec3 = function(mat, vec, dest){
+ if(!dest) { dest = vec; }
+ var x = vec[0], y = vec[1], z = vec[2];
- dest[0] = x0;
- dest[1] = y0;
- dest[2] = z0;
- dest[3] = 0;
- dest[4] = x1;
- dest[5] = y1;
- dest[6] = z1;
- dest[7] = 0;
- dest[8] = x2;
- dest[9] = y2;
- dest[10] = z2;
- dest[11] = 0;
- dest[12] = -(x0*eyex + x1*eyey + x2*eyez);
- dest[13] = -(y0*eyex + y1*eyey + y2*eyez);
- dest[14] = -(z0*eyex + z1*eyey + z2*eyez);
- dest[15] = 1;
+ dest[0] = mat[0]*x + mat[1]*y + mat[2]*z;
+ dest[1] = mat[3]*x + mat[4]*y + mat[5]*z;
+ dest[2] = mat[6]*x + mat[7]*y + mat[8]*z;
return dest;
};
+define("glMatrixAddon", ["glMatrix"], (function (global) {
+ return function () {
+ var ret, fn;
+ return ret || global.glMatrixAddon;
+ };
+}(this)));
+
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
+define('array_helper',[],function() {
+ "use strict";
+
+ return {
+ init : function(arr, val) {
+ var l = arr.length;
+ while (l--) {
+ arr[l] = val;
+ }
+ },
+
+ /**
+ * Shuffles the content of an array
+ * @return {Array} the array itself shuffled
+ */
+ shuffle : function(arr) {
+ var i = arr.length - 1, j, x;
+ for (i; i >= 0; i--) {
+ j = Math.floor(Math.random() * i);
+ x = arr[i];
+ arr[i] = arr[j];
+ arr[j] = x;
+ }
+ return arr;
+ },
+
+ toPointList : function(arr) {
+ var i, j, row = [], rows = [];
+ for ( i = 0; i < arr.length; i++) {
+ row = [];
+ for ( j = 0; j < arr[i].length; j++) {
+ row[j] = arr[i][j];
+ }
+ rows[i] = "[" + row.join(",") + "]";
+ }
+ return "[" + rows.join(",\r\n") + "]";
+ },
+
+ /**
+ * returns the elements which's score is bigger than the threshold
+ * @return {Array} the reduced array
+ */
+ threshold : function(arr, threshold, scoreFunc) {
+ var i, queue = [];
+ for ( i = 0; i < arr.length; i++) {
+ if (scoreFunc.apply(arr, [arr[i]]) >= threshold) {
+ queue.push(arr[i]);
+ }
+ }
+ return queue;
+ },
+
+ maxIndex : function(arr) {
+ var i, max = 0;
+ for ( i = 0; i < arr.length; i++) {
+ if (arr[i] > arr[max]) {
+ max = i;
+ }
+ }
+ return max;
+ },
+
+ max : function(arr) {
+ var i, max = 0;
+ for ( i = 0; i < arr.length; i++) {
+ if (arr[i] > max) {
+ max = arr[i];
+ }
+ }
+ return max;
+ },
+
+ sum: function(arr) {
+ var length = arr.length,
+ sum = 0;
+
+ while(length--) {
+ sum += arr[length];
+ }
+ return sum;
+ }
+ };
+});
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define, vec2, vec3 */
+
+define('cv_utils',['cluster', 'glMatrixAddon', "array_helper"], function(Cluster2, glMatrixAddon, ArrayHelper) {
+
+ "use strict";
+ /*
+ * cv_utils.js
+ * Collection of CV functions and libraries
+ */
+
+ /**
+ * Namespace for various CV alorithms
+ * @class Represents a collection of useful CV algorithms/functions
+ */
+
+ var CVUtils = {};
+
+ /**
+ * @param x x-coordinate
+ * @param y y-coordinate
+ * @return ImageReference {x,y} Coordinate
+ */
+ CVUtils.imageRef = function(x, y) {
+ var that = {
+ x : x,
+ y : y,
+ toVec2 : function() {
+ return vec2.create([this.x, this.y]);
+ },
+ toVec3 : function() {
+ return vec3.create([this.x, this.y, 1]);
+ },
+ round : function() {
+ this.x = this.x > 0.0 ? Math.floor(this.x + 0.5) : Math.floor(this.x - 0.5);
+ this.y = this.y > 0.0 ? Math.floor(this.y + 0.5) : Math.floor(this.y - 0.5);
+ return this;
+ }
+ };
+ return that;
+ };
-/*
- * mat4.str
- * Returns a string representation of a mat4
- *
- * Params:
- * mat - mat4 to represent as a string
- *
- * Returns:
- * string representation of mat
- */
-mat4.str = function(mat) {
- return '[' + mat[0] + ', ' + mat[1] + ', ' + mat[2] + ', ' + mat[3] +
- ',\n '+ mat[4] + ', ' + mat[5] + ', ' + mat[6] + ', ' + mat[7] +
- ',\n '+ mat[8] + ', ' + mat[9] + ', ' + mat[10] + ', ' + mat[11] +
- ',\n '+ mat[12] + ', ' + mat[13] + ', ' + mat[14] + ', ' + mat[15] + ']';
-};
+ /**
+ * Computes an integral image of a given grayscale image.
+ * @param imageDataContainer {ImageDataContainer} the image to be integrated
+ */
+ CVUtils.computeIntegralImage2 = function(imageWrapper, integralWrapper) {
+ var imageData = imageWrapper.data;
+ var width = imageWrapper.size.x;
+ var height = imageWrapper.size.y;
+ var integralImageData = integralWrapper.data;
+ var sum = 0, posA = 0, posB = 0, posC = 0, posD = 0, x, y;
-/*
- * quat4 - Quaternions
- */
-quat4 = {};
+ // sum up first column
+ posB = width;
+ sum = 0;
+ for ( y = 1; y < height; y++) {
+ sum += imageData[posA];
+ integralImageData[posB] += sum;
+ posA += width;
+ posB += width;
+ }
-/*
- * quat4.create
- * Creates a new instance of a quat4 using the default array type
- * Any javascript array containing at least 4 numeric elements can serve as a quat4
- *
- * Params:
- * quat - Optional, quat4 containing values to initialize with
- *
- * Returns:
- * New quat4
- */
-quat4.create = function(quat) {
- var dest;
-
- if(quat) {
- dest = new glMatrixArrayType(4);
- dest[0] = quat[0];
- dest[1] = quat[1];
- dest[2] = quat[2];
- dest[3] = quat[3];
- } else {
- if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0,0]);
- else
- dest = new glMatrixArrayType(4);
- }
-
- return dest;
-};
+ posA = 0;
+ posB = 1;
+ sum = 0;
+ for ( x = 1; x < width; x++) {
+ sum += imageData[posA];
+ integralImageData[posB] += sum;
+ posA++;
+ posB++;
+ }
-/*
- * quat4.set
- * Copies the values of one quat4 to another
- *
- * Params:
- * quat - quat4 containing values to copy
- * dest - quat4 receiving copied values
- *
- * Returns:
- * dest
- */
-quat4.set = function(quat, dest) {
- dest[0] = quat[0];
- dest[1] = quat[1];
- dest[2] = quat[2];
- dest[3] = quat[3];
-
- return dest;
-};
+ for ( y = 1; y < height; y++) {
+ posA = y * width + 1;
+ posB = (y - 1) * width + 1;
+ posC = y * width;
+ posD = (y - 1) * width;
+ for ( x = 1; x < width; x++) {
+ integralImageData[posA] += imageData[posA] + integralImageData[posB] + integralImageData[posC] - integralImageData[posD];
+ posA++;
+ posB++;
+ posC++;
+ posD++;
+ }
+ }
+ };
-/*
- * quat4.calculateW
- * Calculates the W component of a quat4 from the X, Y, and Z components.
- * Assumes that quaternion is 1 unit in length.
- * Any existing W component will be ignored.
- *
- * Params:
- * quat - quat4 to calculate W component of
- * dest - Optional, quat4 receiving calculated values. If not specified result is written to quat
- *
- * Returns:
- * dest if specified, quat otherwise
- */
-quat4.calculateW = function(quat, dest) {
- var x = quat[0], y = quat[1], z = quat[2];
+ CVUtils.computeIntegralImage = function(imageWrapper, integralWrapper) {
+ var imageData = imageWrapper.data;
+ var width = imageWrapper.size.x;
+ var height = imageWrapper.size.y;
+ var integralImageData = integralWrapper.data;
+ var sum = 0;
- if(!dest || quat == dest) {
- quat[3] = -Math.sqrt(Math.abs(1.0 - x*x - y*y - z*z));
- return quat;
- }
- dest[0] = x;
- dest[1] = y;
- dest[2] = z;
- dest[3] = -Math.sqrt(Math.abs(1.0 - x*x - y*y - z*z));
- return dest;
-};
+ // sum up first row
+ for (var i = 0; i < width; i++) {
+ sum += imageData[i];
+ integralImageData[i] = sum;
+ }
-/*
- * quat4.inverse
- * Calculates the inverse of a quat4
- *
- * Params:
- * quat - quat4 to calculate inverse of
- * dest - Optional, quat4 receiving inverse values. If not specified result is written to quat
- *
- * Returns:
- * dest if specified, quat otherwise
- */
-quat4.inverse = function(quat, dest) {
- if(!dest || quat == dest) {
- quat[0] *= -1;
- quat[1] *= -1;
- quat[2] *= -1;
- return quat;
- }
- dest[0] = -quat[0];
- dest[1] = -quat[1];
- dest[2] = -quat[2];
- dest[3] = quat[3];
- return dest;
-};
+ for (var v = 1; v < height; v++) {
+ sum = 0;
+ for (var u = 0; u < width; u++) {
+ sum += imageData[v * width + u];
+ integralImageData[((v) * width) + u] = sum + integralImageData[(v - 1) * width + u];
+ }
+ }
+ };
-/*
- * quat4.length
- * Calculates the length of a quat4
- *
- * Params:
- * quat - quat4 to calculate length of
- *
- * Returns:
- * Length of quat
- */
-quat4.length = function(quat) {
- var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
- return Math.sqrt(x*x + y*y + z*z + w*w);
-};
+ CVUtils.thresholdImage = function(imageWrapper, threshold, targetWrapper) {
+ if (!targetWrapper) {
+ targetWrapper = imageWrapper;
+ }
+ var imageData = imageWrapper.data, length = imageData.length, targetData = targetWrapper.data;
+
+ while (length--) {
+ targetData[length] = imageData[length] < threshold ? 1 : 0;
+ }
+ };
+
+ CVUtils.computeHistogram = function(imageWrapper, bitsPerPixel) {
+ if (!bitsPerPixel) {
+ bitsPerPixel = 8;
+ }
+ var imageData = imageWrapper.data,
+ length = imageData.length,
+ bitShift = 8 - bitsPerPixel,
+ bucketCnt = 1 << bitsPerPixel,
+ hist = new Int32Array(bucketCnt);
+
+ while (length--) {
+ hist[imageData[length] >> bitShift]++;
+ }
+ return hist;
+ };
+
+ CVUtils.sharpenLine = function(line) {
+ var i,
+ length = line.length,
+ left = line[0],
+ center = line[1],
+ right;
+
+ for (i = 1; i < length - 1; i++) {
+ right = line[i + 1];
+ // -1 4 -1 kernel
+ line[i-1] = (((center * 2) - left - right)) & 255;
+ left = center;
+ center = right;
+ }
+ return line;
+ };
+
+ CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) {
+ if (!bitsPerPixel) {
+ bitsPerPixel = 8;
+ }
+ var hist,
+ threshold,
+ bitShift = 8 - bitsPerPixel;
+
+ function px(init, end) {
+ var sum = 0, i;
+ for ( i = init; i <= end; i++) {
+ sum += hist[i];
+ }
+ return sum;
+ }
-/*
- * quat4.normalize
- * Generates a unit quaternion of the same direction as the provided quat4
- * If quaternion length is 0, returns [0, 0, 0, 0]
- *
- * Params:
- * quat - quat4 to normalize
- * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
- *
- * Returns:
- * dest if specified, quat otherwise
- */
-quat4.normalize = function(quat, dest) {
- if(!dest) { dest = quat; }
-
- var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
- var len = Math.sqrt(x*x + y*y + z*z + w*w);
- if(len == 0) {
- dest[0] = 0;
- dest[1] = 0;
- dest[2] = 0;
- dest[3] = 0;
- return dest;
- }
- len = 1/len;
- dest[0] = x * len;
- dest[1] = y * len;
- dest[2] = z * len;
- dest[3] = w * len;
-
- return dest;
-};
+ function mx(init, end) {
+ var i, sum = 0;
-/*
- * quat4.multiply
- * Performs a quaternion multiplication
- *
- * Params:
- * quat - quat4, first operand
- * quat2 - quat4, second operand
- * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
- *
- * Returns:
- * dest if specified, quat otherwise
- */
-quat4.multiply = function(quat, quat2, dest) {
- if(!dest) { dest = quat; }
-
- var qax = quat[0], qay = quat[1], qaz = quat[2], qaw = quat[3];
- var qbx = quat2[0], qby = quat2[1], qbz = quat2[2], qbw = quat2[3];
-
- dest[0] = qax*qbw + qaw*qbx + qay*qbz - qaz*qby;
- dest[1] = qay*qbw + qaw*qby + qaz*qbx - qax*qbz;
- dest[2] = qaz*qbw + qaw*qbz + qax*qby - qay*qbx;
- dest[3] = qaw*qbw - qax*qbx - qay*qby - qaz*qbz;
-
- return dest;
-};
+ for ( i = init; i <= end; i++) {
+ sum += i * hist[i];
+ }
-/*
- * quat4.multiplyVec3
- * Transforms a vec3 with the given quaternion
- *
- * Params:
- * quat - quat4 to transform the vector with
- * vec - vec3 to transform
- * dest - Optional, vec3 receiving operation result. If not specified result is written to vec
- *
- * Returns:
- * dest if specified, vec otherwise
- */
-quat4.multiplyVec3 = function(quat, vec, dest) {
- if(!dest) { dest = vec; }
-
- var x = vec[0], y = vec[1], z = vec[2];
- var qx = quat[0], qy = quat[1], qz = quat[2], qw = quat[3];
+ return sum;
+ }
- // calculate quat * vec
- var ix = qw*x + qy*z - qz*y;
- var iy = qw*y + qz*x - qx*z;
- var iz = qw*z + qx*y - qy*x;
- var iw = -qx*x - qy*y - qz*z;
-
- // calculate result * inverse quat
- dest[0] = ix*qw + iw*-qx + iy*-qz - iz*-qy;
- dest[1] = iy*qw + iw*-qy + iz*-qx - ix*-qz;
- dest[2] = iz*qw + iw*-qz + ix*-qy - iy*-qx;
-
- return dest;
-};
+ function determineThreshold() {
+ var vet = [0], p1, p2, p12, k, m1, m2, m12,
+ max = (1 << bitsPerPixel) - 1;
-/*
- * quat4.toMat3
- * Calculates a 3x3 matrix from the given quat4
- *
- * Params:
- * quat - quat4 to create matrix from
- * dest - Optional, mat3 receiving operation result
- *
- * Returns:
- * dest if specified, a new mat3 otherwise
- */
-quat4.toMat3 = function(quat, dest) {
- if(!dest) { dest = mat3.create(); }
-
- var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+ hist = CVUtils.computeHistogram(imageWrapper, bitsPerPixel);
+ for ( k = 1; k < max; k++) {
+ p1 = px(0, k);
+ p2 = px(k + 1, max);
+ p12 = p1 * p2;
+ if (p12 === 0) {
+ p12 = 1;
+ }
+ m1 = mx(0, k) * p2;
+ m2 = mx(k + 1, max) * p1;
+ m12 = m1 - m2;
+ vet[k] = m12 * m12 / p12;
+ }
+ return ArrayHelper.maxIndex(vet);
+ }
- var x2 = x + x;
- var y2 = y + y;
- var z2 = z + z;
+ threshold = determineThreshold();
+ return threshold << bitShift;
+ };
- var xx = x*x2;
- var xy = x*y2;
- var xz = x*z2;
+ CVUtils.otsuThreshold = function(imageWrapper, targetWrapper) {
+ var threshold = CVUtils.determineOtsuThreshold(imageWrapper);
- var yy = y*y2;
- var yz = y*z2;
- var zz = z*z2;
+ CVUtils.thresholdImage(imageWrapper, threshold, targetWrapper);
+ return threshold;
+ };
- var wx = w*x2;
- var wy = w*y2;
- var wz = w*z2;
+ // local thresholding
+ CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapper) {
+ CVUtils.computeIntegralImage(imageWrapper, integralWrapper);
- dest[0] = 1 - (yy + zz);
- dest[1] = xy - wz;
- dest[2] = xz + wy;
+ if (!targetWrapper) {
+ targetWrapper = imageWrapper;
+ }
+ var imageData = imageWrapper.data;
+ var targetData = targetWrapper.data;
+ var width = imageWrapper.size.x;
+ var height = imageWrapper.size.y;
+ var integralImageData = integralWrapper.data;
+ var sum = 0, v, u, kernel = 3, A, B, C, D, avg, size = (kernel * 2 + 1) * (kernel * 2 + 1);
- dest[3] = xy + wz;
- dest[4] = 1 - (xx + zz);
- dest[5] = yz - wx;
+ // clear out top & bottom-border
+ for ( v = 0; v <= kernel; v++) {
+ for ( u = 0; u < width; u++) {
+ targetData[((v) * width) + u] = 0;
+ targetData[(((height - 1) - v) * width) + u] = 0;
+ }
+ }
- dest[6] = xz - wy;
- dest[7] = yz + wx;
- dest[8] = 1 - (xx + yy);
-
- return dest;
-};
+ // clear out left & right border
+ for ( v = kernel; v < height - kernel; v++) {
+ for ( u = 0; u <= kernel; u++) {
+ targetData[((v) * width) + u] = 0;
+ targetData[((v) * width) + (width - 1 - u)] = 0;
+ }
+ }
-/*
- * quat4.toMat4
- * Calculates a 4x4 matrix from the given quat4
- *
- * Params:
- * quat - quat4 to create matrix from
- * dest - Optional, mat4 receiving operation result
- *
- * Returns:
- * dest if specified, a new mat4 otherwise
- */
-quat4.toMat4 = function(quat, dest) {
- if(!dest) { dest = mat4.create(); }
-
- var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+ for ( v = kernel + 1; v < height - kernel - 1; v++) {
+ for ( u = kernel + 1; u < width - kernel; u++) {
+ A = integralImageData[(v - kernel - 1) * width + (u - kernel - 1)];
+ B = integralImageData[(v - kernel - 1) * width + (u + kernel)];
+ C = integralImageData[(v + kernel) * width + (u - kernel - 1)];
+ D = integralImageData[(v + kernel) * width + (u + kernel)];
+ sum = D - C - B + A;
+ avg = sum / (size);
+ targetData[v * width + u] = imageData[v * width + u] > (avg + 5) ? 0 : 1;
+ }
+ }
+ };
- var x2 = x + x;
- var y2 = y + y;
- var z2 = z + z;
+ CVUtils.cluster = function(points, threshold, property) {
+ var i, k, cluster, point, clusters = [];
- var xx = x*x2;
- var xy = x*y2;
- var xz = x*z2;
+ if (!property) {
+ property = "rad";
+ }
- var yy = y*y2;
- var yz = y*z2;
- var zz = z*z2;
+ function addToCluster(point) {
+ var found = false;
+ for ( k = 0; k < clusters.length; k++) {
+ cluster = clusters[k];
+ if (cluster.fits(point)) {
+ cluster.add(point);
+ found = true;
+ }
+ }
+ return found;
+ }
- var wx = w*x2;
- var wy = w*y2;
- var wz = w*z2;
+ // iterate over each cloud
+ for ( i = 0; i < points.length; i++) {
+ point = Cluster2.createPoint(points[i], i, property);
+ if (!addToCluster(point)) {
+ clusters.push(Cluster2.create(point, threshold));
+ }
+ }
+
+ return clusters;
+
+ };
+
+ CVUtils.Tracer = {
+ trace : function(points, vec) {
+ var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0;
- dest[0] = 1 - (yy + zz);
- dest[1] = xy - wz;
- dest[2] = xz + wy;
- dest[3] = 0;
+ function trace(idx, forward) {
+ var from, to, toIdx, predictedPos, thresholdX = 1, thresholdY = Math.abs(vec[1] / 10), found = false;
- dest[4] = xy + wz;
- dest[5] = 1 - (xx + zz);
- dest[6] = yz - wx;
- dest[7] = 0;
+ function match(pos, predicted) {
+ if (pos.x > (predicted.x - thresholdX) && pos.x < (predicted.x + thresholdX) && pos.y > (predicted.y - thresholdY) && pos.y < (predicted.y + thresholdY)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
- dest[8] = xz - wy;
- dest[9] = yz + wx;
- dest[10] = 1 - (xx + yy);
- dest[11] = 0;
+ // check if the next index is within the vec specifications
+ // if not, check as long as the threshold is met
- dest[12] = 0;
- dest[13] = 0;
- dest[14] = 0;
- dest[15] = 1;
-
- return dest;
-};
+ from = points[idx];
+ if (forward) {
+ predictedPos = {
+ x : from.x + vec[0],
+ y : from.y + vec[1]
+ };
+ } else {
+ predictedPos = {
+ x : from.x - vec[0],
+ y : from.y - vec[1]
+ };
+ }
-/*
- * quat4.slerp
- * Performs a spherical linear interpolation between two quat4
- *
- * Params:
- * quat - quat4, first quaternion
- * quat2 - quat4, second quaternion
- * slerp - interpolation amount between the two inputs
- * dest - Optional, quat4 receiving operation result. If not specified result is written to quat
- *
- * Returns:
- * dest if specified, quat otherwise
- */
-quat4.slerp = function(quat, quat2, slerp, dest) {
- if(!dest) { dest = quat; }
-
- var cosHalfTheta = quat[0]*quat2[0] + quat[1]*quat2[1] + quat[2]*quat2[2] + quat[3]*quat2[3];
-
- if (Math.abs(cosHalfTheta) >= 1.0){
- if(dest != quat) {
- dest[0] = quat[0];
- dest[1] = quat[1];
- dest[2] = quat[2];
- dest[3] = quat[3];
- }
- return dest;
- }
-
- var halfTheta = Math.acos(cosHalfTheta);
- var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta*cosHalfTheta);
+ toIdx = forward ? idx + 1 : idx - 1;
+ to = points[toIdx];
+ while (to && ( found = match(to, predictedPos)) !== true && (Math.abs(to.y - from.y) < vec[1])) {
+ toIdx = forward ? toIdx + 1 : toIdx - 1;
+ to = points[toIdx];
+ }
- if (Math.abs(sinHalfTheta) < 0.001){
- dest[0] = (quat[0]*0.5 + quat2[0]*0.5);
- dest[1] = (quat[1]*0.5 + quat2[1]*0.5);
- dest[2] = (quat[2]*0.5 + quat2[2]*0.5);
- dest[3] = (quat[3]*0.5 + quat2[3]*0.5);
- return dest;
- }
-
- var ratioA = Math.sin((1 - slerp)*halfTheta) / sinHalfTheta;
- var ratioB = Math.sin(slerp*halfTheta) / sinHalfTheta;
-
- dest[0] = (quat[0]*ratioA + quat2[0]*ratioB);
- dest[1] = (quat[1]*ratioA + quat2[1]*ratioB);
- dest[2] = (quat[2]*ratioA + quat2[2]*ratioB);
- dest[3] = (quat[3]*ratioA + quat2[3]*ratioB);
-
- return dest;
-};
+ return found ? toIdx : null;
+ }
+ for ( iteration = 0; iteration < maxIterations; iteration++) {
+ // randomly select point to start with
+ centerPos = Math.floor(Math.random() * points.length);
-/*
- * quat4.str
- * Returns a string representation of a quaternion
- *
- * Params:
- * quat - quat4 to represent as a string
- *
- * Returns:
- * string representation of quat
- */
-quat4.str = function(quat) {
- return '[' + quat[0] + ', ' + quat[1] + ', ' + quat[2] + ', ' + quat[3] + ']';
-};
+ // trace forward
+ top = [];
+ currentPos = centerPos;
+ top.push(points[currentPos]);
+ while (( currentPos = trace(currentPos, true)) !== null) {
+ top.push(points[currentPos]);
+ }
+ if (centerPos > 0) {
+ currentPos = centerPos;
+ while (( currentPos = trace(currentPos, false)) !== null) {
+ top.push(points[currentPos]);
+ }
+ }
+
+ if (top.length > result.length) {
+ result = top;
+ }
+ }
+ return result;
-define("glMatrix", ["typedefs"], (function (global) {
- return function () {
- var ret, fn;
- return ret || global.glMatrix;
+ }
};
-}(this)));
-
-/*
- * glMatrixAddon.js
- * Extension to the glMatrix library. The original glMatrix library
- * was created by Brandon Jones.
- */
+ CVUtils.DILATE = 1;
+ CVUtils.ERODE = 2;
-mat4.xVec4 = function(mat, vec, dest){
- if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
-
- dest[0] = mat[0]*x + mat[1]*y + mat[2]*z + mat[3]*w;
- dest[1] = mat[4]*x + mat[5]*y + mat[6]*z + mat[7]*w;
- dest[2] = mat[8]*x + mat[9]*y + mat[10]*z + mat[11]*w;
- dest[3] = mat[12]*x + mat[13]*y + mat[14]*z + mat[15]*w;
-
- return dest;
-};
+ CVUtils.dilate = function(inImageWrapper, outImageWrapper) {
+ var v, u, inImageData = inImageWrapper.data, outImageData = outImageWrapper.data, height = inImageWrapper.size.y, width = inImageWrapper.size.x, sum, yStart1, yStart2, xStart1, xStart2;
-mat3.scale = function(mat, scalar, dest){
- if(!dest || mat == dest) {
- mat[0] *= scalar;
- mat[1] *= scalar;
- mat[2] *= scalar;
- mat[3] *= scalar;
- mat[4] *= scalar;
- mat[5] *= scalar;
- mat[6] *= scalar;
- mat[7] *= scalar;
- mat[8] *= scalar;
- return mat;
- }
- dest = mat3.create();
- dest[0] = mat[0]*scalar;
- dest[1] = mat[1]*scalar;
- dest[2] = mat[2]*scalar;
- dest[3] = mat[3]*scalar;
- dest[4] = mat[4]*scalar;
- dest[5] = mat[5]*scalar;
- dest[6] = mat[6]*scalar;
- dest[7] = mat[7]*scalar;
- dest[8] = mat[8]*scalar;
- return dest;
-};
+ for ( v = 1; v < height - 1; v++) {
+ for ( u = 1; u < width - 1; u++) {
+ yStart1 = v - 1;
+ yStart2 = v + 1;
+ xStart1 = u - 1;
+ xStart2 = u + 1;
+ sum = inImageData[yStart1 * width + xStart1]/* + inImageData[yStart1*width+u] */ + inImageData[yStart1 * width + xStart2] +
+ /* inImageData[v*width+xStart1] + */
+ inImageData[v * width + u] + /* inImageData[v*width+xStart2] +*/
+ inImageData[yStart2 * width + xStart1]/* + inImageData[yStart2*width+u]*/ + inImageData[yStart2 * width + xStart2];
+ outImageData[v * width + u] = sum > 0 ? 1 : 0;
+ }
+ }
+ };
-mat3.inverse = function(mat, dest){
- if(!dest) { dest = mat; }
-
- var ha00 = mat[0], ha01 = mat[1], ha02 = mat[2];
- var ha10 = mat[3], ha11 = mat[4], ha12 = mat[5];
- var ha20 = mat[6], ha21 = mat[7], ha22 = mat[8];
-
- var invDetA = 1/(ha00*ha11*ha22 + ha01*ha12*ha20 + ha02*ha10*ha21 - ha02*ha11*ha20 - ha01*ha10*ha22 - ha00*ha12*ha21);
- dest[0] = (ha11*ha22 - ha12*ha21)*invDetA;
- dest[1] = (ha02*ha21 - ha01*ha22)*invDetA;
- dest[2] = (ha01*ha12 - ha02*ha11)*invDetA;
- dest[3] = (ha12*ha20 - ha10*ha22)*invDetA;
- dest[4] = (ha00*ha22 - ha02*ha20)*invDetA;
- dest[5] = (ha02*ha10 - ha00*ha12)*invDetA;
- dest[6] = (ha10*ha21 - ha11*ha20)*invDetA;
- dest[7] = (ha01*ha20 - ha00*ha21)*invDetA;
- dest[8] = (ha00*ha11 - ha01*ha10)*invDetA;
- return dest;
-};
+ CVUtils.erode = function(inImageWrapper, outImageWrapper) {
+ var v, u, inImageData = inImageWrapper.data, outImageData = outImageWrapper.data, height = inImageWrapper.size.y, width = inImageWrapper.size.x, sum, yStart1, yStart2, xStart1, xStart2;
-mat3.multiply = function(mat, mat2, dest) {
- if(!dest) { dest = mat; }
-
- var ha00 = mat[0], ha01 = mat[1], ha02 = mat[2];
- var ha10 = mat[3], ha11 = mat[4], ha12 = mat[5];
- var ha20 = mat[6], ha21 = mat[7], ha22 = mat[8];
-
- var hb00 = mat2[0], hb01 = mat2[1], hb02 = mat2[2];
- var hb10 = mat2[3], hb11 = mat2[4], hb12 = mat2[5];
- var hb20 = mat2[6], hb21 = mat2[7], hb22 = mat2[8];
-
- dest[0] = ha00*hb00 + ha01*hb10 + ha02*hb20;
- dest[1] = ha00*hb01 + ha01*hb11 + ha02*hb21;
- dest[2] = ha00*hb02 + ha01*hb12 + ha02*hb22;
-
- dest[3] = ha10*hb00 + ha11*hb10 + ha12*hb20;
- dest[4] = ha10*hb01 + ha11*hb11 + ha12*hb21;
- dest[5] = ha10*hb02 + ha11*hb12 + ha12*hb22;
-
- dest[6] = ha20*hb00 + ha21*hb10 + ha22*hb20;
- dest[7] = ha20*hb01 + ha21*hb11 + ha22*hb21;
- dest[8] = ha20*hb02 + ha21*hb12 + ha22*hb22;
- return dest;
-};
+ for ( v = 1; v < height - 1; v++) {
+ for ( u = 1; u < width - 1; u++) {
+ yStart1 = v - 1;
+ yStart2 = v + 1;
+ xStart1 = u - 1;
+ xStart2 = u + 1;
+ sum = inImageData[yStart1 * width + xStart1]/* + inImageData[yStart1*width+u] */ + inImageData[yStart1 * width + xStart2] +
+ /* inImageData[v*width+xStart1] + */
+ inImageData[v * width + u] + /* inImageData[v*width+xStart2] +*/
+ inImageData[yStart2 * width + xStart1]/* + inImageData[yStart2*width+u]*/ + inImageData[yStart2 * width + xStart2];
+ outImageData[v * width + u] = sum === 5 ? 1 : 0;
+ }
+ }
+ };
-mat3.xVec3 = function(mat, vec, dest){
- if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1], z = vec[2];
-
- dest[0] = mat[0]*x + mat[1]*y + mat[2]*z;
- dest[1] = mat[3]*x + mat[4]*y + mat[5]*z;
- dest[2] = mat[6]*x + mat[7]*y + mat[8]*z;
-
- return dest;
-};
+ CVUtils.subtract = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
+ if (!resultImageWrapper) {
+ resultImageWrapper = aImageWrapper;
+ }
+ var length = aImageWrapper.data.length, aImageData = aImageWrapper.data, bImageData = bImageWrapper.data, cImageData = resultImageWrapper.data;
-var vec4={};
+ while (length--) {
+ cImageData[length] = aImageData[length] - bImageData[length];
+ }
+ };
-vec4.create = function(vec){
- var dest;
-
- if(vec) {
- dest = new glMatrixArrayType(4);
- dest[0] = vec[0];
- dest[1] = vec[1];
- dest[2] = vec[2];
- dest[3] = vec[3];
- } else {
- if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0,0]);
- else
- dest = new glMatrixArrayType(4);
- }
-
- return dest;
-};
+ CVUtils.bitwiseOr = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
+ if (!resultImageWrapper) {
+ resultImageWrapper = aImageWrapper;
+ }
+ var length = aImageWrapper.data.length, aImageData = aImageWrapper.data, bImageData = bImageWrapper.data, cImageData = resultImageWrapper.data;
-vec4.project = function(vec, dest){
- if(!dest) { dest = vec; }
-
- dest[0] = vec[0]/vec[3];
- dest[1] = vec[1]/vec[3];
- dest[2] = vec[2]/vec[3];
- return dest;
-};
+ while (length--) {
+ cImageData[length] = aImageData[length] || bImageData[length];
+ }
+ };
-vec4.scale = function(vec, val, dest){
- if(!dest || vec == dest) {
- vec[0] *= val;
- vec[1] *= val;
- vec[2] *= val;
- vec[4] *= val;
- return vec;
- }
-
- dest[0] = vec[0]*val;
- dest[1] = vec[1]*val;
- dest[2] = vec[2]*val;
- dest[3] = vec[3]*val;
- return dest;
-};
+ CVUtils.countNonZero = function(imageWrapper) {
+ var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0;
-vec4.xMat4 = function(vec, mat, dest){
- if(!dest) { dest = vec; }
-
- var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
-
- dest[0] = mat[0]*x + mat[4]*y + mat[8]*z + mat[12]*w;
- dest[1] = mat[1]*x + mat[5]*y + mat[9]*z + mat[13]*w;
- dest[2] = mat[2]*x + mat[6]*y + mat[10]*z + mat[14]*w;
- dest[3] = mat[3]*x + mat[7]*y + mat[11]*z + mat[15]*w;
-
- return dest;
-};
+ while (length--) {
+ sum += data[length];
+ }
+ return sum;
+ };
+ CVUtils.topGeneric = function(list, top, scoreFunc) {
+ var i, minIdx = 0, min = 0, queue = [], score, hit, pos;
-var mat2 = {};
+ for ( i = 0; i < top; i++) {
+ queue[i] = {
+ score : 0,
+ item : null
+ };
+ }
-mat2.create = function(mat){
- var dest;
-
- if(mat) {
- dest = new glMatrixArrayType(4);
- dest[0] = mat[0];
- dest[1] = mat[1];
- dest[2] = mat[2];
- dest[3] = mat[3];
- } else {
- if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0,0,0]);
- else
- dest = new glMatrixArrayType(4);
- }
-
- return dest;
-};
+ for ( i = 0; i < list.length; i++) {
+ score = scoreFunc.apply(this, [list[i]]);
+ if (score > min) {
+ hit = queue[minIdx];
+ hit.score = score;
+ hit.item = list[i];
+ min = Number.MAX_VALUE;
+ for ( pos = 0; pos < top; pos++) {
+ if (queue[pos].score < min) {
+ min = queue[pos].score;
+ minIdx = pos;
+ }
+ }
+ }
+ }
-mat2.xVec2 = function(mat, vec, dest){
- if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1];
-
- dest[0] = mat[0]*x + mat[1]*y;
- dest[1] = mat[2]*x + mat[3]*y;
-
- return dest;
-};
+ return queue;
+ };
-mat2.scale = function(mat, scale, dest){
- if(!dest || mat == dest) {
- mat[0] *= scale;
- mat[1] *= scale;
- mat[2] *= scale;
- mat[3] *= scale;
- return mat;
- }
-
- dest[0] = mat[0]*scale;
- dest[1] = mat[1]*scale;
- dest[2] = mat[2]*scale;
- dest[3] = mat[3]*scale;
- return dest;
-};
+ CVUtils.grayArrayFromImage = function(htmlImage, offsetX, ctx, array) {
+ ctx.drawImage(htmlImage, offsetX, 0, htmlImage.width, htmlImage.height);
+ var ctxData = ctx.getImageData(offsetX, 0, htmlImage.width, htmlImage.height).data;
+ CVUtils.computeGray(ctxData, array);
+ };
-mat2.determinant = function(mat){
- return mat[0]*mat[3] - mat[1]*mat[2];
-};
+ CVUtils.grayArrayFromContext = function(ctx, size, offset, array) {
+ var ctxData = ctx.getImageData(offset.x, offset.y, size.x, size.y).data;
+ CVUtils.computeGray(ctxData, array);
+ };
-mat2.inverse = function(mat){
- var scale = 1/(mat2.determinant(mat));
- var a = mat[3]*scale,
- b = -mat[1]*scale,
- c = -mat[2]*scale,
- d = mat[0];
- mat[0] = a;
- mat[1] = b;
- mat[2] = c;
- mat[3] = d;
- return mat;
-};
+ CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
+ var topRowIdx = 0;
+ var bottomRowIdx = size.x;
+ var endIdx = Math.floor(canvasData.length / 4);
+ var outWidth = size.x / 2;
+ var outImgIdx = 0;
+ var inWidth = size.x;
+ var i;
-var vec2 = {};
-vec2.create = function(vec){
- var dest;
-
- if(vec) {
- dest = new glMatrixArrayType(2);
- dest[0] = vec[0];
- dest[1] = vec[1];
- } else {
- if(glMatrixArrayType === Array)
- dest = new glMatrixArrayType([0,0]);
- else
- dest = new glMatrixArrayType(2);
- }
-
- return dest;
-};
+ while (bottomRowIdx < endIdx) {
+ for ( i = 0; i < outWidth; i++) {
+ outArray[outImgIdx] = Math.floor(((0.299 * canvasData[topRowIdx * 4 + 0] + 0.587 * canvasData[topRowIdx * 4 + 1] + 0.114 * canvasData[topRowIdx * 4 + 2]) + (0.299 * canvasData[(topRowIdx + 1) * 4 + 0] + 0.587 * canvasData[(topRowIdx + 1) * 4 + 1] + 0.114 * canvasData[(topRowIdx + 1) * 4 + 2]) + (0.299 * canvasData[(bottomRowIdx) * 4 + 0] + 0.587 * canvasData[(bottomRowIdx) * 4 + 1] + 0.114 * canvasData[(bottomRowIdx) * 4 + 2]) + (0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] + 0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] + 0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4);
+ outImgIdx++;
+ topRowIdx = topRowIdx + 2;
+ bottomRowIdx = bottomRowIdx + 2;
+ }
+ topRowIdx = topRowIdx + inWidth;
+ bottomRowIdx = bottomRowIdx + inWidth;
+ }
-vec2.subtract = function(vec, vec2, dest) {
- if(!dest || vec == dest) {
- vec[0] -= vec2[0];
- vec[1] -= vec2[1];
- return vec;
- }
-
- dest[0] = vec[0] - vec2[0];
- dest[1] = vec[1] - vec2[1];
- return dest;
-};
+ };
-vec2.add = function(vec, vec2, dest) {
- if(!dest || vec == dest) {
- vec[0] += vec2[0];
- vec[1] += vec2[1];
- return vec;
- }
-
- dest[0] = vec[0] + vec2[0];
- dest[1] = vec[1] + vec2[1];
- return dest;
-};
+ CVUtils.computeGray = function(imageData, outArray, config) {
+ var l = (imageData.length / 4) | 0,
+ i,
+ singleChannel = config && config.singleChannel === true;
-vec2.scale = function(vec, val, dest) {
- if(!dest || vec == dest) {
- vec[0] *= val;
- vec[1] *= val;
- return vec;
- }
-
- dest[0] = vec[0]*val;
- dest[1] = vec[1]*val;
- return dest;
-};
+ if (singleChannel) {
+ for (i = 0; i < l; i++) {
+ outArray[i] = imageData[i * 4 + 0];
+ }
+ } else {
+ for (i = 0; i < l; i++) {
+ outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
+ }
+ }
+ };
-vec2.normalize = function(vec, dest) {
- if(!dest) { dest = vec; }
-
- var x = vec[0], y = vec[1];
- var len = Math.sqrt(x*x + y*y);
-
- if (!len) {
- dest[0] = 0;
- dest[1] = 0;
- return dest;
- } else if (len == 1) {
- dest[0] = x;
- dest[1] = y;
- return dest;
- }
-
- len = 1 / len;
- dest[0] = x*len;
- dest[1] = y*len;
- return dest;
-};
+ CVUtils.loadImageArray = function(src, callback, canvas) {
+ if (!canvas)
+ canvas = document.createElement('canvas');
+ var img = new Image();
+ img.callback = callback;
+ img.onload = function() {
+ canvas.width = this.width;
+ canvas.height = this.height;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(this, 0, 0);
+ var array = new Uint8Array(this.width * this.height);
+ ctx.drawImage(this, 0, 0);
+ var data = ctx.getImageData(0, 0, this.width, this.height).data;
+ CVUtils.computeGray(data, array);
+ this.callback(array, {
+ x : this.width,
+ y : this.height
+ }, this);
+ };
+ img.src = src;
+ };
-vec2.dot = function(vec, vec2){
- return vec[0]*vec2[0] + vec[1]*vec2[1];
-};
+ /**
+ * @param inImg {ImageWrapper} input image to be sampled
+ * @param outImg {ImageWrapper} to be stored in
+ */
+ CVUtils.halfSample = function(inImgWrapper, outImgWrapper) {
+ var inImg = inImgWrapper.data;
+ var inWidth = inImgWrapper.size.x;
+ var outImg = outImgWrapper.data;
+ var topRowIdx = 0;
+ var bottomRowIdx = inWidth;
+ var endIdx = inImg.length;
+ var outWidth = inWidth / 2;
+ var outImgIdx = 0;
+ while (bottomRowIdx < endIdx) {
+ for (var i = 0; i < outWidth; i++) {
+ outImg[outImgIdx] = Math.floor((inImg[topRowIdx] + inImg[topRowIdx + 1] + inImg[bottomRowIdx] + inImg[bottomRowIdx + 1]) / 4);
+ outImgIdx++;
+ topRowIdx = topRowIdx + 2;
+ bottomRowIdx = bottomRowIdx + 2;
+ }
+ topRowIdx = topRowIdx + inWidth;
+ bottomRowIdx = bottomRowIdx + inWidth;
+ }
+ };
-vec2.multiply = function(vec, vec2, dest){
- if(!dest) { dest = vec; }
-
- dest[0] = vec[0]*vec2[0];
- dest[1] = vec[1]*vec2[1];
- return dest;
-};
+ CVUtils.hsv2rgb = function(hsv, rgb) {
+ var h = hsv[0], s = hsv[1], v = hsv[2], c = v * s, x = c * (1 - Math.abs((h / 60) % 2 - 1)), m = v - c, r = 0, g = 0, b = 0;
+ rgb = rgb || [0, 0, 0];
-/**
- * @param vec vec2 to be unprojected [x,y] -> [x,y,1]
- * @returns vec3 unprojected vector
- */
-vec2.unproject = function(vec){
- return vec3.create([vec[0], vec[1], 1]);
-};
+ if (h < 60) {
+ r = c;
+ g = x;
+ } else if (h < 120) {
+ r = x;
+ g = c;
+ } else if (h < 180) {
+ g = c;
+ b = x;
+ } else if (h < 240) {
+ g = x;
+ b = c;
+ } else if (h < 300) {
+ r = x;
+ b = c;
+ } else if (h < 360) {
+ r = c;
+ b = x;
+ }
+ rgb[0] = ((r + m) * 255) | 0;
+ rgb[1] = ((g + m) * 255) | 0;
+ rgb[2] = ((b + m) * 255) | 0;
+ return rgb;
+ };
-vec2.length = function(vec){
- return Math.sqrt(vec[0]*vec[0] + vec[1]*vec[1]);
-};
+ CVUtils._computeDivisors = function(n) {
+ var largeDivisors = [],
+ divisors = [],
+ i;
-vec2.perspectiveProject = function(vec){
- var result = vec2.create(vec);
- return vec2.scale(result, 1/vec[2]);
-};
+ for (i = 1; i < Math.sqrt(n) + 1; i++) {
+ if (n % i === 0) {
+ divisors.push(i);
+ if (i !== n/i) {
+ largeDivisors.unshift(Math.floor(n/i));
+ }
+ }
+ }
+ return divisors.concat(largeDivisors);
+ };
-/**
- * @param vec vec3 to be projected [x,y,z] -> [x/z,y/z]
- * @returns vec2 projected vector
- */
-vec3.project = function(vec){
- return vec2.scale(vec2.create(vec), 1/vec[2]);
-};
+ CVUtils._computeIntersection = function(arr1, arr2) {
+ var i = 0,
+ j = 0,
+ result = [];
-var vec6 = {};
-vec6.scale = function(vec, val, dest){
- if(!dest || vec == dest) {
- vec[0] *= val;
- vec[1] *= val;
- vec[2] *= val;
- vec[3] *= val;
- vec[4] *= val;
- vec[5] *= val;
- return vec;
- }
-
- dest[0] = vec[0]*val;
- dest[1] = vec[1]*val;
- dest[2] = vec[2]*val;
- dest[3] = vec[3]*val;
- dest[4] = vec[4]*val;
- dest[5] = vec[5]*val;
- return dest;
-};
+ while (i < arr1.length && j < arr2.length) {
+ if (arr1[i] === arr2[j]) {
+ result.push(arr1[i]);
+ i++;
+ j++;
+ } else if (arr1[i] > arr2[j]) {
+ j++;
+ } else {
+ i++;
+ }
+ }
+ return result;
+ };
-vec6.subtract = function(vec, vec2, dest){
- if(!dest || vec == dest) {
- vec[0] -= vec2[0];
- vec[1] -= vec2[1];
- vec[2] -= vec2[2];
- vec[3] -= vec2[3];
- vec[4] -= vec2[4];
- vec[5] -= vec2[5];
- return vec;
- }
-
- dest[0] = vec[0] - vec2[0];
- dest[1] = vec[1] - vec2[1];
- dest[2] = vec[2] - vec2[2];
- dest[3] = vec[3] - vec2[3];
- dest[4] = vec[4] - vec2[4];
- dest[5] = vec[5] - vec2[5];
- return dest;
-};
+ CVUtils.calculatePatchSize = function(patchSize, imgSize) {
+ var divisorsX = this._computeDivisors(imgSize.x),
+ divisorsY = this._computeDivisors(imgSize.y),
+ wideSide = Math.max(imgSize.x, imgSize.y),
+ common = this._computeIntersection(divisorsX, divisorsY),
+ nrOfPatchesList = [8, 10, 15, 20, 32, 60, 80],
+ nrOfPatchesMap = {
+ "x-small": 5,
+ "small": 4,
+ "medium": 3,
+ "large": 2,
+ "x-large": 1
+ },
+ nrOfPatchesIdx = nrOfPatchesMap[patchSize] || nrOfPatchesMap.medium,
+ nrOfPatches = nrOfPatchesList[nrOfPatchesIdx],
+ desiredPatchSize = Math.floor(wideSide/nrOfPatches),
+ optimalPatchSize;
-vec6.dot = function(vec, vec2){
- return vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2] + vec[3]*vec2[3] + vec[4]*vec2[4] + vec[5]*vec2[5];
-};
+ function findPatchSizeForDivisors(divisors) {
+ var i = 0,
+ found = divisors[Math.floor(divisors.length/2)];
-var mat6 = {};
-mat6.xVec6 = function(mat, vec, dest){
- if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1], z = vec[2], u = vec[3], w = vec[4], v = vec[5];
-
- dest[0] = mat[0]*x + mat[1]*y + mat[2]*z + mat[3]*u + mat[4]*w + mat[5]*v;
- dest[1] = mat[6]*x + mat[7]*y + mat[8]*z + mat[9]*u + mat[10]*w + mat[11]*v;
- dest[2] = mat[12]*x + mat[13]*y + mat[14]*z + mat[15]*u + mat[16]*w + mat[17]*v;
- dest[3] = mat[18]*x + mat[19]*y + mat[20]*z + mat[21]*u + mat[22]*w + mat[23]*v;
- dest[4] = mat[24]*x + mat[25]*y + mat[26]*z + mat[27]*u + mat[28]*w + mat[29]*v;
- dest[5] = mat[30]*x + mat[31]*y + mat[32]*z + mat[33]*u + mat[34]*w + mat[35]*v;
-
- return dest;
-};
+ while(i < (divisors.length - 1) && divisors[i] < desiredPatchSize) {
+ i++;
+ }
+ if (i > 0) {
+ if (Math.abs(divisors[i] - desiredPatchSize) > Math.abs(divisors[i-1] - desiredPatchSize)) {
+ found = divisors[i-1];
+ } else {
+ found = divisors[i];
+ }
+ }
+ if (desiredPatchSize / found < nrOfPatchesList[nrOfPatchesIdx+1] / nrOfPatchesList[nrOfPatchesIdx] &&
+ desiredPatchSize / found > nrOfPatchesList[nrOfPatchesIdx-1]/nrOfPatchesList[nrOfPatchesIdx] ) {
+ return {x: found, y: found};
+ }
+ return null;
+ }
-mat3.xVec3 = function(mat, vec, dest){
- if(!dest) { dest = vec; }
- var x = vec[0], y = vec[1], z = vec[2];
-
- dest[0] = mat[0]*x + mat[1]*y + mat[2]*z;
- dest[1] = mat[3]*x + mat[4]*y + mat[5]*z;
- dest[2] = mat[6]*x + mat[7]*y + mat[8]*z;
-
- return dest;
-};
-define("glMatrixAddon", ["glMatrix"], (function (global) {
- return function () {
- var ret, fn;
- return ret || global.glMatrixAddon;
+ optimalPatchSize = findPatchSizeForDivisors(common);
+ if (!optimalPatchSize) {
+ optimalPatchSize = findPatchSizeForDivisors(this._computeDivisors(wideSide));
+ if (!optimalPatchSize) {
+ optimalPatchSize = findPatchSizeForDivisors((this._computeDivisors(desiredPatchSize * nrOfPatches)));
+ }
+ }
+ return optimalPatchSize;
};
-}(this)));
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+ CVUtils._parseCSSDimensionValues = function(value) {
+ var dimension = {
+ value: parseFloat(value),
+ unit: value.indexOf("%") === value.length-1 ? "%" : "%"
+ };
-define('array_helper',[],function() {
- "use strict";
+ return dimension;
+ };
- return {
- init : function(arr, val) {
- var l = arr.length;
- while (l--) {
- arr[l] = val;
+ CVUtils._dimensionsConverters = {
+ top: function(dimension, context) {
+ if (dimension.unit === "%") {
+ return Math.floor(context.height * (dimension.value / 100));
}
},
-
- /**
- * Shuffles the content of an array
- * @return {Array} the array itself shuffled
- */
- shuffle : function(arr) {
- var i = arr.length - 1, j, x;
- for (i; i >= 0; i--) {
- j = Math.floor(Math.random() * i);
- x = arr[i];
- arr[i] = arr[j];
- arr[j] = x;
+ right: function(dimension, context) {
+ if (dimension.unit === "%") {
+ return Math.floor(context.width - (context.width * (dimension.value / 100)));
}
- return arr;
},
-
- toPointList : function(arr) {
- var i, j, row = [], rows = [];
- for ( i = 0; i < arr.length; i++) {
- row = [];
- for ( j = 0; j < arr[i].length; j++) {
- row[j] = arr[i][j];
- }
- rows[i] = "[" + row.join(",") + "]";
+ bottom: function(dimension, context) {
+ if (dimension.unit === "%") {
+ return Math.floor(context.height - (context.height * (dimension.value / 100)));
}
- return "[" + rows.join(",\r\n") + "]";
},
-
- /**
- * returns the elements which's score is bigger than the threshold
- * @return {Array} the reduced array
- */
- threshold : function(arr, threshold, scoreFunc) {
- var i, queue = [];
- for ( i = 0; i < arr.length; i++) {
- if (scoreFunc.apply(arr, [arr[i]]) >= threshold) {
- queue.push(arr[i]);
- }
+ left: function(dimension, context) {
+ if (dimension.unit === "%") {
+ return Math.floor(context.width * (dimension.value / 100));
}
- return queue;
- },
+ }
+ };
- maxIndex : function(arr) {
- var i, max = 0;
- for ( i = 0; i < arr.length; i++) {
- if (arr[i] > arr[max]) {
- max = i;
- }
- }
- return max;
- },
+ CVUtils.computeImageArea = function(inputWidth, inputHeight, area) {
+ var context = {width: inputWidth, height: inputHeight};
- max : function(arr) {
- var i, max = 0;
- for ( i = 0; i < arr.length; i++) {
- if (arr[i] > max) {
- max = arr[i];
- }
- }
- return max;
- },
+ var parsedArea = Object.keys(area).reduce(function(result, key) {
+ var value = area[key],
+ parsed = CVUtils._parseCSSDimensionValues(value),
+ calculated = CVUtils._dimensionsConverters[key](parsed, context);
- sum: function(arr) {
- var length = arr.length,
- sum = 0;
+ result[key] = calculated;
+ return result;
+ }, {});
- while(length--) {
- sum += arr[length];
+ return {
+ sx: parsedArea.left,
+ sy: parsedArea.top,
+ sw: parsedArea.right - parsedArea.left,
+ sh: parsedArea.bottom - parsedArea.top
+ };
+ };
+
+ return (CVUtils);
+});
+
+
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define, vec2, mat2 */
+
+define('image_wrapper',[
+ "subImage",
+ "cv_utils",
+ "array_helper"
+ ],
+ function(SubImage, CVUtils, ArrayHelper) {
+
+ 'use strict';
+
+ /**
+ * Represents a basic image combining the data and size.
+ * In addition, some methods for manipulation are contained.
+ * @param size {x,y} The size of the image in pixel
+ * @param data {Array} If given, a flat array containing the pixel data
+ * @param ArrayType {Type} If given, the desired DataType of the Array (may be typed/non-typed)
+ * @param initialize {Boolean} Indicating if the array should be initialized on creation.
+ * @returns {ImageWrapper}
+ */
+ function ImageWrapper(size, data, ArrayType, initialize) {
+ if (!data) {
+ if (ArrayType) {
+ this.data = new ArrayType(size.x * size.y);
+ if (ArrayType === Array && initialize) {
+ ArrayHelper.init(this.data, 0);
+ }
+ } else {
+ this.data = new Uint8Array(size.x * size.y);
+ if (Uint8Array === Array && initialize) {
+ ArrayHelper.init(this.data, 0);
+ }
}
- return sum;
+
+ } else {
+ this.data = data;
}
+ this.size = size;
+ }
+
+ /**
+ * tests if a position is within the image with a given offset
+ * @param imgRef {x, y} The location to test
+ * @param border Number the padding value in pixel
+ * @returns {Boolean} true if location inside the image's border, false otherwise
+ * @see cvd/image.h
+ */
+ ImageWrapper.prototype.inImageWithBorder = function(imgRef, border) {
+ return (imgRef.x >= border) && (imgRef.y >= border) && (imgRef.x < (this.size.x - border)) && (imgRef.y < (this.size.y - border));
};
-});
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define, vec2, vec3 */
-define('cv_utils',['cluster', 'glMatrixAddon', "array_helper"], function(Cluster2, glMatrixAddon, ArrayHelper) {
+ /**
+ * Transforms an image according to the given affine-transformation matrix.
+ * @param inImg ImageWrapper a image containing the information to be extracted.
+ * @param outImg ImageWrapper the image to be filled. The whole image out image is filled by the in image.
+ * @param M mat2 the matrix used to map point in the out matrix to those in the in matrix
+ * @param inOrig vec2 origin in the in image
+ * @param outOrig vec2 origin in the out image
+ * @returns Number the number of pixels not in the in image
+ * @see cvd/vision.h
+ */
+ ImageWrapper.transform = function(inImg, outImg, M, inOrig, outOrig) {
+ var w = outImg.size.x, h = outImg.size.y, iw = inImg.size.x, ih = inImg.size.y;
+ var across = vec2.create([M[0], M[2]]);
+ var down = vec2.create([M[1], M[3]]);
+ var defaultValue = 0;
- "use strict";
- /*
- * cv_utils.js
- * Collection of CV functions and libraries
- */
+ var p0 = vec2.subtract(inOrig, mat2.xVec2(M, outOrig, vec2.create()), vec2.create());
+
+ var min_x = p0[0], min_y = p0[1];
+ var max_x = min_x, max_y = min_y;
+ var p, i, j;
+
+ var sampleFunc = ImageWrapper.sample;
+
+ if (across[0] < 0)
+ min_x += w * across[0];
+ else
+ max_x += w * across[0];
+
+ if (down[0] < 0)
+ min_x += h * down[0];
+ else
+ max_x += h * down[0];
+
+ if (across[1] < 0)
+ min_y += w * across[1];
+ else
+ max_y += w * across[1];
+
+ if (down[1] < 0)
+ min_y += h * down[1];
+ else
+ max_y += h * down[1];
+
+ var carrigeReturn = vec2.subtract(down, vec2.scale(across, w, vec2.create()), vec2.create());
+
+ if (min_x >= 0 && min_y >= 0 && max_x < iw - 1 && max_y < ih - 1) {
+ p = p0;
+ for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn))
+ for ( j = 0; j < w; ++j, vec2.add(p, across))
+ outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
+ return 0;
+ } else {
+ var x_bound = iw - 1;
+ var y_bound = ih - 1;
+ var count = 0;
+ p = p0;
+ for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn)) {
+ for ( j = 0; j < w; ++j, vec2.add(p, across)) {
+ if (0 <= p[0] && 0 <= p[1] && p[0] < x_bound && p[1] < y_bound) {
+ outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
+ } else {
+ outImg.set(j, i, defaultValue); ++count;
+ }
+ }
+ }
+ return count;
+ }
+ };
/**
- * Namespace for various CV alorithms
- * @class Represents a collection of useful CV algorithms/functions
+ * Performs bilinear sampling
+ * @param inImg Image to extract sample from
+ * @param x the x-coordinate
+ * @param y the y-coordinate
+ * @returns the sampled value
+ * @see cvd/vision.h
*/
+ ImageWrapper.sample = function(inImg, x, y) {
+ var lx = Math.floor(x);
+ var ly = Math.floor(y);
+ var w = inImg.size.x;
+ var base = ly * inImg.size.x + lx;
+ var a = inImg.data[base + 0];
+ var b = inImg.data[base + 1];
+ var c = inImg.data[base + w];
+ var d = inImg.data[base + w + 1];
+ var e = a - b;
+ x -= lx;
+ y -= ly;
- var CVUtils = {};
+ var result = Math.floor(x * (y * (e - c + d) - e) + y * (c - a) + a);
+ return result;
+ };
/**
- * @param x x-coordinate
- * @param y y-coordinate
- * @return ImageReference {x,y} Coordinate
+ * Initializes a given array. Sets each element to zero.
+ * @param array {Array} The array to initialize
*/
- CVUtils.imageRef = function(x, y) {
- var that = {
- x : x,
- y : y,
- toVec2 : function() {
- return vec2.create([this.x, this.y]);
- },
- toVec3 : function() {
- return vec3.create([this.x, this.y, 1]);
- },
- round : function() {
- this.x = this.x > 0.0 ? Math.floor(this.x + 0.5) : Math.floor(this.x - 0.5);
- this.y = this.y > 0.0 ? Math.floor(this.y + 0.5) : Math.floor(this.y - 0.5);
- return this;
- }
- };
- return that;
+ ImageWrapper.clearArray = function(array) {
+ var l = array.length;
+ while (l--) {
+ array[l] = 0;
+ }
};
/**
- * Computes an integral image of a given grayscale image.
- * @param imageDataContainer {ImageDataContainer} the image to be integrated
+ * Creates a {SubImage} from the current image ({this}).
+ * @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
+ * @param size {ImageRef} The size of the resulting image
+ * @returns {SubImage} A shared part of the original image
*/
- CVUtils.computeIntegralImage2 = function(imageWrapper, integralWrapper) {
- var imageData = imageWrapper.data;
- var width = imageWrapper.size.x;
- var height = imageWrapper.size.y;
- var integralImageData = integralWrapper.data;
- var sum = 0, posA = 0, posB = 0, posC = 0, posD = 0, x, y;
-
- // sum up first column
- posB = width;
- sum = 0;
- for ( y = 1; y < height; y++) {
- sum += imageData[posA];
- integralImageData[posB] += sum;
- posA += width;
- posB += width;
- }
-
- posA = 0;
- posB = 1;
- sum = 0;
- for ( x = 1; x < width; x++) {
- sum += imageData[posA];
- integralImageData[posB] += sum;
- posA++;
- posB++;
- }
+ ImageWrapper.prototype.subImage = function(from, size) {
+ return new SubImage(from, size, this);
+ };
- for ( y = 1; y < height; y++) {
- posA = y * width + 1;
- posB = (y - 1) * width + 1;
- posC = y * width;
- posD = (y - 1) * width;
- for ( x = 1; x < width; x++) {
- integralImageData[posA] += imageData[posA] + integralImageData[posB] + integralImageData[posC] - integralImageData[posD];
- posA++;
- posB++;
- posC++;
- posD++;
+ /**
+ * Creates an {ImageWrapper) and copies the needed underlying image-data area
+ * @param imageWrapper {ImageWrapper} The target {ImageWrapper} where the data should be copied
+ * @param from {ImageRef} The location where to copy from (top-left location)
+ */
+ ImageWrapper.prototype.subImageAsCopy = function(imageWrapper, from) {
+ var sizeY = imageWrapper.size.y, sizeX = imageWrapper.size.x;
+ var x, y;
+ for ( x = 0; x < sizeX; x++) {
+ for ( y = 0; y < sizeY; y++) {
+ imageWrapper.data[y * sizeX + x] = this.data[(from.y + y) * this.size.x + from.x + x];
}
}
};
- CVUtils.computeIntegralImage = function(imageWrapper, integralWrapper) {
- var imageData = imageWrapper.data;
- var width = imageWrapper.size.x;
- var height = imageWrapper.size.y;
- var integralImageData = integralWrapper.data;
- var sum = 0;
+ ImageWrapper.prototype.copyTo = function(imageWrapper) {
+ var length = this.data.length, srcData = this.data, dstData = imageWrapper.data;
- // sum up first row
- for (var i = 0; i < width; i++) {
- sum += imageData[i];
- integralImageData[i] = sum;
+ while (length--) {
+ dstData[length] = srcData[length];
}
+ };
- for (var v = 1; v < height; v++) {
- sum = 0;
- for (var u = 0; u < width; u++) {
- sum += imageData[v * width + u];
- integralImageData[((v) * width) + u] = sum + integralImageData[(v - 1) * width + u];
+ /**
+ * Retrieves a given pixel position from the image
+ * @param x {Number} The x-position
+ * @param y {Number} The y-position
+ * @returns {Number} The grayscale value at the pixel-position
+ */
+ ImageWrapper.prototype.get = function(x, y) {
+ return this.data[y * this.size.x + x];
+ };
+
+ /**
+ * Retrieves a given pixel position from the image
+ * @param x {Number} The x-position
+ * @param y {Number} The y-position
+ * @returns {Number} The grayscale value at the pixel-position
+ */
+ ImageWrapper.prototype.getSafe = function(x, y) {
+ var i;
+
+ if (!this.indexMapping) {
+ this.indexMapping = {
+ x : [],
+ y : []
+ };
+ for (i = 0; i < this.size.x; i++) {
+ this.indexMapping.x[i] = i;
+ this.indexMapping.x[i + this.size.x] = i;
+ }
+ for (i = 0; i < this.size.y; i++) {
+ this.indexMapping.y[i] = i;
+ this.indexMapping.y[i + this.size.y] = i;
}
}
+ return this.data[(this.indexMapping.y[y + this.size.y]) * this.size.x + this.indexMapping.x[x + this.size.x]];
};
- CVUtils.thresholdImage = function(imageWrapper, threshold, targetWrapper) {
- if (!targetWrapper) {
- targetWrapper = imageWrapper;
- }
- var imageData = imageWrapper.data, length = imageData.length, targetData = targetWrapper.data;
+ /**
+ * Sets a given pixel position in the image
+ * @param x {Number} The x-position
+ * @param y {Number} The y-position
+ * @param value {Number} The grayscale value to set
+ * @returns {ImageWrapper} The Image itself (for possible chaining)
+ */
+ ImageWrapper.prototype.set = function(x, y, value) {
+ this.data[y * this.size.x + x] = value;
+ return this;
+ };
- while (length--) {
- targetData[length] = imageData[length] < threshold ? 1 : 0;
+ /**
+ * Sets the border of the image (1 pixel) to zero
+ */
+ ImageWrapper.prototype.zeroBorder = function() {
+ var i, width = this.size.x, height = this.size.y, data = this.data;
+ for ( i = 0; i < width; i++) {
+ data[i] = data[(height - 1) * width + i] = 0;
+ }
+ for ( i = 1; i < height - 1; i++) {
+ data[i * width] = data[i * width + (width - 1)] = 0;
}
};
- CVUtils.computeHistogram = function(imageWrapper, bitsPerPixel) {
- if (!bitsPerPixel) {
- bitsPerPixel = 8;
- }
- var imageData = imageWrapper.data,
- length = imageData.length,
- bitShift = 8 - bitsPerPixel,
- bucketCnt = 1 << bitsPerPixel,
- hist = new Int32Array(bucketCnt);
+ /**
+ * Inverts a binary image in place
+ */
+ ImageWrapper.prototype.invert = function() {
+ var data = this.data, length = data.length;
while (length--) {
- hist[imageData[length] >> bitShift]++;
+ data[length] = data[length] ? 0 : 1;
}
- return hist;
- };
- CVUtils.sharpenLine = function(line) {
- var i,
- length = line.length,
- left = line[0],
- center = line[1],
- right;
+ };
- for (i = 1; i < length - 1; i++) {
- right = line[i + 1];
- // -1 4 -1 kernel
- line[i-1] = (((center * 2) - left - right)) & 255;
- left = center;
- center = right;
+ ImageWrapper.prototype.convolve = function(kernel) {
+ var x, y, kx, ky, kSize = (kernel.length / 2) | 0, accu = 0;
+ for ( y = 0; y < this.size.y; y++) {
+ for ( x = 0; x < this.size.x; x++) {
+ accu = 0;
+ for ( ky = -kSize; ky <= kSize; ky++) {
+ for ( kx = -kSize; kx <= kSize; kx++) {
+ accu += kernel[ky+kSize][kx + kSize] * this.getSafe(x + kx, y + ky);
+ }
+ }
+ this.data[y * this.size.x + x] = accu;
+ }
}
- return line;
};
- CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) {
- if (!bitsPerPixel) {
- bitsPerPixel = 8;
+ ImageWrapper.prototype.moments = function(labelcount) {
+ var data = this.data,
+ x,
+ y,
+ height = this.size.y,
+ width = this.size.x,
+ val,
+ ysq,
+ labelsum = [],
+ i,
+ label,
+ mu11,
+ mu02,
+ mu20,
+ x_,
+ y_,
+ tmp,
+ result = [],
+ PI = Math.PI,
+ PI_4 = PI / 4;
+
+ if (labelcount <= 0) {
+ return result;
}
- var hist,
- threshold,
- bitShift = 8 - bitsPerPixel;
- function px(init, end) {
- var sum = 0, i;
- for ( i = init; i <= end; i++) {
- sum += hist[i];
- }
- return sum;
+ for ( i = 0; i < labelcount; i++) {
+ labelsum[i] = {
+ m00 : 0,
+ m01 : 0,
+ m10 : 0,
+ m11 : 0,
+ m02 : 0,
+ m20 : 0,
+ theta : 0,
+ rad : 0
+ };
}
- function mx(init, end) {
- var i, sum = 0;
-
- for ( i = init; i <= end; i++) {
- sum += i * hist[i];
+ for ( y = 0; y < height; y++) {
+ ysq = y * y;
+ for ( x = 0; x < width; x++) {
+ val = data[y * width + x];
+ if (val > 0) {
+ label = labelsum[val - 1];
+ label.m00 += 1;
+ label.m01 += y;
+ label.m10 += x;
+ label.m11 += x * y;
+ label.m02 += ysq;
+ label.m20 += x * x;
+ }
}
-
- return sum;
}
- function determineThreshold() {
- var vet = [0], p1, p2, p12, k, m1, m2, m12,
- max = (1 << bitsPerPixel) - 1;
-
- hist = CVUtils.computeHistogram(imageWrapper, bitsPerPixel);
- for ( k = 1; k < max; k++) {
- p1 = px(0, k);
- p2 = px(k + 1, max);
- p12 = p1 * p2;
- if (p12 === 0) {
- p12 = 1;
+ for ( i = 0; i < labelcount; i++) {
+ label = labelsum[i];
+ if (!isNaN(label.m00) && label.m00 !== 0) {
+ x_ = label.m10 / label.m00;
+ y_ = label.m01 / label.m00;
+ mu11 = label.m11 / label.m00 - x_ * y_;
+ mu02 = label.m02 / label.m00 - y_ * y_;
+ mu20 = label.m20 / label.m00 - x_ * x_;
+ tmp = (mu02 - mu20) / (2 * mu11);
+ tmp = 0.5 * Math.atan(tmp) + (mu11 >= 0 ? PI_4 : -PI_4 ) + PI;
+ label.theta = (tmp * 180 / PI + 90) % 180 - 90;
+ if (label.theta < 0) {
+ label.theta += 180;
}
- m1 = mx(0, k) * p2;
- m2 = mx(k + 1, max) * p1;
- m12 = m1 - m2;
- vet[k] = m12 * m12 / p12;
+ label.rad = tmp > PI ? tmp - PI : tmp;
+ label.vec = vec2.create([Math.cos(tmp), Math.sin(tmp)]);
+ result.push(label);
}
- return ArrayHelper.maxIndex(vet);
}
- threshold = determineThreshold();
- return threshold << bitShift;
+ return result;
};
- CVUtils.otsuThreshold = function(imageWrapper, targetWrapper) {
- var threshold = CVUtils.determineOtsuThreshold(imageWrapper);
+ /**
+ * Displays the {ImageWrapper} in a given canvas
+ * @param canvas {Canvas} The canvas element to write to
+ * @param scale {Number} Scale which is applied to each pixel-value
+ */
+ ImageWrapper.prototype.show = function(canvas, scale) {
+ var ctx,
+ frame,
+ data,
+ current,
+ pixel,
+ x,
+ y;
+
+ if (!scale) {
+ scale = 1.0;
+ }
+ ctx = canvas.getContext('2d');
+ canvas.width = this.size.x;
+ canvas.height = this.size.y;
+ frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ data = frame.data;
+ current = 0;
+ for (y = 0; y < this.size.y; y++) {
+ for (x = 0; x < this.size.x; x++) {
+ pixel = y * this.size.x + x;
+ current = this.get(x, y) * scale;
+ data[pixel * 4 + 0] = current;
+ data[pixel * 4 + 1] = current;
+ data[pixel * 4 + 2] = current;
+ data[pixel * 4 + 3] = 255;
+ }
+ }
+ //frame.data = data;
+ ctx.putImageData(frame, 0, 0);
+ };
- CVUtils.thresholdImage(imageWrapper, threshold, targetWrapper);
- return threshold;
+ /**
+ * Displays the {SubImage} in a given canvas
+ * @param canvas {Canvas} The canvas element to write to
+ * @param scale {Number} Scale which is applied to each pixel-value
+ */
+ ImageWrapper.prototype.overlay = function(canvas, scale, from) {
+ if (!scale || scale < 0 || scale > 360) {
+ scale = 360;
+ }
+ var hsv = [0, 1, 1];
+ var rgb = [0, 0, 0];
+ var whiteRgb = [255, 255, 255];
+ var blackRgb = [0, 0, 0];
+ var result = [];
+ var ctx = canvas.getContext('2d');
+ var frame = ctx.getImageData(from.x, from.y, this.size.x, this.size.y);
+ var data = frame.data;
+ var length = this.data.length;
+ while (length--) {
+ hsv[0] = this.data[length] * scale;
+ result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb);
+ data[length * 4 + 0] = result[0];
+ data[length * 4 + 1] = result[1];
+ data[length * 4 + 2] = result[2];
+ data[length * 4 + 3] = 255;
+ }
+ ctx.putImageData(frame, from.x, from.y);
};
- // local thresholding
- CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapper) {
- CVUtils.computeIntegralImage(imageWrapper, integralWrapper);
+ return (ImageWrapper);
+});
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- if (!targetWrapper) {
- targetWrapper = imageWrapper;
- }
- var imageData = imageWrapper.data;
- var targetData = targetWrapper.data;
- var width = imageWrapper.size.x;
- var height = imageWrapper.size.y;
- var integralImageData = integralWrapper.data;
- var sum = 0, v, u, kernel = 3, A, B, C, D, avg, size = (kernel * 2 + 1) * (kernel * 2 + 1);
+/**
+ * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
+ */
+define('tracer',[],function() {
+ "use strict";
+
+ var Tracer = {
+ searchDirections : [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]],
+ create : function(imageWrapper, labelWrapper) {
+ var imageData = imageWrapper.data,
+ labelData = labelWrapper.data,
+ searchDirections = this.searchDirections,
+ width = imageWrapper.size.x,
+ pos;
- // clear out top & bottom-border
- for ( v = 0; v <= kernel; v++) {
- for ( u = 0; u < width; u++) {
- targetData[((v) * width) + u] = 0;
- targetData[(((height - 1) - v) * width) + u] = 0;
+ function trace(current, color, label, edgelabel) {
+ var i,
+ y,
+ x;
+
+ for ( i = 0; i < 7; i++) {
+ y = current.cy + searchDirections[current.dir][0];
+ x = current.cx + searchDirections[current.dir][1];
+ pos = y * width + x;
+ if ((imageData[pos] === color) && ((labelData[pos] === 0) || (labelData[pos] === label))) {
+ labelData[pos] = label;
+ current.cy = y;
+ current.cx = x;
+ return true;
+ } else {
+ if (labelData[pos] === 0) {
+ labelData[pos] = edgelabel;
+ }
+ current.dir = (current.dir + 1) % 8;
+ }
+ }
+ return false;
}
- }
- // clear out left & right border
- for ( v = kernel; v < height - kernel; v++) {
- for ( u = 0; u <= kernel; u++) {
- targetData[((v) * width) + u] = 0;
- targetData[((v) * width) + (width - 1 - u)] = 0;
+ function vertex2D(x, y, dir) {
+ return {
+ dir : dir,
+ x : x,
+ y : y,
+ next : null,
+ prev : null
+ };
}
- }
- for ( v = kernel + 1; v < height - kernel - 1; v++) {
- for ( u = kernel + 1; u < width - kernel; u++) {
- A = integralImageData[(v - kernel - 1) * width + (u - kernel - 1)];
- B = integralImageData[(v - kernel - 1) * width + (u + kernel)];
- C = integralImageData[(v + kernel) * width + (u - kernel - 1)];
- D = integralImageData[(v + kernel) * width + (u + kernel)];
- sum = D - C - B + A;
- avg = sum / (size);
- targetData[v * width + u] = imageData[v * width + u] > (avg + 5) ? 0 : 1;
+ function contourTracing(sy, sx, label, color, edgelabel) {
+ var Fv = null,
+ Cv,
+ P,
+ ldir,
+ current = {
+ cx : sx,
+ cy : sy,
+ dir : 0
+ };
+
+ if (trace(current, color, label, edgelabel)) {
+ Fv = vertex2D(sx, sy, current.dir);
+ Cv = Fv;
+ ldir = current.dir;
+ P = vertex2D(current.cx, current.cy, 0);
+ P.prev = Cv;
+ Cv.next = P;
+ P.next = null;
+ Cv = P;
+ do {
+ current.dir = (current.dir + 6) % 8;
+ trace(current, color, label, edgelabel);
+ if (ldir != current.dir) {
+ Cv.dir = current.dir;
+ P = vertex2D(current.cx, current.cy, 0);
+ P.prev = Cv;
+ Cv.next = P;
+ P.next = null;
+ Cv = P;
+ } else {
+ Cv.dir = ldir;
+ Cv.x = current.cx;
+ Cv.y = current.cy;
+ }
+ ldir = current.dir;
+ } while(current.cx != sx || current.cy != sy);
+ Fv.prev = Cv.prev;
+ Cv.prev.next = Fv;
+ }
+ return Fv;
}
- }
- };
-
- CVUtils.cluster = function(points, threshold, property) {
- var i, k, cluster, point, clusters = [];
-
- if (!property) {
- property = "rad";
- }
- function addToCluster(point) {
- var found = false;
- for ( k = 0; k < clusters.length; k++) {
- cluster = clusters[k];
- if (cluster.fits(point)) {
- cluster.add(point);
- found = true;
+ return {
+ trace : function(current, color, label, edgelabel) {
+ return trace(current, color, label, edgelabel);
+ },
+ contourTracing : function(sy, sx, label, color, edgelabel) {
+ return contourTracing(sy, sx, label, color, edgelabel);
}
- }
- return found;
+ };
}
+ };
- // iterate over each cloud
- for ( i = 0; i < points.length; i++) {
- point = Cluster2.createPoint(points[i], i, property);
- if (!addToCluster(point)) {
- clusters.push(Cluster2.create(point, threshold));
- }
- }
+ return (Tracer);
+});
- return clusters;
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- };
+/**
+ * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
+ */
+define('rasterizer',["tracer"], function(Tracer) {
+ "use strict";
- CVUtils.Tracer = {
- trace : function(points, vec) {
- var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0;
+ var Rasterizer = {
+ createContour2D : function() {
+ return {
+ dir : null,
+ index : null,
+ firstVertex : null,
+ insideContours : null,
+ nextpeer : null,
+ prevpeer : null
+ };
+ },
+ CONTOUR_DIR : {
+ CW_DIR : 0,
+ CCW_DIR : 1,
+ UNKNOWN_DIR : 2
+ },
+ DIR : {
+ OUTSIDE_EDGE : -32767,
+ INSIDE_EDGE : -32766
+ },
+ create : function(imageWrapper, labelWrapper) {
+ var imageData = imageWrapper.data,
+ labelData = labelWrapper.data,
+ width = imageWrapper.size.x,
+ height = imageWrapper.size.y,
+ tracer = Tracer.create(imageWrapper, labelWrapper);
- function trace(idx, forward) {
- var from, to, toIdx, predictedPos, thresholdX = 1, thresholdY = Math.abs(vec[1] / 10), found = false;
+ return {
+ rasterize : function(depthlabel) {
+ var color,
+ bc,
+ lc,
+ labelindex,
+ cx,
+ cy,
+ colorMap = [],
+ vertex,
+ p,
+ cc,
+ sc,
+ pos,
+ connectedCount = 0,
+ i;
- function match(pos, predicted) {
- if (pos.x > (predicted.x - thresholdX) && pos.x < (predicted.x + thresholdX) && pos.y > (predicted.y - thresholdY) && pos.y < (predicted.y + thresholdY)) {
- return true;
- } else {
- return false;
+ for ( i = 0; i < 400; i++) {
+ colorMap[i] = 0;
}
- }
-
- // check if the next index is within the vec specifications
- // if not, check as long as the threshold is met
- from = points[idx];
- if (forward) {
- predictedPos = {
- x : from.x + vec[0],
- y : from.y + vec[1]
- };
- } else {
- predictedPos = {
- x : from.x - vec[0],
- y : from.y - vec[1]
+ colorMap[0] = imageData[0];
+ cc = null;
+ for ( cy = 1; cy < height - 1; cy++) {
+ labelindex = 0;
+ bc = colorMap[0];
+ for ( cx = 1; cx < width - 1; cx++) {
+ pos = cy * width + cx;
+ if (labelData[pos] === 0) {
+ color = imageData[pos];
+ if (color !== bc) {
+ if (labelindex === 0) {
+ lc = connectedCount + 1;
+ colorMap[lc] = color;
+ bc = color;
+ vertex = tracer.contourTracing(cy, cx, lc, color, Rasterizer.DIR.OUTSIDE_EDGE);
+ if (vertex !== null) {
+ connectedCount++;
+ labelindex = lc;
+ p = Rasterizer.createContour2D();
+ p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
+ p.index = labelindex;
+ p.firstVertex = vertex;
+ p.nextpeer = cc;
+ p.insideContours = null;
+ if (cc !== null) {
+ cc.prevpeer = p;
+ }
+ cc = p;
+ }
+ } else {
+ vertex = tracer.contourTracing(cy, cx, Rasterizer.DIR.INSIDE_EDGE, color, labelindex);
+ if (vertex !== null) {
+ p = Rasterizer.createContour2D();
+ p.firstVertex = vertex;
+ p.insideContours = null;
+ if (depthlabel === 0) {
+ p.dir = Rasterizer.CONTOUR_DIR.CCW_DIR;
+ } else {
+ p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
+ }
+ p.index = depthlabel;
+ sc = cc;
+ while ((sc !== null) && sc.index !== labelindex) {
+ sc = sc.nextpeer;
+ }
+ if (sc !== null) {
+ p.nextpeer = sc.insideContours;
+ if (sc.insideContours !== null) {
+ sc.insideContours.prevpeer = p;
+ }
+ sc.insideContours = p;
+ }
+ }
+ }
+ } else {
+ labelData[pos] = labelindex;
+ }
+ } else if (labelData[pos] === Rasterizer.DIR.OUTSIDE_EDGE || labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
+ labelindex = 0;
+ if (labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
+ bc = imageData[pos];
+ } else {
+ bc = colorMap[0];
+ }
+ } else {
+ labelindex = labelData[pos];
+ bc = colorMap[labelindex];
+ }
+ }
+ }
+ sc = cc;
+ while (sc !== null) {
+ sc.index = depthlabel;
+ sc = sc.nextpeer;
+ }
+ return {
+ cc : cc,
+ count : connectedCount
};
- }
-
- toIdx = forward ? idx + 1 : idx - 1;
- to = points[toIdx];
- while (to && ( found = match(to, predictedPos)) !== true && (Math.abs(to.y - from.y) < vec[1])) {
- toIdx = forward ? toIdx + 1 : toIdx - 1;
- to = points[toIdx];
- }
-
- return found ? toIdx : null;
- }
-
- for ( iteration = 0; iteration < maxIterations; iteration++) {
- // randomly select point to start with
- centerPos = Math.floor(Math.random() * points.length);
-
- // trace forward
- top = [];
- currentPos = centerPos;
- top.push(points[currentPos]);
- while (( currentPos = trace(currentPos, true)) !== null) {
- top.push(points[currentPos]);
- }
- if (centerPos > 0) {
- currentPos = centerPos;
- while (( currentPos = trace(currentPos, false)) !== null) {
- top.push(points[currentPos]);
+ },
+ debug: {
+ drawContour : function(canvas, firstContour) {
+ var ctx = canvas.getContext("2d"),
+ pq = firstContour,
+ iq,
+ q,
+ p;
+
+ ctx.strokeStyle = "red";
+ ctx.fillStyle = "red";
+ ctx.lineWidth = 1;
+
+ if (pq !== null) {
+ iq = pq.insideContours;
+ } else {
+ iq = null;
+ }
+
+ while (pq !== null) {
+ if (iq !== null) {
+ q = iq;
+ iq = iq.nextpeer;
+ } else {
+ q = pq;
+ pq = pq.nextpeer;
+ if (pq !== null) {
+ iq = pq.insideContours;
+ } else {
+ iq = null;
+ }
+ }
+
+ switch(q.dir) {
+ case Rasterizer.CONTOUR_DIR.CW_DIR:
+ ctx.strokeStyle = "red";
+ break;
+ case Rasterizer.CONTOUR_DIR.CCW_DIR:
+ ctx.strokeStyle = "blue";
+ break;
+ case Rasterizer.CONTOUR_DIR.UNKNOWN_DIR:
+ ctx.strokeStyle = "green";
+ break;
+ }
+
+ p = q.firstVertex;
+ ctx.beginPath();
+ ctx.moveTo(p.x, p.y);
+ do {
+ p = p.next;
+ ctx.lineTo(p.x, p.y);
+ } while(p !== q.firstVertex);
+ ctx.stroke();
+ }
}
}
-
- if (top.length > result.length) {
- result = top;
- }
- }
-
- return result;
-
- }
- };
-
- CVUtils.DILATE = 1;
- CVUtils.ERODE = 2;
-
- CVUtils.dilate = function(inImageWrapper, outImageWrapper) {
- var v, u, inImageData = inImageWrapper.data, outImageData = outImageWrapper.data, height = inImageWrapper.size.y, width = inImageWrapper.size.x, sum, yStart1, yStart2, xStart1, xStart2;
-
- for ( v = 1; v < height - 1; v++) {
- for ( u = 1; u < width - 1; u++) {
- yStart1 = v - 1;
- yStart2 = v + 1;
- xStart1 = u - 1;
- xStart2 = u + 1;
- sum = inImageData[yStart1 * width + xStart1]/* + inImageData[yStart1*width+u] */ + inImageData[yStart1 * width + xStart2] +
- /* inImageData[v*width+xStart1] + */
- inImageData[v * width + u] + /* inImageData[v*width+xStart2] +*/
- inImageData[yStart2 * width + xStart1]/* + inImageData[yStart2*width+u]*/ + inImageData[yStart2 * width + xStart2];
- outImageData[v * width + u] = sum > 0 ? 1 : 0;
- }
- }
- };
-
- CVUtils.erode = function(inImageWrapper, outImageWrapper) {
- var v, u, inImageData = inImageWrapper.data, outImageData = outImageWrapper.data, height = inImageWrapper.size.y, width = inImageWrapper.size.x, sum, yStart1, yStart2, xStart1, xStart2;
-
- for ( v = 1; v < height - 1; v++) {
- for ( u = 1; u < width - 1; u++) {
- yStart1 = v - 1;
- yStart2 = v + 1;
- xStart1 = u - 1;
- xStart2 = u + 1;
- sum = inImageData[yStart1 * width + xStart1]/* + inImageData[yStart1*width+u] */ + inImageData[yStart1 * width + xStart2] +
- /* inImageData[v*width+xStart1] + */
- inImageData[v * width + u] + /* inImageData[v*width+xStart2] +*/
- inImageData[yStart2 * width + xStart1]/* + inImageData[yStart2*width+u]*/ + inImageData[yStart2 * width + xStart2];
- outImageData[v * width + u] = sum === 5 ? 1 : 0;
- }
+ };
}
};
- CVUtils.subtract = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
- if (!resultImageWrapper) {
- resultImageWrapper = aImageWrapper;
- }
- var length = aImageWrapper.data.length, aImageData = aImageWrapper.data, bImageData = bImageWrapper.data, cImageData = resultImageWrapper.data;
-
- while (length--) {
- cImageData[length] = aImageData[length] - bImageData[length];
- }
- };
+ return (Rasterizer);
+});
- CVUtils.bitwiseOr = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
- if (!resultImageWrapper) {
- resultImageWrapper = aImageWrapper;
- }
- var length = aImageWrapper.data.length, aImageData = aImageWrapper.data, bImageData = bImageWrapper.data, cImageData = resultImageWrapper.data;
+/* jshint undef: true, unused: true, browser:true, devel: true, -W041: false */
+/* global define */
- while (length--) {
- cImageData[length] = aImageData[length] || bImageData[length];
- }
- };
+define('skeletonizer',[],function() {
+ "use strict";
- CVUtils.countNonZero = function(imageWrapper) {
- var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0;
+ /* @preserve ASM BEGIN */
+ function Skeletonizer(stdlib, foreign, buffer) {
+ "use asm";
- while (length--) {
- sum += data[length];
- }
- return sum;
- };
+ var images = new stdlib.Uint8Array(buffer),
+ size = foreign.size | 0,
+ imul = stdlib.Math.imul;
- CVUtils.topGeneric = function(list, top, scoreFunc) {
- var i, minIdx = 0, min = 0, queue = [], score, hit, pos;
+ function erode(inImagePtr, outImagePtr) {
+ inImagePtr = inImagePtr | 0;
+ outImagePtr = outImagePtr | 0;
- for ( i = 0; i < top; i++) {
- queue[i] = {
- score : 0,
- item : null
- };
- }
+ var v = 0,
+ u = 0,
+ sum = 0,
+ yStart1 = 0,
+ yStart2 = 0,
+ xStart1 = 0,
+ xStart2 = 0,
+ offset = 0;
- for ( i = 0; i < list.length; i++) {
- score = scoreFunc.apply(this, [list[i]]);
- if (score > min) {
- hit = queue[minIdx];
- hit.score = score;
- hit.item = list[i];
- min = Number.MAX_VALUE;
- for ( pos = 0; pos < top; pos++) {
- if (queue[pos].score < min) {
- min = queue[pos].score;
- minIdx = pos;
+ for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
+ offset = (offset + size) | 0;
+ for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
+ yStart1 = (offset - size) | 0;
+ yStart2 = (offset + size) | 0;
+ xStart1 = (u - 1) | 0;
+ xStart2 = (u + 1) | 0;
+ sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
+ if ((sum | 0) == (5 | 0)) {
+ images[(outImagePtr + offset + u) | 0] = 1;
+ } else {
+ images[(outImagePtr + offset + u) | 0] = 0;
}
}
}
+ return;
}
- return queue;
- };
-
- CVUtils.grayArrayFromImage = function(htmlImage, offsetX, ctx, array) {
- ctx.drawImage(htmlImage, offsetX, 0, htmlImage.width, htmlImage.height);
- var ctxData = ctx.getImageData(offsetX, 0, htmlImage.width, htmlImage.height).data;
- CVUtils.computeGray(ctxData, array);
- };
-
- CVUtils.grayArrayFromContext = function(ctx, size, offset, array) {
- var ctxData = ctx.getImageData(offset.x, offset.y, size.x, size.y).data;
- CVUtils.computeGray(ctxData, array);
- };
-
- CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
- var topRowIdx = 0;
- var bottomRowIdx = size.x;
- var endIdx = Math.floor(canvasData.length / 4);
- var outWidth = size.x / 2;
- var outImgIdx = 0;
- var inWidth = size.x;
- var i;
-
- while (bottomRowIdx < endIdx) {
- for ( i = 0; i < outWidth; i++) {
- outArray[outImgIdx] = Math.floor(((0.299 * canvasData[topRowIdx * 4 + 0] + 0.587 * canvasData[topRowIdx * 4 + 1] + 0.114 * canvasData[topRowIdx * 4 + 2]) + (0.299 * canvasData[(topRowIdx + 1) * 4 + 0] + 0.587 * canvasData[(topRowIdx + 1) * 4 + 1] + 0.114 * canvasData[(topRowIdx + 1) * 4 + 2]) + (0.299 * canvasData[(bottomRowIdx) * 4 + 0] + 0.587 * canvasData[(bottomRowIdx) * 4 + 1] + 0.114 * canvasData[(bottomRowIdx) * 4 + 2]) + (0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] + 0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] + 0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4);
- outImgIdx++;
- topRowIdx = topRowIdx + 2;
- bottomRowIdx = bottomRowIdx + 2;
- }
- topRowIdx = topRowIdx + inWidth;
- bottomRowIdx = bottomRowIdx + inWidth;
- }
-
- };
-
- CVUtils.computeGray = function(imageData, outArray, config) {
- var l = (imageData.length / 4) | 0,
- i,
- singleChannel = config && config.singleChannel === true;
-
- if (singleChannel) {
- for (i = 0; i < l; i++) {
- outArray[i] = imageData[i * 4 + 0];
- }
- } else {
- for (i = 0; i < l; i++) {
- outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
- }
- }
- };
-
- CVUtils.loadImageArray = function(src, callback, canvas) {
- if (!canvas)
- canvas = document.createElement('canvas');
- var img = new Image();
- img.callback = callback;
- img.onload = function() {
- canvas.width = this.width;
- canvas.height = this.height;
- var ctx = canvas.getContext('2d');
- ctx.drawImage(this, 0, 0);
- var array = new Uint8Array(this.width * this.height);
- ctx.drawImage(this, 0, 0);
- var data = ctx.getImageData(0, 0, this.width, this.height).data;
- CVUtils.computeGray(data, array);
- this.callback(array, {
- x : this.width,
- y : this.height
- }, this);
- };
- img.src = src;
- };
+ function subtract(aImagePtr, bImagePtr, outImagePtr) {
+ aImagePtr = aImagePtr | 0;
+ bImagePtr = bImagePtr | 0;
+ outImagePtr = outImagePtr | 0;
- /**
- * @param inImg {ImageWrapper} input image to be sampled
- * @param outImg {ImageWrapper} to be stored in
- */
- CVUtils.halfSample = function(inImgWrapper, outImgWrapper) {
- var inImg = inImgWrapper.data;
- var inWidth = inImgWrapper.size.x;
- var outImg = outImgWrapper.data;
- var topRowIdx = 0;
- var bottomRowIdx = inWidth;
- var endIdx = inImg.length;
- var outWidth = inWidth / 2;
- var outImgIdx = 0;
- while (bottomRowIdx < endIdx) {
- for (var i = 0; i < outWidth; i++) {
- outImg[outImgIdx] = Math.floor((inImg[topRowIdx] + inImg[topRowIdx + 1] + inImg[bottomRowIdx] + inImg[bottomRowIdx + 1]) / 4);
- outImgIdx++;
- topRowIdx = topRowIdx + 2;
- bottomRowIdx = bottomRowIdx + 2;
+ var length = 0;
+
+ length = imul(size, size) | 0;
+
+ while ((length | 0) > 0) {
+ length = (length - 1) | 0;
+ images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) - (images[(bImagePtr + length) | 0] | 0)) | 0;
}
- topRowIdx = topRowIdx + inWidth;
- bottomRowIdx = bottomRowIdx + inWidth;
}
- };
- CVUtils.hsv2rgb = function(hsv, rgb) {
- var h = hsv[0], s = hsv[1], v = hsv[2], c = v * s, x = c * (1 - Math.abs((h / 60) % 2 - 1)), m = v - c, r = 0, g = 0, b = 0;
- rgb = rgb || [0, 0, 0];
+ function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) {
+ aImagePtr = aImagePtr | 0;
+ bImagePtr = bImagePtr | 0;
+ outImagePtr = outImagePtr | 0;
- if (h < 60) {
- r = c;
- g = x;
- } else if (h < 120) {
- r = x;
- g = c;
- } else if (h < 180) {
- g = c;
- b = x;
- } else if (h < 240) {
- g = x;
- b = c;
- } else if (h < 300) {
- r = x;
- b = c;
- } else if (h < 360) {
- r = c;
- b = x;
- }
- rgb[0] = ((r + m) * 255) | 0;
- rgb[1] = ((g + m) * 255) | 0;
- rgb[2] = ((b + m) * 255) | 0;
- return rgb;
- };
+ var length = 0;
- CVUtils._computeDivisors = function(n) {
- var largeDivisors = [],
- divisors = [],
- i;
+ length = imul(size, size) | 0;
- for (i = 1; i < Math.sqrt(n) + 1; i++) {
- if (n % i === 0) {
- divisors.push(i);
- if (i !== n/i) {
- largeDivisors.unshift(Math.floor(n/i));
- }
+ while ((length | 0) > 0) {
+ length = (length - 1) | 0;
+ images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) | (images[(bImagePtr + length) | 0] | 0)) | 0;
}
}
- return divisors.concat(largeDivisors);
- };
- CVUtils._computeIntersection = function(arr1, arr2) {
- var i = 0,
- j = 0,
- result = [];
+ function countNonZero(imagePtr) {
+ imagePtr = imagePtr | 0;
- while (i < arr1.length && j < arr2.length) {
- if (arr1[i] === arr2[j]) {
- result.push(arr1[i]);
- i++;
- j++;
- } else if (arr1[i] > arr2[j]) {
- j++;
- } else {
- i++;
+ var sum = 0,
+ length = 0;
+
+ length = imul(size, size) | 0;
+
+ while ((length | 0) > 0) {
+ length = (length - 1) | 0;
+ sum = ((sum | 0) + (images[(imagePtr + length) | 0] | 0)) | 0;
}
+
+ return (sum | 0);
}
- return result;
- };
- CVUtils.calculatePatchSize = function(patchSize, imgSize) {
- var divisorsX = this._computeDivisors(imgSize.x),
- divisorsY = this._computeDivisors(imgSize.y),
- wideSide = Math.max(imgSize.x, imgSize.y),
- common = this._computeIntersection(divisorsX, divisorsY),
- nrOfPatchesList = [8, 10, 15, 20, 32, 60, 80],
- nrOfPatchesMap = {
- "x-small": 5,
- "small": 4,
- "medium": 3,
- "large": 2,
- "x-large": 1
- },
- nrOfPatchesIdx = nrOfPatchesMap[patchSize] || nrOfPatchesMap.medium,
- nrOfPatches = nrOfPatchesList[nrOfPatchesIdx],
- desiredPatchSize = Math.floor(wideSide/nrOfPatches),
- optimalPatchSize;
+ function init(imagePtr, value) {
+ imagePtr = imagePtr | 0;
+ value = value | 0;
- function findPatchSizeForDivisors(divisors) {
- var i = 0,
- found = divisors[Math.floor(divisors.length/2)];
+ var length = 0;
- while(i < (divisors.length - 1) && divisors[i] < desiredPatchSize) {
- i++;
+ length = imul(size, size) | 0;
+
+ while ((length | 0) > 0) {
+ length = (length - 1) | 0;
+ images[(imagePtr + length) | 0] = value;
}
- if (i > 0) {
- if (Math.abs(divisors[i] - desiredPatchSize) > Math.abs(divisors[i-1] - desiredPatchSize)) {
- found = divisors[i-1];
- } else {
- found = divisors[i];
+ }
+
+ function dilate(inImagePtr, outImagePtr) {
+ inImagePtr = inImagePtr | 0;
+ outImagePtr = outImagePtr | 0;
+
+ var v = 0,
+ u = 0,
+ sum = 0,
+ yStart1 = 0,
+ yStart2 = 0,
+ xStart1 = 0,
+ xStart2 = 0,
+ offset = 0;
+
+ for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
+ offset = (offset + size) | 0;
+ for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
+ yStart1 = (offset - size) | 0;
+ yStart2 = (offset + size) | 0;
+ xStart1 = (u - 1) | 0;
+ xStart2 = (u + 1) | 0;
+ sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
+ if ((sum | 0) > (0 | 0)) {
+ images[(outImagePtr + offset + u) | 0] = 1;
+ } else {
+ images[(outImagePtr + offset + u) | 0] = 0;
+ }
}
}
- if (desiredPatchSize / found < nrOfPatchesList[nrOfPatchesIdx+1] / nrOfPatchesList[nrOfPatchesIdx] &&
- desiredPatchSize / found > nrOfPatchesList[nrOfPatchesIdx-1]/nrOfPatchesList[nrOfPatchesIdx] ) {
- return {x: found, y: found};
- }
- return null;
+ return;
}
- optimalPatchSize = findPatchSizeForDivisors(common);
- if (!optimalPatchSize) {
- optimalPatchSize = findPatchSizeForDivisors(this._computeDivisors(wideSide));
- if (!optimalPatchSize) {
- optimalPatchSize = findPatchSizeForDivisors((this._computeDivisors(desiredPatchSize * nrOfPatches)));
+ function memcpy(srcImagePtr, dstImagePtr) {
+ srcImagePtr = srcImagePtr | 0;
+ dstImagePtr = dstImagePtr | 0;
+
+ var length = 0;
+
+ length = imul(size, size) | 0;
+
+ while ((length | 0) > 0) {
+ length = (length - 1) | 0;
+ images[(dstImagePtr + length) | 0] = (images[(srcImagePtr + length) | 0] | 0);
}
}
- return optimalPatchSize;
- };
- CVUtils._parseCSSDimensionValues = function(value) {
- var dimension = {
- value: parseFloat(value),
- unit: value.indexOf("%") === value.length-1 ? "%" : "%"
- };
+ function zeroBorder(imagePtr) {
+ imagePtr = imagePtr | 0;
- return dimension;
- };
+ var x = 0,
+ y = 0;
- CVUtils._dimensionsConverters = {
- top: function(dimension, context) {
- if (dimension.unit === "%") {
- return Math.floor(context.height * (dimension.value / 100));
+ for ( x = 0; (x | 0) < ((size - 1) | 0); x = (x + 1) | 0) {
+ images[(imagePtr + x) | 0] = 0;
+ images[(imagePtr + y) | 0] = 0;
+ y = ((y + size) - 1) | 0;
+ images[(imagePtr + y) | 0] = 0;
+ y = (y + 1) | 0;
+ }
+ for ( x = 0; (x | 0) < (size | 0); x = (x + 1) | 0) {
+ images[(imagePtr + y) | 0] = 0;
+ y = (y + 1) | 0;
}
+ }
+
+ function skeletonize() {
+ var subImagePtr = 0,
+ erodedImagePtr = 0,
+ tempImagePtr = 0,
+ skelImagePtr = 0,
+ sum = 0,
+ done = 0;
+
+ erodedImagePtr = imul(size, size) | 0;
+ tempImagePtr = (erodedImagePtr + erodedImagePtr) | 0;
+ skelImagePtr = (tempImagePtr + erodedImagePtr) | 0;
+
+ // init skel-image
+ init(skelImagePtr, 0);
+ zeroBorder(subImagePtr);
+
+ do {
+ erode(subImagePtr, erodedImagePtr);
+ dilate(erodedImagePtr, tempImagePtr);
+ subtract(subImagePtr, tempImagePtr, tempImagePtr);
+ bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);
+ memcpy(erodedImagePtr, subImagePtr);
+ sum = countNonZero(subImagePtr) | 0;
+ done = ((sum | 0) == 0 | 0);
+ } while(!done);
+ }
+
+ return {
+ skeletonize : skeletonize
+ };
+ }
+ /* @preserve ASM END */
+
+ return Skeletonizer;
+});
+
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
+define('image_debug',[],function() {
+ "use strict";
+
+ return {
+ drawRect: function(pos, size, ctx, style){
+ ctx.strokeStyle = style.color;
+ ctx.fillStyle = style.color;
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.strokeRect(pos.x, pos.y, size.x, size.y);
},
- right: function(dimension, context) {
- if (dimension.unit === "%") {
- return Math.floor(context.width - (context.width * (dimension.value / 100)));
+ drawPath: function(path, def, ctx, style) {
+ ctx.strokeStyle = style.color;
+ ctx.fillStyle = style.color;
+ ctx.lineWidth = style.lineWidth;
+ ctx.beginPath();
+ ctx.moveTo(path[0][def.x], path[0][def.y]);
+ for (var j = 1; j < path.length; j++) {
+ ctx.lineTo(path[j][def.x], path[j][def.y]);
}
+ ctx.closePath();
+ ctx.stroke();
},
- bottom: function(dimension, context) {
- if (dimension.unit === "%") {
- return Math.floor(context.height - (context.height * (dimension.value / 100)));
+ drawImage: function(imageData, size, ctx) {
+ var canvasData = ctx.getImageData(0, 0, size.x, size.y),
+ data = canvasData.data,
+ imageDataPos = imageData.length,
+ canvasDataPos = data.length,
+ value;
+
+ if (canvasDataPos/imageDataPos !== 4) {
+ return false;
}
- },
- left: function(dimension, context) {
- if (dimension.unit === "%") {
- return Math.floor(context.width * (dimension.value / 100));
+ while(imageDataPos--){
+ value = imageData[imageDataPos];
+ data[--canvasDataPos] = 255;
+ data[--canvasDataPos] = value;
+ data[--canvasDataPos] = value;
+ data[--canvasDataPos] = value;
}
+ ctx.putImageData(canvasData, 0, 0);
+ return true;
}
};
-
- CVUtils.computeImageArea = function(inputWidth, inputHeight, area) {
- var context = {width: inputWidth, height: inputHeight};
-
- var parsedArea = Object.keys(area).reduce(function(result, key) {
- var value = area[key],
- parsed = CVUtils._parseCSSDimensionValues(value),
- calculated = CVUtils._dimensionsConverters[key](parsed, context);
-
- result[key] = calculated;
- return result;
- }, {});
-
- return {
- sx: parsedArea.left,
- sy: parsedArea.top,
- sw: parsedArea.right - parsedArea.left,
- sh: parsedArea.bottom - parsedArea.top
- };
- };
-
- return (CVUtils);
+
});
-
/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define, vec2, mat2 */
+/* global define, mat2, vec2 */
-define('image_wrapper',[
- "subImage",
- "cv_utils",
- "array_helper"
- ],
- function(SubImage, CVUtils, ArrayHelper) {
-
- 'use strict';
+define("barcode_locator", ["image_wrapper", "cv_utils", "rasterizer", "tracer", "skeletonizer", "array_helper", "image_debug"],
+function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, ImageDebug) {
- /**
- * Represents a basic image combining the data and size.
- * In addition, some methods for manipulation are contained.
- * @param size {x,y} The size of the image in pixel
- * @param data {Array} If given, a flat array containing the pixel data
- * @param ArrayType {Type} If given, the desired DataType of the Array (may be typed/non-typed)
- * @param initialize {Boolean} Indicating if the array should be initialized on creation.
- * @returns {ImageWrapper}
- */
- function ImageWrapper(size, data, ArrayType, initialize) {
- if (!data) {
- if (ArrayType) {
- this.data = new ArrayType(size.x * size.y);
- if (ArrayType === Array && initialize) {
- ArrayHelper.init(this.data, 0);
- }
- } else {
- this.data = new Uint8Array(size.x * size.y);
- if (Uint8Array === Array && initialize) {
- ArrayHelper.init(this.data, 0);
- }
+ var _config,
+ _currentImageWrapper,
+ _skelImageWrapper,
+ _subImageWrapper,
+ _labelImageWrapper,
+ _patchGrid,
+ _patchLabelGrid,
+ _imageToPatchGrid,
+ _binaryImageWrapper,
+ _patchSize,
+ _canvasContainer = {
+ ctx : {
+ binary : null
+ },
+ dom : {
+ binary : null
}
+ },
+ _numPatches = {x: 0, y: 0},
+ _inputImageWrapper,
+ _skeletonizer,
+ self = this;
+ function initBuffers() {
+ var skeletonImageData;
+
+ if (_config.halfSample) {
+ _currentImageWrapper = new ImageWrapper({
+ x : _inputImageWrapper.size.x / 2 | 0,
+ y : _inputImageWrapper.size.y / 2 | 0
+ });
} else {
- this.data = data;
+ _currentImageWrapper = _inputImageWrapper;
}
- this.size = size;
- }
-
- /**
- * tests if a position is within the image with a given offset
- * @param imgRef {x, y} The location to test
- * @param border Number the padding value in pixel
- * @returns {Boolean} true if location inside the image's border, false otherwise
- * @see cvd/image.h
- */
- ImageWrapper.prototype.inImageWithBorder = function(imgRef, border) {
- return (imgRef.x >= border) && (imgRef.y >= border) && (imgRef.x < (this.size.x - border)) && (imgRef.y < (this.size.y - border));
- };
-
- /**
- * Transforms an image according to the given affine-transformation matrix.
- * @param inImg ImageWrapper a image containing the information to be extracted.
- * @param outImg ImageWrapper the image to be filled. The whole image out image is filled by the in image.
- * @param M mat2 the matrix used to map point in the out matrix to those in the in matrix
- * @param inOrig vec2 origin in the in image
- * @param outOrig vec2 origin in the out image
- * @returns Number the number of pixels not in the in image
- * @see cvd/vision.h
- */
- ImageWrapper.transform = function(inImg, outImg, M, inOrig, outOrig) {
- var w = outImg.size.x, h = outImg.size.y, iw = inImg.size.x, ih = inImg.size.y;
- var across = vec2.create([M[0], M[2]]);
- var down = vec2.create([M[1], M[3]]);
- var defaultValue = 0;
-
- var p0 = vec2.subtract(inOrig, mat2.xVec2(M, outOrig, vec2.create()), vec2.create());
-
- var min_x = p0[0], min_y = p0[1];
- var max_x = min_x, max_y = min_y;
- var p, i, j;
- var sampleFunc = ImageWrapper.sample;
+ _patchSize = CVUtils.calculatePatchSize(_config.patchSize, _currentImageWrapper.size);
- if (across[0] < 0)
- min_x += w * across[0];
- else
- max_x += w * across[0];
+ _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0;
+ _numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0;
- if (down[0] < 0)
- min_x += h * down[0];
- else
- max_x += h * down[0];
+ _binaryImageWrapper = new ImageWrapper(_currentImageWrapper.size, undefined, Uint8Array, false);
- if (across[1] < 0)
- min_y += w * across[1];
- else
- max_y += w * across[1];
+ _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true);
- if (down[1] < 0)
- min_y += h * down[1];
- else
- max_y += h * down[1];
+ 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(self, {
+ size : _patchSize.x
+ }, skeletonImageData);
- var carrigeReturn = vec2.subtract(down, vec2.scale(across, w, vec2.create()), vec2.create());
+ _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);
+ }
- if (min_x >= 0 && min_y >= 0 && max_x < iw - 1 && max_y < ih - 1) {
- p = p0;
- for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn))
- for ( j = 0; j < w; ++j, vec2.add(p, across))
- outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
- return 0;
- } else {
- var x_bound = iw - 1;
- var y_bound = ih - 1;
- var count = 0;
- p = p0;
- for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn)) {
- for ( j = 0; j < w; ++j, vec2.add(p, across)) {
- if (0 <= p[0] && 0 <= p[1] && p[0] < x_bound && p[1] < y_bound) {
- outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
- } else {
- outImg.set(j, i, defaultValue); ++count;
- }
- }
- }
- return count;
+ function initCanvas() {
+ if (_config.useWorker || typeof document === 'undefined') {
+ return;
}
- };
-
- /**
- * Performs bilinear sampling
- * @param inImg Image to extract sample from
- * @param x the x-coordinate
- * @param y the y-coordinate
- * @returns the sampled value
- * @see cvd/vision.h
- */
- ImageWrapper.sample = function(inImg, x, y) {
- var lx = Math.floor(x);
- var ly = Math.floor(y);
- var w = inImg.size.x;
- var base = ly * inImg.size.x + lx;
- var a = inImg.data[base + 0];
- var b = inImg.data[base + 1];
- var c = inImg.data[base + w];
- var d = inImg.data[base + w + 1];
- var e = a - b;
- x -= lx;
- y -= ly;
-
- var result = Math.floor(x * (y * (e - c + d) - e) + y * (c - a) + a);
- return result;
- };
-
- /**
- * Initializes a given array. Sets each element to zero.
- * @param array {Array} The array to initialize
- */
- ImageWrapper.clearArray = function(array) {
- var l = array.length;
- while (l--) {
- array[l] = 0;
+ _canvasContainer.dom.binary = document.createElement("canvas");
+ _canvasContainer.dom.binary.className = "binaryBuffer";
+ if (_config.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;
+ }
/**
- * Creates a {SubImage} from the current image ({this}).
- * @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
- * @param size {ImageRef} The size of the resulting image
- * @returns {SubImage} A shared part of the original image
+ * Creates a bounding box which encloses all the given patches
+ * @returns {Array} The minimal bounding box
*/
- ImageWrapper.prototype.subImage = function(from, size) {
- return new SubImage(from, size, this);
- };
+ 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;
- /**
- * Creates an {ImageWrapper) and copies the needed underlying image-data area
- * @param imageWrapper {ImageWrapper} The target {ImageWrapper} where the data should be copied
- * @param from {ImageRef} The location where to copy from (top-left location)
- */
- ImageWrapper.prototype.subImageAsCopy = function(imageWrapper, from) {
- var sizeY = imageWrapper.size.y, sizeX = imageWrapper.size.x;
- var x, y;
- for ( x = 0; x < sizeX; x++) {
- for ( y = 0; y < sizeY; y++) {
- imageWrapper.data[y * sizeX + x] = this.data[(from.y + y) * this.size.x + from.x + x];
+ // 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 (_config.showPatches) {
+ ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "red"});
}
}
- };
-
- ImageWrapper.prototype.copyTo = function(imageWrapper) {
- var length = this.data.length, srcData = this.data, dstData = imageWrapper.data;
- while (length--) {
- dstData[length] = srcData[length];
+ overAvg /= patches.length;
+ overAvg = (overAvg * 180 / Math.PI + 90) % 180 - 90;
+ if (overAvg < 0) {
+ overAvg += 180;
}
- };
- /**
- * Retrieves a given pixel position from the image
- * @param x {Number} The x-position
- * @param y {Number} The y-position
- * @returns {Number} The grayscale value at the pixel-position
- */
- ImageWrapper.prototype.get = function(x, y) {
- return this.data[y * this.size.x + x];
- };
+ //console.log(overAvg);
+ overAvg = (180 - overAvg) * Math.PI / 180;
+ transMat = mat2.create([Math.cos(overAvg), -Math.sin(overAvg), Math.sin(overAvg), Math.cos(overAvg)]);
- /**
- * Retrieves a given pixel position from the image
- * @param x {Number} The x-position
- * @param y {Number} The y-position
- * @returns {Number} The grayscale value at the pixel-position
- */
- ImageWrapper.prototype.getSafe = function(x, y) {
- var i;
-
- if (!this.indexMapping) {
- this.indexMapping = {
- x : [],
- y : []
- };
- for (i = 0; i < this.size.x; i++) {
- this.indexMapping.x[i] = i;
- this.indexMapping.x[i + this.size.x] = i;
+ // iterate over patches and rotate by angle
+ for ( i = 0; i < patches.length; i++) {
+ patch = patches[i];
+ for ( j = 0; j < 4; j++) {
+ mat2.xVec2(transMat, patch.box[j]);
}
- for (i = 0; i < this.size.y; i++) {
- this.indexMapping.y[i] = i;
- this.indexMapping.y[i + this.size.y] = i;
+
+ if (_config.boxFromPatches.showTransformed) {
+ ImageDebug.drawPath(patch.box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#99ff00', lineWidth: 2});
}
}
- return this.data[(this.indexMapping.y[y + this.size.y]) * this.size.x + this.indexMapping.x[x + this.size.x]];
- };
-
- /**
- * Sets a given pixel position in the image
- * @param x {Number} The x-position
- * @param y {Number} The y-position
- * @param value {Number} The grayscale value to set
- * @returns {ImageWrapper} The Image itself (for possible chaining)
- */
- ImageWrapper.prototype.set = function(x, y, value) {
- this.data[y * this.size.x + x] = value;
- return this;
- };
- /**
- * Sets the border of the image (1 pixel) to zero
- */
- ImageWrapper.prototype.zeroBorder = function() {
- var i, width = this.size.x, height = this.size.y, data = this.data;
- for ( i = 0; i < width; i++) {
- data[i] = data[(height - 1) * width + i] = 0;
- }
- for ( i = 1; i < height - 1; i++) {
- data[i * width] = data[i * width + (width - 1)] = 0;
+ // 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];
+ }
+ }
}
- };
- /**
- * Inverts a binary image in place
- */
- ImageWrapper.prototype.invert = function() {
- var data = this.data, length = data.length;
+ box = [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]];
- while (length--) {
- data[length] = data[length] ? 0 : 1;
+ if (_config.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.inverse(transMat);
+ for ( j = 0; j < 4; j++) {
+ mat2.xVec2(transMat, box[j]);
+ }
- ImageWrapper.prototype.convolve = function(kernel) {
- var x, y, kx, ky, kSize = (kernel.length / 2) | 0, accu = 0;
- for ( y = 0; y < this.size.y; y++) {
- for ( x = 0; x < this.size.x; x++) {
- accu = 0;
- for ( ky = -kSize; ky <= kSize; ky++) {
- for ( kx = -kSize; kx <= kSize; kx++) {
- accu += kernel[ky+kSize][kx + kSize] * this.getSafe(x + kx, y + ky);
- }
- }
- this.data[y * this.size.x + x] = accu;
- }
+ if (_config.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], scale);
}
- };
- ImageWrapper.prototype.moments = function(labelcount) {
- var data = this.data,
+ return box;
+ }
+
+ /**
+ * Creates a binary image of the current image
+ */
+ function binarizeImage() {
+ CVUtils.otsuThreshold(_currentImageWrapper, _binaryImageWrapper);
+ _binaryImageWrapper.zeroBorder();
+ if (_config.showCanvas) {
+ _binaryImageWrapper.show(_canvasContainer.dom.binary, 255);
+ }
+ }
+
+ /**
+ * Iterate over the entire image
+ * extract patches
+ */
+ function findPatches() {
+ var i,
+ j,
x,
y,
- height = this.size.y,
- width = this.size.x,
- val,
- ysq,
- labelsum = [],
- i,
- label,
- mu11,
- mu02,
- mu20,
- x_,
- y_,
- tmp,
- result = [],
- PI = Math.PI,
- PI_4 = PI / 4;
+ moments,
+ patchesFound = [],
+ rasterizer,
+ rasterResult,
+ patch;
+ for ( i = 0; i < _numPatches.x; i++) {
+ for ( j = 0; j < _numPatches.y; j++) {
- if (labelcount <= 0) {
- return result;
- }
+ x = _subImageWrapper.size.x * i;
+ y = _subImageWrapper.size.y * j;
- for ( i = 0; i < labelcount; i++) {
- labelsum[i] = {
- m00 : 0,
- m01 : 0,
- m10 : 0,
- m11 : 0,
- m02 : 0,
- m20 : 0,
- theta : 0,
- rad : 0
- };
- }
+ // seperate parts
+ skeletonize(x, y);
- for ( y = 0; y < height; y++) {
- ysq = y * y;
- for ( x = 0; x < width; x++) {
- val = data[y * width + x];
- if (val > 0) {
- label = labelsum[val - 1];
- label.m00 += 1;
- label.m01 += y;
- label.m10 += x;
- label.m11 += x * y;
- label.m02 += ysq;
- label.m20 += x * x;
- }
- }
- }
+ // Rasterize, find individual bars
+ _skelImageWrapper.zeroBorder();
+ ArrayHelper.init(_labelImageWrapper.data, 0);
+ rasterizer = Rasterizer.create(_skelImageWrapper, _labelImageWrapper);
+ rasterResult = rasterizer.rasterize(0);
- for ( i = 0; i < labelcount; i++) {
- label = labelsum[i];
- if (!isNaN(label.m00) && label.m00 !== 0) {
- x_ = label.m10 / label.m00;
- y_ = label.m01 / label.m00;
- mu11 = label.m11 / label.m00 - x_ * y_;
- mu02 = label.m02 / label.m00 - y_ * y_;
- mu20 = label.m20 / label.m00 - x_ * x_;
- tmp = (mu02 - mu20) / (2 * mu11);
- tmp = 0.5 * Math.atan(tmp) + (mu11 >= 0 ? PI_4 : -PI_4 ) + PI;
- label.theta = (tmp * 180 / PI + 90) % 180 - 90;
- if (label.theta < 0) {
- label.theta += 180;
+ if (_config.showLabels) {
+ _labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), {x : x, y : y});
}
- label.rad = tmp > PI ? tmp - PI : tmp;
- label.vec = vec2.create([Math.cos(tmp), Math.sin(tmp)]);
- result.push(label);
- }
- }
- return result;
- };
+ // calculate moments from the skeletonized patch
+ moments = _labelImageWrapper.moments(rasterResult.count);
- /**
- * Displays the {ImageWrapper} in a given canvas
- * @param canvas {Canvas} The canvas element to write to
- * @param scale {Number} Scale which is applied to each pixel-value
- */
- ImageWrapper.prototype.show = function(canvas, scale) {
- var ctx,
- frame,
- data,
- current,
- pixel,
- x,
- y;
-
- if (!scale) {
- scale = 1.0;
+ // extract eligible patches
+ patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y));
+ }
}
- ctx = canvas.getContext('2d');
- canvas.width = this.size.x;
- canvas.height = this.size.y;
- frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
- data = frame.data;
- current = 0;
- for (y = 0; y < this.size.y; y++) {
- for (x = 0; x < this.size.x; x++) {
- pixel = y * this.size.x + x;
- current = this.get(x, y) * scale;
- data[pixel * 4 + 0] = current;
- data[pixel * 4 + 1] = current;
- data[pixel * 4 + 2] = current;
- data[pixel * 4 + 3] = 255;
+
+ if (_config.showFoundPatches) {
+ for ( i = 0; i < patchesFound.length; i++) {
+ patch = patchesFound[i];
+ ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "#99ff00", lineWidth: 2});
}
}
- //frame.data = data;
- ctx.putImageData(frame, 0, 0);
- };
-
+
+ return patchesFound;
+ }
+
/**
- * Displays the {SubImage} in a given canvas
- * @param canvas {Canvas} The canvas element to write to
- * @param scale {Number} Scale which is applied to each pixel-value
+ * Finds those connected areas which contain at least 6 patches
+ * and returns them ordered DESC by the number of contained patches
+ * @param {Number} maxLabel
*/
- ImageWrapper.prototype.overlay = function(canvas, scale, from) {
- if (!scale || scale < 0 || scale > 360) {
- scale = 360;
+ function findBiggestConnectedAreas(maxLabel){
+ var i,
+ sum,
+ labelHist = [],
+ topLabels = [];
+
+ for ( i = 0; i < maxLabel; i++) {
+ labelHist.push(0);
}
- var hsv = [0, 1, 1];
- var rgb = [0, 0, 0];
- var whiteRgb = [255, 255, 255];
- var blackRgb = [0, 0, 0];
- var result = [];
- var ctx = canvas.getContext('2d');
- var frame = ctx.getImageData(from.x, from.y, this.size.x, this.size.y);
- var data = frame.data;
- var length = this.data.length;
- while (length--) {
- hsv[0] = this.data[length] * scale;
- result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb);
- data[length * 4 + 0] = result[0];
- data[length * 4 + 1] = result[1];
- data[length * 4 + 2] = result[2];
- data[length * 4 + 3] = 255;
+ sum = _patchLabelGrid.data.length;
+ while (sum--) {
+ if (_patchLabelGrid.data[sum] > 0) {
+ labelHist[_patchLabelGrid.data[sum] - 1]++;
+ }
}
- ctx.putImageData(frame, from.x, from.y);
- };
-
- return (ImageWrapper);
-});
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-/**
- * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
- */
-define('tracer',[],function() {
- "use strict";
-
- var Tracer = {
- searchDirections : [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]],
- create : function(imageWrapper, labelWrapper) {
- var imageData = imageWrapper.data,
- labelData = labelWrapper.data,
- searchDirections = this.searchDirections,
- width = imageWrapper.size.x,
- pos;
+ labelHist = labelHist.map(function(val, idx) {
+ return {
+ val : val,
+ label : idx + 1
+ };
+ });
- function trace(current, color, label, edgelabel) {
- var i,
- y,
- x;
+ labelHist.sort(function(a, b) {
+ return b.val - a.val;
+ });
- for ( i = 0; i < 7; i++) {
- y = current.cy + searchDirections[current.dir][0];
- x = current.cx + searchDirections[current.dir][1];
- pos = y * width + x;
- if ((imageData[pos] === color) && ((labelData[pos] === 0) || (labelData[pos] === label))) {
- labelData[pos] = label;
- current.cy = y;
- current.cx = x;
- return true;
- } else {
- if (labelData[pos] === 0) {
- labelData[pos] = edgelabel;
- }
- current.dir = (current.dir + 1) % 8;
- }
+ // extract top areas with at least 6 patches present
+ topLabels = labelHist.filter(function(el) {
+ return el.val >= 5;
+ });
+
+ 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);
}
- return false;
- }
-
- function vertex2D(x, y, dir) {
- return {
- dir : dir,
- x : x,
- y : y,
- next : null,
- prev : null
- };
}
+ box = boxFromPatches(patches);
+ if (box) {
+ boxes.push(box);
- function contourTracing(sy, sx, label, color, edgelabel) {
- var Fv = null,
- Cv,
- P,
- ldir,
- current = {
- cx : sx,
- cy : sy,
- dir : 0
- };
-
- if (trace(current, color, label, edgelabel)) {
- Fv = vertex2D(sx, sy, current.dir);
- Cv = Fv;
- ldir = current.dir;
- P = vertex2D(current.cx, current.cy, 0);
- P.prev = Cv;
- Cv.next = P;
- P.next = null;
- Cv = P;
- do {
- current.dir = (current.dir + 6) % 8;
- trace(current, color, label, edgelabel);
- if (ldir != current.dir) {
- Cv.dir = current.dir;
- P = vertex2D(current.cx, current.cy, 0);
- P.prev = Cv;
- Cv.next = P;
- P.next = null;
- Cv = P;
- } else {
- Cv.dir = ldir;
- Cv.x = current.cx;
- Cv.y = current.cy;
- }
- ldir = current.dir;
- } while(current.cx != sx || current.cy != sy);
- Fv.prev = Cv.prev;
- Cv.prev.next = Fv;
+ // draw patch-labels if requested
+ if (_config.showRemainingPatchLabels) {
+ for ( j = 0; j < patches.length; j++) {
+ patch = patches[j];
+ hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360;
+ CVUtils.hsv2rgb(hsv, rgb);
+ ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
+ }
}
- return Fv;
}
+ }
+ return boxes;
+ }
- return {
- trace : function(current, color, label, edgelabel) {
- return trace(current, color, label, edgelabel);
- },
- contourTracing : function(sy, sx, label, color, edgelabel) {
- return contourTracing(sy, sx, label, color, edgelabel);
- }
- };
+ /**
+ * Find similar moments (via cluster)
+ * @param {Object} moments
+ */
+ function similarMoments(moments) {
+ var clusters = CVUtils.cluster(moments, 0.90);
+ var topCluster = CVUtils.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 (Tracer);
-});
-
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+ return result;
+ }
-/**
- * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
- */
-define('rasterizer',["tracer"], function(Tracer) {
- "use strict";
+ function skeletonize(x, y) {
+ _binaryImageWrapper.subImageAsCopy(_subImageWrapper, CVUtils.imageRef(x, y));
+ _skeletonizer.skeletonize();
+
+ // Show skeleton if requested
+ if (_config.showSkeleton) {
+ _skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, CVUtils.imageRef(x, y));
+ }
+ }
- var Rasterizer = {
- createContour2D : function() {
- return {
- dir : null,
- index : null,
- firstVertex : null,
- insideContours : null,
- nextpeer : null,
- prevpeer : null
- };
- },
- CONTOUR_DIR : {
- CW_DIR : 0,
- CCW_DIR : 1,
- UNKNOWN_DIR : 2
- },
- DIR : {
- OUTSIDE_EDGE : -32767,
- INSIDE_EDGE : -32766
- },
- create : function(imageWrapper, labelWrapper) {
- var imageData = imageWrapper.data,
- labelData = labelWrapper.data,
- width = imageWrapper.size.x,
- height = imageWrapper.size.y,
- tracer = Tracer.create(imageWrapper, labelWrapper);
+ /**
+ * 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,
+ sum = 0,
+ eligibleMoments = [],
+ matchingMoments,
+ patch,
+ patchesFound = [],
+ minComponentWeight = Math.ceil(_patchSize.x/3);
- return {
- rasterize : function(depthlabel) {
- var color,
- bc,
- lc,
- labelindex,
- cx,
- cy,
- colorMap = [],
- vertex,
- p,
- cc,
- sc,
- pos,
- connectedCount = 0,
- i;
+ 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]);
+ }
+ }
- for ( i = 0; i < 400; i++) {
- colorMap[i] = 0;
- }
+ // if at least 2 moments are found which have at least minComponentWeights covered
+ if (eligibleMoments.length >= 2) {
+ sum = eligibleMoments.length;
+ matchingMoments = similarMoments(eligibleMoments);
+ avg = 0;
+ // determine the similarity of the moments
+ for ( k = 0; k < matchingMoments.length; k++) {
+ avg += matchingMoments[k].rad;
+ }
- colorMap[0] = imageData[0];
- cc = null;
- for ( cy = 1; cy < height - 1; cy++) {
- labelindex = 0;
- bc = colorMap[0];
- for ( cx = 1; cx < width - 1; cx++) {
- pos = cy * width + cx;
- if (labelData[pos] === 0) {
- color = imageData[pos];
- if (color !== bc) {
- if (labelindex === 0) {
- lc = connectedCount + 1;
- colorMap[lc] = color;
- bc = color;
- vertex = tracer.contourTracing(cy, cx, lc, color, Rasterizer.DIR.OUTSIDE_EDGE);
- if (vertex !== null) {
- connectedCount++;
- labelindex = lc;
- p = Rasterizer.createContour2D();
- p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
- p.index = labelindex;
- p.firstVertex = vertex;
- p.nextpeer = cc;
- p.insideContours = null;
- if (cc !== null) {
- cc.prevpeer = p;
- }
- cc = p;
- }
- } else {
- vertex = tracer.contourTracing(cy, cx, Rasterizer.DIR.INSIDE_EDGE, color, labelindex);
- if (vertex !== null) {
- p = Rasterizer.createContour2D();
- p.firstVertex = vertex;
- p.insideContours = null;
- if (depthlabel === 0) {
- p.dir = Rasterizer.CONTOUR_DIR.CCW_DIR;
- } else {
- p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
- }
- p.index = depthlabel;
- sc = cc;
- while ((sc !== null) && sc.index !== labelindex) {
- sc = sc.nextpeer;
- }
- if (sc !== null) {
- p.nextpeer = sc.insideContours;
- if (sc.insideContours !== null) {
- sc.insideContours.prevpeer = p;
- }
- sc.insideContours = p;
- }
- }
- }
- } else {
- labelData[pos] = labelindex;
- }
- } else if (labelData[pos] === Rasterizer.DIR.OUTSIDE_EDGE || labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
- labelindex = 0;
- if (labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
- bc = imageData[pos];
- } else {
- bc = colorMap[0];
- }
- } else {
- labelindex = labelData[pos];
- bc = colorMap[labelindex];
- }
- }
- }
- sc = cc;
- while (sc !== null) {
- sc.index = depthlabel;
- sc = sc.nextpeer;
- }
- return {
- cc : cc,
- count : connectedCount
+ // 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.create([x, y]), vec2.create([x + _subImageWrapper.size.x, y]), vec2.create([x + _subImageWrapper.size.x, y + _subImageWrapper.size.y]), vec2.create([x, y + _subImageWrapper.size.y])],
+ moments : matchingMoments,
+ rad : avg,
+ vec : vec2.create([Math.cos(avg), Math.sin(avg)])
};
- },
- debug: {
- drawContour : function(canvas, firstContour) {
- var ctx = canvas.getContext("2d"),
- pq = firstContour,
- iq,
- q,
- p;
-
- ctx.strokeStyle = "red";
- ctx.fillStyle = "red";
- ctx.lineWidth = 1;
-
- if (pq !== null) {
- iq = pq.insideContours;
- } else {
- iq = null;
- }
-
- while (pq !== null) {
- if (iq !== null) {
- q = iq;
- iq = iq.nextpeer;
- } else {
- q = pq;
- pq = pq.nextpeer;
- if (pq !== null) {
- iq = pq.insideContours;
- } else {
- iq = null;
- }
- }
-
- switch(q.dir) {
- case Rasterizer.CONTOUR_DIR.CW_DIR:
- ctx.strokeStyle = "red";
- break;
- case Rasterizer.CONTOUR_DIR.CCW_DIR:
- ctx.strokeStyle = "blue";
- break;
- case Rasterizer.CONTOUR_DIR.UNKNOWN_DIR:
- ctx.strokeStyle = "green";
- break;
- }
-
- p = q.firstVertex;
- ctx.beginPath();
- ctx.moveTo(p.x, p.y);
- do {
- p = p.next;
- ctx.lineTo(p.x, p.y);
- } while(p !== q.firstVertex);
- ctx.stroke();
- }
- }
+ patchesFound.push(patch);
}
- };
+ }
}
- };
+ return patchesFound;
+ }
- return (Rasterizer);
-});
+ /**
+ * 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];
-/* jshint undef: true, unused: true, browser:true, devel: true, -W041: false */
-/* global define */
+ 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;
+ }
-define('skeletonizer',[],function() {
- "use strict";
+ function trace(currentIdx) {
+ var x, y, currentPatch, patch, idx, dir, current = {
+ x : currentIdx % _patchLabelGrid.size.x,
+ y : (currentIdx / _patchLabelGrid.size.x) | 0
+ }, similarity;
- /* @preserve ASM BEGIN */
- function Skeletonizer(stdlib, foreign, buffer) {
- "use asm";
+ 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;
- var images = new stdlib.Uint8Array(buffer),
- size = foreign.size | 0,
- imul = stdlib.Math.imul;
+ // continue if patch empty
+ if (_patchGrid.data[idx] === 0) {
+ _patchLabelGrid.data[idx] = Number.MAX_VALUE;
+ continue;
+ }
- function erode(inImagePtr, outImagePtr) {
- inImagePtr = inImagePtr | 0;
- outImagePtr = outImagePtr | 0;
+ patch = _imageToPatchGrid.data[idx];
+ if (_patchLabelGrid.data[idx] === 0) {
+ similarity = Math.abs(vec2.dot(patch.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);
- var v = 0,
- u = 0,
- sum = 0,
- yStart1 = 0,
- yStart2 = 0,
- xStart1 = 0,
- xStart2 = 0,
- offset = 0;
+ for ( j = 0; j < patchesFound.length; j++) {
+ patch = patchesFound[j];
+ _imageToPatchGrid.data[patch.index] = patch;
+ _patchGrid.data[patch.index] = 1;
+ }
- for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
- offset = (offset + size) | 0;
- for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
- yStart1 = (offset - size) | 0;
- yStart2 = (offset + size) | 0;
- xStart1 = (u - 1) | 0;
- xStart2 = (u + 1) | 0;
- sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
- if ((sum | 0) == (5 | 0)) {
- images[(outImagePtr + offset + u) | 0] = 1;
- } else {
- images[(outImagePtr + offset + u) | 0] = 0;
- }
+ // rasterize the patches found to determine area
+ _patchGrid.zeroBorder();
+
+ while (( currIdx = notYetProcessed()) < _patchLabelGrid.data.length) {
+ label++;
+ trace(currIdx);
+ }
+
+ // draw patch-labels if requested
+ if (_config.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;
+ CVUtils.hsv2rgb(hsv, rgb);
+ ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
}
}
- return;
}
+
+ return label;
+ }
- function subtract(aImagePtr, bImagePtr, outImagePtr) {
- aImagePtr = aImagePtr | 0;
- bImagePtr = bImagePtr | 0;
- outImagePtr = outImagePtr | 0;
+ return {
+ init : function(inputImageWrapper, config) {
+ _config = config;
+ _inputImageWrapper = inputImageWrapper;
- var length = 0;
+ initBuffers();
+ initCanvas();
+ },
- length = imul(size, size) | 0;
+ locate : function() {
+ var patchesFound,
+ topLabels,
+ boxes;
- while ((length | 0) > 0) {
- length = (length - 1) | 0;
- images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) - (images[(bImagePtr + length) | 0] | 0)) | 0;
+ if (_config.halfSample) {
+ CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
}
- }
- function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) {
- aImagePtr = aImagePtr | 0;
- bImagePtr = bImagePtr | 0;
- outImagePtr = outImagePtr | 0;
+ binarizeImage();
+ patchesFound = findPatches();
+ // return unless 5% or more patches are found
+ if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
+ return null;
+ }
- var length = 0;
+ // rasterrize area by comparing angular similarity;
+ var maxLabel = rasterizeAngularSimilarity(patchesFound);
+ if (maxLabel < 1) {
+ return null;
+ }
- length = imul(size, size) | 0;
+ // search for area with the most patches (biggest connected area)
+ topLabels = findBiggestConnectedAreas(maxLabel);
+ if (topLabels.length === 0) {
+ return null;
+ }
- while ((length | 0) > 0) {
- length = (length - 1) | 0;
- images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) | (images[(bImagePtr + length) | 0] | 0)) | 0;
+ 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 = CVUtils.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;
}
- }
- function countNonZero(imagePtr) {
- imagePtr = imagePtr | 0;
+ size = {
+ x: Math.floor(width * halfSample),
+ y: Math.floor(height * halfSample)
+ };
- var sum = 0,
- length = 0;
+ patchSize = CVUtils.calculatePatchSize(config.patchSize, size);
+ console.log("Patch-Size: " + JSON.stringify(patchSize));
- length = imul(size, size) | 0;
+ 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));
- while ((length | 0) > 0) {
- length = (length - 1) | 0;
- sum = ((sum | 0) + (images[(imagePtr + length) | 0] | 0)) | 0;
+ if ((inputStream.getWidth() % patchSize.x) === 0 && (inputStream.getHeight() % patchSize.y) === 0) {
+ return true;
}
- return (sum | 0);
+ throw new Error("Image dimensions do not comply with the current settings: Width (" +
+ width + " )and height (" + height +
+ ") must a multiple of " + patchSize.x);
}
+ };
+});
- function init(imagePtr, value) {
- imagePtr = imagePtr | 0;
- value = value | 0;
- var length = 0;
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- length = imul(size, size) | 0;
+define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) {
+ "use strict";
+ var Bresenham = {};
+
+ var Slope = {
+ DIR : {
+ UP : 1,
+ DOWN : -1
+ }
+ };
+ /**
+ * Scans a line of the given image from point p1 to p2 and returns a result object containing
+ * gray-scale values (0-255) of the underlying pixels in addition to the min
+ * and max values.
+ * @param {Object} imageWrapper
+ * @param {Object} p1 The start point {x,y}
+ * @param {Object} p2 The end point {x,y}
+ * @returns {line, min, max}
+ */
+ Bresenham.getBarcodeLine = function(imageWrapper, p1, p2) {
+ var x0 = p1.x | 0,
+ y0 = p1.y | 0,
+ x1 = p2.x | 0,
+ y1 = p2.y | 0,
+ steep = Math.abs(y1 - y0) > Math.abs(x1 - x0),
+ deltax,
+ deltay,
+ error,
+ ystep,
+ y,
+ tmp,
+ x,
+ line = [],
+ imageData = imageWrapper.data,
+ width = imageWrapper.size.x,
+ sum = 0,
+ val,
+ min = 255,
+ max = 0;
- while ((length | 0) > 0) {
- length = (length - 1) | 0;
- images[(imagePtr + length) | 0] = value;
- }
+ function read(a, b) {
+ val = imageData[b * width + a];
+ sum += val;
+ min = val < min ? val : min;
+ max = val > max ? val : max;
+ line.push(val);
}
- function dilate(inImagePtr, outImagePtr) {
- inImagePtr = inImagePtr | 0;
- outImagePtr = outImagePtr | 0;
+ if (steep) {
+ tmp = x0;
+ x0 = y0;
+ y0 = tmp;
- var v = 0,
- u = 0,
- sum = 0,
- yStart1 = 0,
- yStart2 = 0,
- xStart1 = 0,
- xStart2 = 0,
- offset = 0;
+ tmp = x1;
+ x1 = y1;
+ y1 = tmp;
+ }
+ if (x0 > x1) {
+ tmp = x0;
+ x0 = x1;
+ x1 = tmp;
- for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
- offset = (offset + size) | 0;
- for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
- yStart1 = (offset - size) | 0;
- yStart2 = (offset + size) | 0;
- xStart1 = (u - 1) | 0;
- xStart2 = (u + 1) | 0;
- sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
- if ((sum | 0) > (0 | 0)) {
- images[(outImagePtr + offset + u) | 0] = 1;
- } else {
- images[(outImagePtr + offset + u) | 0] = 0;
- }
- }
+ tmp = y0;
+ y0 = y1;
+ y1 = tmp;
+ }
+ deltax = x1 - x0;
+ deltay = Math.abs(y1 - y0);
+ error = (deltax / 2) | 0;
+ y = y0;
+ ystep = y0 < y1 ? 1 : -1;
+ for ( x = x0; x < x1; x++) {
+ if(steep){
+ read(y, x);
+ } else {
+ read(x, y);
+ }
+ error = error - deltay;
+ if (error < 0) {
+ y = y + ystep;
+ error = error + deltax;
}
- return;
}
- function memcpy(srcImagePtr, dstImagePtr) {
- srcImagePtr = srcImagePtr | 0;
- dstImagePtr = dstImagePtr | 0;
-
- var length = 0;
+ return {
+ line : line,
+ min : min,
+ max : max
+ };
+ };
- length = imul(size, size) | 0;
+ Bresenham.toOtsuBinaryLine = function(result) {
+ var line = result.line,
+ image = new ImageWrapper({x: line.length - 1, y: 1}, line),
+ threshold = CVUtils.determineOtsuThreshold(image, 5);
- while ((length | 0) > 0) {
- length = (length - 1) | 0;
- images[(dstImagePtr + length) | 0] = (images[(srcImagePtr + length) | 0] | 0);
- }
- }
+ line = CVUtils.sharpenLine(line);
+ CVUtils.thresholdImage(image, threshold);
- function zeroBorder(imagePtr) {
- imagePtr = imagePtr | 0;
+ return {
+ line: line,
+ threshold: threshold
+ };
+ };
+
+ /**
+ * Converts the result from getBarcodeLine into a binary representation
+ * also considering the frequency and slope of the signal for more robust results
+ * @param {Object} result {line, min, max}
+ */
+ Bresenham.toBinaryLine = function(result) {
- var x = 0,
- y = 0;
+ var min = result.min,
+ max = result.max,
+ line = result.line,
+ slope,
+ slope2,
+ center = min + (max - min) / 2,
+ extrema = [],
+ currentDir,
+ dir,
+ threshold = (max - min) / 12,
+ rThreshold = -threshold,
+ i,
+ j;
- for ( x = 0; (x | 0) < ((size - 1) | 0); x = (x + 1) | 0) {
- images[(imagePtr + x) | 0] = 0;
- images[(imagePtr + y) | 0] = 0;
- y = ((y + size) - 1) | 0;
- images[(imagePtr + y) | 0] = 0;
- y = (y + 1) | 0;
+ // 1. find extrema
+ currentDir = line[0] > center ? Slope.DIR.UP : Slope.DIR.DOWN;
+ extrema.push({
+ pos : 0,
+ val : line[0]
+ });
+ for ( i = 0; i < line.length - 2; i++) {
+ slope = (line[i + 1] - line[i]);
+ slope2 = (line[i + 2] - line[i + 1]);
+ if ((slope + slope2) < rThreshold && line[i + 1] < (center*1.5)) {
+ dir = Slope.DIR.DOWN;
+ } else if ((slope + slope2) > threshold && line[i + 1] > (center*0.5)) {
+ dir = Slope.DIR.UP;
+ } else {
+ dir = currentDir;
}
- for ( x = 0; (x | 0) < (size | 0); x = (x + 1) | 0) {
- images[(imagePtr + y) | 0] = 0;
- y = (y + 1) | 0;
+
+ if (currentDir !== dir) {
+ extrema.push({
+ pos : i,
+ val : line[i]
+ });
+ currentDir = dir;
}
}
+ extrema.push({
+ pos : line.length,
+ val : line[line.length - 1]
+ });
- function skeletonize() {
- var subImagePtr = 0,
- erodedImagePtr = 0,
- tempImagePtr = 0,
- skelImagePtr = 0,
- sum = 0,
- done = 0;
-
- erodedImagePtr = imul(size, size) | 0;
- tempImagePtr = (erodedImagePtr + erodedImagePtr) | 0;
- skelImagePtr = (tempImagePtr + erodedImagePtr) | 0;
+ for ( j = extrema[0].pos; j < extrema[1].pos; j++) {
+ line[j] = line[j] > center ? 0 : 1;
+ }
- // init skel-image
- init(skelImagePtr, 0);
- zeroBorder(subImagePtr);
+ // iterate over extrema and convert to binary based on avg between minmax
+ for ( i = 1; i < extrema.length - 1; i++) {
+ if (extrema[i + 1].val > extrema[i].val) {
+ threshold = (extrema[i].val + ((extrema[i + 1].val - extrema[i].val) / 3) * 2) | 0;
+ } else {
+ threshold = (extrema[i + 1].val + ((extrema[i].val - extrema[i + 1].val) / 3)) | 0;
+ }
- do {
- erode(subImagePtr, erodedImagePtr);
- dilate(erodedImagePtr, tempImagePtr);
- subtract(subImagePtr, tempImagePtr, tempImagePtr);
- bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);
- memcpy(erodedImagePtr, subImagePtr);
- sum = countNonZero(subImagePtr) | 0;
- done = ((sum | 0) == 0 | 0);
- } while(!done);
+ for ( j = extrema[i].pos; j < extrema[i + 1].pos; j++) {
+ line[j] = line[j] > threshold ? 0 : 1;
+ }
}
return {
- skeletonize : skeletonize
+ line : line,
+ threshold : threshold
};
- }
- /* @preserve ASM END */
-
- return Skeletonizer;
-});
-
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-
-define('image_debug',[],function() {
- "use strict";
+ };
+
+ /**
+ * Used for development only
+ */
+ Bresenham.debug = {
+ printFrequency: function(line, canvas) {
+ var i,
+ ctx = canvas.getContext("2d");
+ canvas.width = line.length;
+ canvas.height = 256;
- return {
- drawRect: function(pos, size, ctx, style){
- ctx.strokeStyle = style.color;
- ctx.fillStyle = style.color;
- ctx.lineWidth = 1;
- ctx.beginPath();
- ctx.strokeRect(pos.x, pos.y, size.x, size.y);
- },
- drawPath: function(path, def, ctx, style) {
- ctx.strokeStyle = style.color;
- ctx.fillStyle = style.color;
- ctx.lineWidth = style.lineWidth;
ctx.beginPath();
- ctx.moveTo(path[0][def.x], path[0][def.y]);
- for (var j = 1; j < path.length; j++) {
- ctx.lineTo(path[j][def.x], path[j][def.y]);
+ ctx.strokeStyle = "blue";
+ for ( i = 0; i < line.length; i++) {
+ ctx.moveTo(i, 255);
+ ctx.lineTo(i, 255 - line[i]);
}
- ctx.closePath();
ctx.stroke();
+ ctx.closePath();
},
- drawImage: function(imageData, size, ctx) {
- var canvasData = ctx.getImageData(0, 0, size.x, size.y),
- data = canvasData.data,
- imageDataPos = imageData.length,
- canvasDataPos = data.length,
- value;
-
- if (canvasDataPos/imageDataPos !== 4) {
- return false;
- }
- while(imageDataPos--){
- value = imageData[imageDataPos];
- data[--canvasDataPos] = 255;
- data[--canvasDataPos] = value;
- data[--canvasDataPos] = value;
- data[--canvasDataPos] = value;
+
+ printPattern: function(line, canvas) {
+ var ctx = canvas.getContext("2d"), i;
+
+ canvas.width = line.length;
+ ctx.fillColor = "black";
+ for ( i = 0; i < line.length; i++) {
+ if (line[i] === 1) {
+ ctx.fillRect(i, 0, 1, 100);
+ }
}
- ctx.putImageData(canvasData, 0, 0);
- return true;
}
};
-
-});
+ return (Bresenham);
+});
/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define, mat2, vec2 */
-
-define("barcode_locator", ["image_wrapper", "cv_utils", "rasterizer", "tracer", "skeletonizer", "array_helper", "image_debug"],
-function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, ImageDebug) {
-
- var _config,
- _currentImageWrapper,
- _skelImageWrapper,
- _subImageWrapper,
- _labelImageWrapper,
- _patchGrid,
- _patchLabelGrid,
- _imageToPatchGrid,
- _binaryImageWrapper,
- _patchSize,
- _canvasContainer = {
- ctx : {
- binary : null
- },
- dom : {
- binary : null
- }
- },
- _numPatches = {x: 0, y: 0},
- _inputImageWrapper,
- _skeletonizer,
- self = this;
+/* global define */
- function initBuffers() {
- var skeletonImageData;
+define(
+ 'barcode_reader',[],function() {
+ "use strict";
- if (_config.halfSample) {
- _currentImageWrapper = new ImageWrapper({
- x : _inputImageWrapper.size.x / 2 | 0,
- y : _inputImageWrapper.size.y / 2 | 0
- });
- } else {
- _currentImageWrapper = _inputImageWrapper;
+ function BarcodeReader(config) {
+ this._row = [];
+ this.config = config || {};
+ return this;
}
+
+ BarcodeReader.prototype._nextUnset = function(line, start) {
+ var i;
+
+ if (start === undefined) {
+ start = 0;
+ }
+ for (i = start; i < line.length; i++) {
+ if (!line[i]) {
+ return i;
+ }
+ }
+ return line.length;
+ };
+
+ BarcodeReader.prototype._matchPattern = function(counter, code) {
+ var i,
+ error = 0,
+ singleError = 0,
+ modulo = this.MODULO,
+ maxSingleError = this.SINGLE_CODE_ERROR || 1;
+
+ for (i = 0; i < counter.length; i++) {
+ singleError = Math.abs(code[i] - counter[i]);
+ if (singleError > maxSingleError) {
+ return Number.MAX_VALUE;
+ }
+ error += singleError;
+ }
+ return error/modulo;
+ };
- _patchSize = CVUtils.calculatePatchSize(_config.patchSize, _currentImageWrapper.size);
-
- _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0;
- _numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0;
-
- _binaryImageWrapper = new ImageWrapper(_currentImageWrapper.size, undefined, Uint8Array, false);
-
- _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true);
+ BarcodeReader.prototype._nextSet = function(line, offset) {
+ var i;
- 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(self, {
- size : _patchSize.x
- }, skeletonImageData);
+ offset = offset || 0;
+ for (i = offset; i < line.length; i++) {
+ if (line[i]) {
+ return i;
+ }
+ }
+ return line.length;
+ };
- _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);
- }
+ BarcodeReader.prototype._normalize = function(counter, modulo) {
+ var i,
+ self = this,
+ sum = 0,
+ ratio,
+ numOnes = 0,
+ normalized = [],
+ norm = 0;
+
+ if (!modulo) {
+ modulo = self.MODULO;
+ }
+ for (i = 0; i < counter.length; i++) {
+ if (counter[i] === 1) {
+ numOnes++;
+ } else {
+ sum += counter[i];
+ }
+ }
+ ratio = sum / (modulo - numOnes);
+ if (ratio > 1.0) {
+ for (i = 0; i < counter.length; i++) {
+ norm = counter[i] === 1 ? counter[i] : counter[i] / ratio;
+ normalized.push(norm);
+ }
+ } else {
+ ratio = (sum + numOnes)/modulo;
+ for (i = 0; i < counter.length; i++) {
+ norm = counter[i] / ratio;
+ normalized.push(norm);
+ }
+ }
+ return normalized;
+ };
- function initCanvas() {
- if (_config.useWorker || typeof document === 'undefined') {
- return;
- }
- _canvasContainer.dom.binary = document.createElement("canvas");
- _canvasContainer.dom.binary.className = "binaryBuffer";
- if (_config.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;
- }
+ BarcodeReader.prototype._matchTrace = function(cmpCounter, epsilon) {
+ var counter = [],
+ i,
+ self = this,
+ offset = self._nextSet(self._row),
+ isWhite = !self._row[offset],
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : 0
+ },
+ error;
- /**
- * 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;
+ if (cmpCounter) {
+ for ( i = 0; i < cmpCounter.length; i++) {
+ counter.push(0);
+ }
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ error = self._matchPattern(counter, cmpCounter);
- // 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 (_config.showPatches) {
- ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "red"});
+ if (error < epsilon) {
+ bestMatch.start = i - offset;
+ bestMatch.end = i;
+ bestMatch.counter = counter;
+ return bestMatch;
+ } else {
+ return null;
+ }
+ } else {
+ counterPos++;
+ }
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ } else {
+ counter.push(0);
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ counterPos++;
+ counter.push(0);
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
}
- }
-
- overAvg /= patches.length;
- overAvg = (overAvg * 180 / Math.PI + 90) % 180 - 90;
- if (overAvg < 0) {
- overAvg += 180;
- }
-
- //console.log(overAvg);
- overAvg = (180 - overAvg) * Math.PI / 180;
- transMat = 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++) {
- mat2.xVec2(transMat, patch.box[j]);
+ // if cmpCounter was not given
+ bestMatch.start = offset;
+ bestMatch.end = self._row.length - 1;
+ bestMatch.counter = counter;
+ return bestMatch;
+ };
+
+ BarcodeReader.prototype.decodePattern = function(pattern) {
+ var self = this,
+ result;
+
+ self._row = pattern;
+ result = self._decode();
+ if (result === null) {
+ self._row.reverse();
+ result = self._decode();
+ if (result) {
+ result.direction = BarcodeReader.DIRECTION.REVERSE;
+ result.start = self._row.length - result.start;
+ result.end = self._row.length - result.end;
+ }
+ } else {
+ result.direction = BarcodeReader.DIRECTION.FORWARD;
}
-
- if (_config.boxFromPatches.showTransformed) {
- ImageDebug.drawPath(patch.box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#99ff00', lineWidth: 2});
+ if (result) {
+ result.format = self.FORMAT;
}
- }
+ return result;
+ };
- // 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];
+ BarcodeReader.prototype._matchRange = function(start, end, value) {
+ var i;
+
+ start = start < 0 ? 0 : start;
+ for (i = start; i < end; i++) {
+ if (this._row[i] !== value) {
+ return false;
}
}
- }
+ return true;
+ };
- box = [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]];
+ BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) {
+ var self = this,
+ counterPos = 0,
+ i,
+ counters = [];
- if (_config.boxFromPatches.showTransformedBox) {
- ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2});
- }
+ isWhite = (typeof isWhite !== 'undefined') ? isWhite : true;
+ offset = (typeof offset !== 'undefined') ? offset : self._nextUnset(self._row);
+ end = end || self._row.length;
- scale = _config.halfSample ? 2 : 1;
- // reverse rotation;
- transMat = mat2.inverse(transMat);
- for ( j = 0; j < 4; j++) {
- mat2.xVec2(transMat, box[j]);
- }
+ counters[counterPos] = 0;
+ for (i = offset; i < end; i++) {
+ if (self._row[i] ^ isWhite) {
+ counters[counterPos]++;
+ } else {
+ counterPos++;
+ counters[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ return counters;
+ };
- if (_config.boxFromPatches.showBB) {
- ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2});
- }
+ Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
+ value: 'unknown',
+ writeable: false
+ });
- for ( j = 0; j < 4; j++) {
- vec2.scale(box[j], scale);
- }
+ BarcodeReader.DIRECTION = {
+ FORWARD : 1,
+ REVERSE : -1
+ };
+
+ BarcodeReader.Exception = {
+ StartNotFoundException : "Start-Info was not found!",
+ CodeNotFoundException : "Code could not be found!",
+ PatternNotFoundException : "Pattern could not be found!"
+ };
- return box;
+ BarcodeReader.CONFIG_KEYS = {};
+
+ return (BarcodeReader);
}
+);
- /**
- * Creates a binary image of the current image
- */
- function binarizeImage() {
- CVUtils.otsuThreshold(_currentImageWrapper, _binaryImageWrapper);
- _binaryImageWrapper.zeroBorder();
- if (_config.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++) {
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- x = _subImageWrapper.size.x * i;
- y = _subImageWrapper.size.y * j;
+define(
+ 'code_128_reader',[
+ "./barcode_reader"
+ ],
+ function(BarcodeReader) {
+ "use strict";
+
+ function Code128Reader() {
+ BarcodeReader.call(this);
+ }
+
+ var properties = {
+ CODE_SHIFT : {value: 98},
+ CODE_C : {value: 99},
+ CODE_B : {value: 100},
+ CODE_A : {value: 101},
+ START_CODE_A : {value: 103},
+ START_CODE_B : {value: 104},
+ START_CODE_C : {value: 105},
+ STOP_CODE : {value: 106},
+ MODULO : {value: 11},
+ CODE_PATTERN : {value: [
+ [2, 1, 2, 2, 2, 2],
+ [2, 2, 2, 1, 2, 2],
+ [2, 2, 2, 2, 2, 1],
+ [1, 2, 1, 2, 2, 3],
+ [1, 2, 1, 3, 2, 2],
+ [1, 3, 1, 2, 2, 2],
+ [1, 2, 2, 2, 1, 3],
+ [1, 2, 2, 3, 1, 2],
+ [1, 3, 2, 2, 1, 2],
+ [2, 2, 1, 2, 1, 3],
+ [2, 2, 1, 3, 1, 2],
+ [2, 3, 1, 2, 1, 2],
+ [1, 1, 2, 2, 3, 2],
+ [1, 2, 2, 1, 3, 2],
+ [1, 2, 2, 2, 3, 1],
+ [1, 1, 3, 2, 2, 2],
+ [1, 2, 3, 1, 2, 2],
+ [1, 2, 3, 2, 2, 1],
+ [2, 2, 3, 2, 1, 1],
+ [2, 2, 1, 1, 3, 2],
+ [2, 2, 1, 2, 3, 1],
+ [2, 1, 3, 2, 1, 2],
+ [2, 2, 3, 1, 1, 2],
+ [3, 1, 2, 1, 3, 1],
+ [3, 1, 1, 2, 2, 2],
+ [3, 2, 1, 1, 2, 2],
+ [3, 2, 1, 2, 2, 1],
+ [3, 1, 2, 2, 1, 2],
+ [3, 2, 2, 1, 1, 2],
+ [3, 2, 2, 2, 1, 1],
+ [2, 1, 2, 1, 2, 3],
+ [2, 1, 2, 3, 2, 1],
+ [2, 3, 2, 1, 2, 1],
+ [1, 1, 1, 3, 2, 3],
+ [1, 3, 1, 1, 2, 3],
+ [1, 3, 1, 3, 2, 1],
+ [1, 1, 2, 3, 1, 3],
+ [1, 3, 2, 1, 1, 3],
+ [1, 3, 2, 3, 1, 1],
+ [2, 1, 1, 3, 1, 3],
+ [2, 3, 1, 1, 1, 3],
+ [2, 3, 1, 3, 1, 1],
+ [1, 1, 2, 1, 3, 3],
+ [1, 1, 2, 3, 3, 1],
+ [1, 3, 2, 1, 3, 1],
+ [1, 1, 3, 1, 2, 3],
+ [1, 1, 3, 3, 2, 1],
+ [1, 3, 3, 1, 2, 1],
+ [3, 1, 3, 1, 2, 1],
+ [2, 1, 1, 3, 3, 1],
+ [2, 3, 1, 1, 3, 1],
+ [2, 1, 3, 1, 1, 3],
+ [2, 1, 3, 3, 1, 1],
+ [2, 1, 3, 1, 3, 1],
+ [3, 1, 1, 1, 2, 3],
+ [3, 1, 1, 3, 2, 1],
+ [3, 3, 1, 1, 2, 1],
+ [3, 1, 2, 1, 1, 3],
+ [3, 1, 2, 3, 1, 1],
+ [3, 3, 2, 1, 1, 1],
+ [3, 1, 4, 1, 1, 1],
+ [2, 2, 1, 4, 1, 1],
+ [4, 3, 1, 1, 1, 1],
+ [1, 1, 1, 2, 2, 4],
+ [1, 1, 1, 4, 2, 2],
+ [1, 2, 1, 1, 2, 4],
+ [1, 2, 1, 4, 2, 1],
+ [1, 4, 1, 1, 2, 2],
+ [1, 4, 1, 2, 2, 1],
+ [1, 1, 2, 2, 1, 4],
+ [1, 1, 2, 4, 1, 2],
+ [1, 2, 2, 1, 1, 4],
+ [1, 2, 2, 4, 1, 1],
+ [1, 4, 2, 1, 1, 2],
+ [1, 4, 2, 2, 1, 1],
+ [2, 4, 1, 2, 1, 1],
+ [2, 2, 1, 1, 1, 4],
+ [4, 1, 3, 1, 1, 1],
+ [2, 4, 1, 1, 1, 2],
+ [1, 3, 4, 1, 1, 1],
+ [1, 1, 1, 2, 4, 2],
+ [1, 2, 1, 1, 4, 2],
+ [1, 2, 1, 2, 4, 1],
+ [1, 1, 4, 2, 1, 2],
+ [1, 2, 4, 1, 1, 2],
+ [1, 2, 4, 2, 1, 1],
+ [4, 1, 1, 2, 1, 2],
+ [4, 2, 1, 1, 1, 2],
+ [4, 2, 1, 2, 1, 1],
+ [2, 1, 2, 1, 4, 1],
+ [2, 1, 4, 1, 2, 1],
+ [4, 1, 2, 1, 2, 1],
+ [1, 1, 1, 1, 4, 3],
+ [1, 1, 1, 3, 4, 1],
+ [1, 3, 1, 1, 4, 1],
+ [1, 1, 4, 1, 1, 3],
+ [1, 1, 4, 3, 1, 1],
+ [4, 1, 1, 1, 1, 3],
+ [4, 1, 1, 3, 1, 1],
+ [1, 1, 3, 1, 4, 1],
+ [1, 1, 4, 1, 3, 1],
+ [3, 1, 1, 1, 4, 1],
+ [4, 1, 1, 1, 3, 1],
+ [2, 1, 1, 4, 1, 2],
+ [2, 1, 1, 2, 1, 4],
+ [2, 1, 1, 2, 3, 2],
+ [2, 3, 3, 1, 1, 1, 2]
+ ]},
+ SINGLE_CODE_ERROR: {value: 1},
+ AVG_CODE_ERROR: {value: 0.5},
+ FORMAT: {value: "code_128", writeable: false}
+ };
+
+ Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
+ Code128Reader.prototype.constructor = Code128Reader;
+
+ Code128Reader.prototype._decodeCode = function(start) {
+ var counter = [0, 0, 0, 0, 0, 0],
+ i,
+ self = this,
+ offset = start,
+ isWhite = !self._row[offset],
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : start,
+ end : start
+ },
+ code,
+ error,
+ normalized;
- // seperate parts
- skeletonize(x, y);
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ normalized = self._normalize(counter);
+ if (normalized) {
+ for (code = 0; code < self.CODE_PATTERN.length; code++) {
+ error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
+ if (error < bestMatch.error) {
+ bestMatch.code = code;
+ bestMatch.error = error;
+ }
+ }
+ bestMatch.end = i;
+ return bestMatch;
+ }
+ } else {
+ counterPos++;
+ }
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ return null;
+ };
- // Rasterize, find individual bars
- _skelImageWrapper.zeroBorder();
- ArrayHelper.init(_labelImageWrapper.data, 0);
- rasterizer = Rasterizer.create(_skelImageWrapper, _labelImageWrapper);
- rasterResult = rasterizer.rasterize(0);
+ Code128Reader.prototype._findStart = function() {
+ var counter = [0, 0, 0, 0, 0, 0],
+ i,
+ self = this,
+ offset = self._nextSet(self._row),
+ isWhite = false,
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : 0,
+ end : 0
+ },
+ code,
+ error,
+ j,
+ sum,
+ normalized;
+
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ sum = 0;
+ for ( j = 0; j < counter.length; j++) {
+ sum += counter[j];
+ }
+ normalized = self._normalize(counter);
+ if (normalized) {
+ for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) {
+ error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
+ if (error < bestMatch.error) {
+ bestMatch.code = code;
+ bestMatch.error = error;
+ }
+ }
+ if (bestMatch.error < self.AVG_CODE_ERROR) {
+ bestMatch.start = i - sum;
+ bestMatch.end = i;
+ return bestMatch;
+ }
+ }
- if (_config.showLabels) {
- _labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), {x : x, y : y});
+ for ( j = 0; j < 4; j++) {
+ counter[j] = counter[j + 2];
+ }
+ counter[4] = 0;
+ counter[5] = 0;
+ counterPos--;
+ } else {
+ counterPos++;
+ }
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
}
+ }
+ return null;
+ };
- // calculate moments from the skeletonized patch
- moments = _labelImageWrapper.moments(rasterResult.count);
+ Code128Reader.prototype._decode = function() {
+ var self = this,
+ startInfo = self._findStart(),
+ code = null,
+ done = false,
+ result = [],
+ multiplier = 0,
+ checksum = 0,
+ codeset,
+ rawResult = [],
+ decodedCodes = [],
+ shiftNext = false,
+ unshift,
+ lastCharacterWasPrintable;
- // extract eligible patches
- patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y));
+ if (startInfo === null) {
+ return null;
}
- }
-
- if (_config.showFoundPatches) {
- for ( i = 0; i < patchesFound.length; i++) {
- patch = patchesFound[i];
- ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "#99ff00", lineWidth: 2});
+ code = {
+ code : startInfo.code,
+ start : startInfo.start,
+ end : startInfo.end
+ };
+ decodedCodes.push(code);
+ checksum = code.code;
+ switch(code.code) {
+ case self.START_CODE_A:
+ codeset = self.CODE_A;
+ break;
+ case self.START_CODE_B:
+ codeset = self.CODE_B;
+ break;
+ case self.START_CODE_C:
+ codeset = self.CODE_C;
+ break;
+ default:
+ return null;
}
- }
-
- 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);
- }
- sum = _patchLabelGrid.data.length;
- while (sum--) {
- if (_patchLabelGrid.data[sum] > 0) {
- labelHist[_patchLabelGrid.data[sum] - 1]++;
+
+ while (!done) {
+ unshift = shiftNext;
+ shiftNext = false;
+ code = self._decodeCode(code.end);
+ if (code !== null) {
+ if (code.code !== self.STOP_CODE) {
+ rawResult.push(code.code);
+ multiplier++;
+ checksum += multiplier * code.code;
+ }
+ decodedCodes.push(code);
+
+ switch(codeset) {
+ case self.CODE_A:
+ if (code.code < 64) {
+ result.push(String.fromCharCode(32 + code.code));
+ } else if (code.code < 96) {
+ result.push(String.fromCharCode(code.code - 64));
+ } else {
+ switch (code.code) {
+ case self.CODE_SHIFT:
+ shiftNext = true;
+ codeset = self.CODE_B;
+ break;
+ case self.CODE_B:
+ codeset = self.CODE_B;
+ break;
+ case self.CODE_C:
+ codeset = self.CODE_C;
+ break;
+ case self.STOP_CODE:
+ done = true;
+ break;
+ }
+ }
+ break;
+ case self.CODE_B:
+ if (code.code < 96) {
+ result.push(String.fromCharCode(32 + code.code));
+ } else {
+ if (code.code != self.STOP_CODE) {
+ lastCharacterWasPrintable = false;
+ }
+ switch (code.code) {
+ case self.CODE_SHIFT:
+ shiftNext = true;
+ codeset = self.CODE_A;
+ break;
+ case self.CODE_A:
+ codeset = self.CODE_A;
+ break;
+ case self.CODE_C:
+ codeset = self.CODE_C;
+ break;
+ case self.STOP_CODE:
+ done = true;
+ break;
+ }
+ }
+ break;
+ case self.CODE_C:
+ if (code.code < 100) {
+ result.push(code.code < 10 ? "0" + code.code : code.code);
+ }
+ switch (code.code) {
+ case self.CODE_A:
+ codeset = self.CODE_A;
+ break;
+ case self.CODE_B:
+ codeset = self.CODE_B;
+ break;
+ case self.STOP_CODE:
+ done = true;
+ break;
+ }
+ break;
+ }
+ } else {
+ done = true;
+ }
+ if (unshift) {
+ codeset = codeset == self.CODE_A ? self.CODE_B : self.CODE_A;
+ }
}
- }
-
- labelHist = labelHist.map(function(val, idx) {
- return {
- val : val,
- label : idx + 1
- };
- });
- labelHist.sort(function(a, b) {
- return b.val - a.val;
- });
+ if (code === null) {
+ return null;
+ }
- // extract top areas with at least 6 patches present
- topLabels = labelHist.filter(function(el) {
- return el.val >= 5;
- });
-
- 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);
- }
+ // find end bar
+ code.end = self._nextUnset(self._row, code.end);
+ if(!self._verifyTrailingWhitespace(code)){
+ return null;
}
- box = boxFromPatches(patches);
- if (box) {
- boxes.push(box);
- // draw patch-labels if requested
- if (_config.showRemainingPatchLabels) {
- for ( j = 0; j < patches.length; j++) {
- patch = patches[j];
- hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360;
- CVUtils.hsv2rgb(hsv, rgb);
- ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
- }
- }
+ // checksum
+ // Does not work correctly yet!!! startcode - endcode?
+ checksum -= multiplier * rawResult[rawResult.length - 1];
+ if (checksum % 103 != rawResult[rawResult.length - 1]) {
+ return null;
}
- }
- return boxes;
- }
- /**
- * Find similar moments (via cluster)
- * @param {Object} moments
- */
- function similarMoments(moments) {
- var clusters = CVUtils.cluster(moments, 0.90);
- var topCluster = CVUtils.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);
+ if (!result.length) {
+ return null;
}
- }
- return result;
- }
- function skeletonize(x, y) {
- _binaryImageWrapper.subImageAsCopy(_subImageWrapper, CVUtils.imageRef(x, y));
- _skeletonizer.skeletonize();
-
- // Show skeleton if requested
- if (_config.showSkeleton) {
- _skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, CVUtils.imageRef(x, y));
- }
- }
+ // remove last code from result (checksum)
+ result.splice(result.length - 1, 1);
- /**
- * 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,
- sum = 0,
- 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) {
- sum = eligibleMoments.length;
- matchingMoments = similarMoments(eligibleMoments);
- avg = 0;
- // determine the similarity of the moments
- for ( k = 0; k < matchingMoments.length; k++) {
- avg += matchingMoments[k].rad;
- }
+ return {
+ code : result.join(""),
+ start : startInfo.start,
+ end : code.end,
+ codeset : codeset,
+ startInfo : startInfo,
+ decodedCodes : decodedCodes,
+ endInfo : code
+ };
+ };
- // 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.create([x, y]), vec2.create([x + _subImageWrapper.size.x, y]), vec2.create([x + _subImageWrapper.size.x, y + _subImageWrapper.size.y]), vec2.create([x, y + _subImageWrapper.size.y])],
- moments : matchingMoments,
- rad : avg,
- vec : vec2.create([Math.cos(avg), Math.sin(avg)])
- };
- patchesFound.push(patch);
- }
- }
- }
- 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];
+ BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
+ var self = this,
+ trailingWhitespaceEnd;
- function notYetProcessed() {
- var i;
- for ( i = 0; i < _patchLabelGrid.data.length; i++) {
- if (_patchLabelGrid.data[i] === 0 && _patchGrid.data[i] === 1) {
- return i;
+ trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
+ if (trailingWhitespaceEnd < self._row.length) {
+ if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
+ return endInfo;
}
}
- return _patchLabelGrid.length;
- }
-
- function trace(currentIdx) {
- var x, y, currentPatch, patch, idx, dir, current = {
- x : currentIdx % _patchLabelGrid.size.x,
- y : (currentIdx / _patchLabelGrid.size.x) | 0
- }, similarity;
+ return null;
+ };
+
+ return (Code128Reader);
+ }
+);
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
- 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;
+define(
+ 'ean_reader',[
+ "./barcode_reader"
+ ],
+ function(BarcodeReader) {
+ "use strict";
+
+ function EANReader(opts) {
+ BarcodeReader.call(this, opts);
+ }
+
+ var properties = {
+ CODE_L_START : {value: 0},
+ MODULO : {value: 7},
+ CODE_G_START : {value: 10},
+ START_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
+ STOP_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
+ MIDDLE_PATTERN : {value: [1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7]},
+ CODE_PATTERN : {value: [
+ [3, 2, 1, 1],
+ [2, 2, 2, 1],
+ [2, 1, 2, 2],
+ [1, 4, 1, 1],
+ [1, 1, 3, 2],
+ [1, 2, 3, 1],
+ [1, 1, 1, 4],
+ [1, 3, 1, 2],
+ [1, 2, 1, 3],
+ [3, 1, 1, 2],
+ [1, 1, 2, 3],
+ [1, 2, 2, 2],
+ [2, 2, 1, 2],
+ [1, 1, 4, 1],
+ [2, 3, 1, 1],
+ [1, 3, 2, 1],
+ [4, 1, 1, 1],
+ [2, 1, 3, 1],
+ [3, 1, 2, 1],
+ [2, 1, 1, 3]
+ ]},
+ CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
+ SINGLE_CODE_ERROR: {value: 0.67},
+ AVG_CODE_ERROR: {value: 0.27},
+ FORMAT: {value: "ean_13", writeable: false}
+ };
+
+ EANReader.prototype = Object.create(BarcodeReader.prototype, properties);
+ EANReader.prototype.constructor = EANReader;
+
+ EANReader.prototype._decodeCode = function(start, coderange) {
+ var counter = [0, 0, 0, 0],
+ i,
+ self = this,
+ offset = start,
+ isWhite = !self._row[offset],
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : start,
+ end : start
+ },
+ code,
+ error,
+ normalized;
- // continue if patch empty
- if (_patchGrid.data[idx] === 0) {
- _patchLabelGrid.data[idx] = Number.MAX_VALUE;
- continue;
- }
+ if (!coderange) {
+ coderange = self.CODE_PATTERN.length;
+ }
- patch = _imageToPatchGrid.data[idx];
- if (_patchLabelGrid.data[idx] === 0) {
- similarity = Math.abs(vec2.dot(patch.vec, currentPatch.vec));
- if (similarity > threshold) {
- trace(idx);
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ normalized = self._normalize(counter);
+ if (normalized) {
+ for (code = 0; code < coderange; code++) {
+ error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
+ if (error < bestMatch.error) {
+ bestMatch.code = code;
+ bestMatch.error = error;
+ }
+ }
+ bestMatch.end = i;
+ if (bestMatch.error > self.AVG_CODE_ERROR) {
+ return null;
+ }
+ return bestMatch;
}
+ } else {
+ counterPos++;
}
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
}
}
- }
-
- // 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;
- }
-
- // rasterize the patches found to determine area
- _patchGrid.zeroBorder();
-
- while (( currIdx = notYetProcessed()) < _patchLabelGrid.data.length) {
- label++;
- trace(currIdx);
- }
-
- // draw patch-labels if requested
- if (_config.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;
- CVUtils.hsv2rgb(hsv, rgb);
- ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
- }
- }
- }
-
- return label;
- }
-
- return {
- init : function(inputImageWrapper, config) {
- _config = config;
- _inputImageWrapper = inputImageWrapper;
-
- initBuffers();
- initCanvas();
- },
-
- locate : function() {
- var patchesFound,
- topLabels,
- boxes;
+ return null;
+ };
- if (_config.halfSample) {
- CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
- }
+ EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder, epsilon) {
+ var counter = [],
+ self = this,
+ i,
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : 0,
+ end : 0
+ },
+ error,
+ j,
+ sum,
+ normalized;
- binarizeImage();
- patchesFound = findPatches();
- // return unless 5% or more patches are found
- if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
- return null;
+ if (!offset) {
+ offset = self._nextSet(self._row);
}
- // rasterrize area by comparing angular similarity;
- var maxLabel = rasterizeAngularSimilarity(patchesFound);
- if (maxLabel < 1) {
- return null;
+ if (isWhite === undefined) {
+ isWhite = false;
}
- // search for area with the most patches (biggest connected area)
- topLabels = findBiggestConnectedAreas(maxLabel);
- if (topLabels.length === 0) {
- return null;
+ if (tryHarder === undefined) {
+ tryHarder = true;
}
- 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 = CVUtils.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;
+ if ( epsilon === undefined) {
+ epsilon = self.AVG_CODE_ERROR;
}
- size = {
- x: Math.floor(width * halfSample),
- y: Math.floor(height * halfSample)
- };
-
- patchSize = CVUtils.calculatePatchSize(config.patchSize, size);
- console.log("Patch-Size: " + JSON.stringify(patchSize));
-
- 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));
-
- if ((inputStream.getWidth() % patchSize.x) === 0 && (inputStream.getHeight() % patchSize.y) === 0) {
- return true;
+ for ( i = 0; i < pattern.length; i++) {
+ counter[i] = 0;
}
- throw new Error("Image dimensions do not comply with the current settings: Width (" +
- width + " )and height (" + height +
- ") must a multiple of " + patchSize.x);
- }
- };
-});
-
-
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-
-define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) {
- "use strict";
- var Bresenham = {};
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ sum = 0;
+ for ( j = 0; j < counter.length; j++) {
+ sum += counter[j];
+ }
+ normalized = self._normalize(counter);
+ if (normalized) {
+ error = self._matchPattern(normalized, pattern);
- var Slope = {
- DIR : {
- UP : 1,
- DOWN : -1
- }
- };
- /**
- * Scans a line of the given image from point p1 to p2 and returns a result object containing
- * gray-scale values (0-255) of the underlying pixels in addition to the min
- * and max values.
- * @param {Object} imageWrapper
- * @param {Object} p1 The start point {x,y}
- * @param {Object} p2 The end point {x,y}
- * @returns {line, min, max}
- */
- Bresenham.getBarcodeLine = function(imageWrapper, p1, p2) {
- var x0 = p1.x | 0,
- y0 = p1.y | 0,
- x1 = p2.x | 0,
- y1 = p2.y | 0,
- steep = Math.abs(y1 - y0) > Math.abs(x1 - x0),
- deltax,
- deltay,
- error,
- ystep,
- y,
- tmp,
- x,
- line = [],
- imageData = imageWrapper.data,
- width = imageWrapper.size.x,
- sum = 0,
- val,
- min = 255,
- max = 0;
+ if (error < epsilon) {
+ bestMatch.error = error;
+ bestMatch.start = i - sum;
+ bestMatch.end = i;
+ return bestMatch;
+ }
+ }
+ if (tryHarder) {
+ for ( j = 0; j < counter.length - 2; j++) {
+ counter[j] = counter[j + 2];
+ }
+ counter[counter.length - 2] = 0;
+ counter[counter.length - 1] = 0;
+ counterPos--;
+ } else {
+ return null;
+ }
+ } else {
+ counterPos++;
+ }
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ return null;
+ };
- function read(a, b) {
- val = imageData[b * width + a];
- sum += val;
- min = val < min ? val : min;
- max = val > max ? val : max;
- line.push(val);
- }
+ EANReader.prototype._findStart = function() {
+ var self = this,
+ leadingWhitespaceStart,
+ offset = self._nextSet(self._row),
+ startInfo;
- if (steep) {
- tmp = x0;
- x0 = y0;
- y0 = tmp;
+ while(!startInfo) {
+ startInfo = self._findPattern(self.START_PATTERN, offset);
+ if (!startInfo) {
+ return null;
+ }
+ leadingWhitespaceStart = startInfo.start - (startInfo.end - startInfo.start);
+ if (leadingWhitespaceStart >= 0) {
+ if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
+ return startInfo;
+ }
+ }
+ offset = startInfo.end;
+ startInfo = null;
+ }
+ };
- tmp = x1;
- x1 = y1;
- y1 = tmp;
- }
- if (x0 > x1) {
- tmp = x0;
- x0 = x1;
- x1 = tmp;
+ EANReader.prototype._verifyTrailingWhitespace = function(endInfo) {
+ var self = this,
+ trailingWhitespaceEnd;
- tmp = y0;
- y0 = y1;
- y1 = tmp;
- }
- deltax = x1 - x0;
- deltay = Math.abs(y1 - y0);
- error = (deltax / 2) | 0;
- y = y0;
- ystep = y0 < y1 ? 1 : -1;
- for ( x = x0; x < x1; x++) {
- if(steep){
- read(y, x);
- } else {
- read(x, y);
- }
- error = error - deltay;
- if (error < 0) {
- y = y + ystep;
- error = error + deltax;
+ trailingWhitespaceEnd = endInfo.end + (endInfo.end - endInfo.start);
+ if (trailingWhitespaceEnd < self._row.length) {
+ if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
+ return endInfo;
+ }
}
- }
-
- return {
- line : line,
- min : min,
- max : max
+ return null;
};
- };
-
- Bresenham.toOtsuBinaryLine = function(result) {
- var line = result.line,
- image = new ImageWrapper({x: line.length - 1, y: 1}, line),
- threshold = CVUtils.determineOtsuThreshold(image, 5);
- line = CVUtils.sharpenLine(line);
- CVUtils.thresholdImage(image, threshold);
+ EANReader.prototype._findEnd = function(offset, isWhite) {
+ var self = this,
+ endInfo = self._findPattern(self.STOP_PATTERN, offset, isWhite, false);
- return {
- line: line,
- threshold: threshold
+ return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
};
- };
-
- /**
- * Converts the result from getBarcodeLine into a binary representation
- * also considering the frequency and slope of the signal for more robust results
- * @param {Object} result {line, min, max}
- */
- Bresenham.toBinaryLine = function(result) {
- var min = result.min,
- max = result.max,
- line = result.line,
- slope,
- slope2,
- center = min + (max - min) / 2,
- extrema = [],
- currentDir,
- dir,
- threshold = (max - min) / 12,
- rThreshold = -threshold,
- i,
- j;
+ EANReader.prototype._calculateFirstDigit = function(codeFrequency) {
+ var i,
+ self = this;
- // 1. find extrema
- currentDir = line[0] > center ? Slope.DIR.UP : Slope.DIR.DOWN;
- extrema.push({
- pos : 0,
- val : line[0]
- });
- for ( i = 0; i < line.length - 2; i++) {
- slope = (line[i + 1] - line[i]);
- slope2 = (line[i + 2] - line[i + 1]);
- if ((slope + slope2) < rThreshold && line[i + 1] < (center*1.5)) {
- dir = Slope.DIR.DOWN;
- } else if ((slope + slope2) > threshold && line[i + 1] > (center*0.5)) {
- dir = Slope.DIR.UP;
- } else {
- dir = currentDir;
+ for ( i = 0; i < self.CODE_FREQUENCY.length; i++) {
+ if (codeFrequency === self.CODE_FREQUENCY[i]) {
+ return i;
+ }
}
+ return null;
+ };
- if (currentDir !== dir) {
- extrema.push({
- pos : i,
- val : line[i]
- });
- currentDir = dir;
+ EANReader.prototype._decodePayload = function(code, result, decodedCodes) {
+ var i,
+ self = this,
+ codeFrequency = 0x0,
+ firstDigit;
+
+ for ( i = 0; i < 6; i++) {
+ code = self._decodeCode(code.end);
+ if (!code) {
+ return null;
+ }
+ if (code.code >= self.CODE_G_START) {
+ code.code = code.code - self.CODE_G_START;
+ codeFrequency |= 1 << (5 - i);
+ } else {
+ codeFrequency |= 0 << (5 - i);
+ }
+ result.push(code.code);
+ decodedCodes.push(code);
}
- }
- extrema.push({
- pos : line.length,
- val : line[line.length - 1]
- });
- for ( j = extrema[0].pos; j < extrema[1].pos; j++) {
- line[j] = line[j] > center ? 0 : 1;
- }
+ firstDigit = self._calculateFirstDigit(codeFrequency);
+ if (firstDigit === null) {
+ return null;
+ }
+ result.unshift(firstDigit);
- // iterate over extrema and convert to binary based on avg between minmax
- for ( i = 1; i < extrema.length - 1; i++) {
- if (extrema[i + 1].val > extrema[i].val) {
- threshold = (extrema[i].val + ((extrema[i + 1].val - extrema[i].val) / 3) * 2) | 0;
- } else {
- threshold = (extrema[i + 1].val + ((extrema[i].val - extrema[i + 1].val) / 3)) | 0;
+ code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
+ if (code === null) {
+ return null;
}
+ decodedCodes.push(code);
- for ( j = extrema[i].pos; j < extrema[i + 1].pos; j++) {
- line[j] = line[j] > threshold ? 0 : 1;
+ for ( i = 0; i < 6; i++) {
+ code = self._decodeCode(code.end, self.CODE_G_START);
+ if (!code) {
+ return null;
+ }
+ decodedCodes.push(code);
+ result.push(code.code);
}
- }
- return {
- line : line,
- threshold : threshold
+ return code;
};
- };
-
- /**
- * Used for development only
- */
- Bresenham.debug = {
- printFrequency: function(line, canvas) {
- var i,
- ctx = canvas.getContext("2d");
- canvas.width = line.length;
- canvas.height = 256;
-
- ctx.beginPath();
- ctx.strokeStyle = "blue";
- for ( i = 0; i < line.length; i++) {
- ctx.moveTo(i, 255);
- ctx.lineTo(i, 255 - line[i]);
+
+ EANReader.prototype._decode = function() {
+ var startInfo,
+ self = this,
+ code,
+ result = [],
+ decodedCodes = [];
+
+ startInfo = self._findStart();
+ if (!startInfo) {
+ return null;
}
- ctx.stroke();
- ctx.closePath();
- },
-
- printPattern: function(line, canvas) {
- var ctx = canvas.getContext("2d"), i;
-
- canvas.width = line.length;
- ctx.fillColor = "black";
- for ( i = 0; i < line.length; i++) {
- if (line[i] === 1) {
- ctx.fillRect(i, 0, 1, 100);
- }
+ code = {
+ code : startInfo.code,
+ start : startInfo.start,
+ end : startInfo.end
+ };
+ decodedCodes.push(code);
+ code = self._decodePayload(code, result, decodedCodes);
+ if (!code) {
+ return null;
+ }
+ code = self._findEnd(code.end, false);
+ if (!code){
+ return null;
}
- }
- };
- return (Bresenham);
-});
+ decodedCodes.push(code);
+
+ // Checksum
+ if (!self._checksum(result)) {
+ return null;
+ }
+
+ return {
+ code : result.join(""),
+ start : startInfo.start,
+ end : code.end,
+ codeset : "",
+ startInfo : startInfo,
+ decodedCodes : decodedCodes
+ };
+ };
+
+ EANReader.prototype._checksum = function(result) {
+ var sum = 0, i;
+
+ for ( i = result.length - 2; i >= 0; i -= 2) {
+ sum += result[i];
+ }
+ sum *= 3;
+ for ( i = result.length - 1; i >= 0; i -= 2) {
+ sum += result[i];
+ }
+ return sum % 10 === 0;
+ };
+
+ return (EANReader);
+ }
+);
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
@@ -7139,7 +7165,7 @@ define(
nextStart,
end;
- self._fillCounters();
+ this._counters = self._fillCounters();
start = self._findStart();
if (!start) {
return null;
@@ -7289,334 +7315,698 @@ define(
return true;
};
- CodabarReader.prototype._fillCounters = function() {
+ CodabarReader.prototype._patternToChar = function(pattern) {
+ var i,
+ self = this;
+
+ for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
+ if (self.CHARACTER_ENCODINGS[i] === pattern) {
+ return String.fromCharCode(self.ALPHABET[i]);
+ }
+ }
+ return -1;
+ };
+
+ CodabarReader.prototype._computeAlternatingThreshold = function(offset, end) {
+ var i,
+ min = Number.MAX_VALUE,
+ max = 0,
+ counter;
+
+ for (i = offset; i < end; i += 2){
+ counter = this._counters[i];
+ if (counter > max) {
+ max = counter;
+ }
+ if (counter < min) {
+ min = counter;
+ }
+ }
+
+ return ((min + max) / 2.0) | 0;
+ };
+
+ CodabarReader.prototype._toPattern = function(offset) {
+ var numCounters = 7,
+ end = offset + numCounters,
+ barThreshold,
+ spaceThreshold,
+ bitmask = 1 << (numCounters - 1),
+ pattern = 0,
+ i,
+ threshold;
+
+ if (end > this._counters.length) {
+ return -1;
+ }
+
+ barThreshold = this._computeAlternatingThreshold(offset, end);
+ spaceThreshold = this._computeAlternatingThreshold(offset + 1, end);
+
+ for (i = 0; i < numCounters; i++){
+ threshold = (i & 1) === 0 ? barThreshold : spaceThreshold;
+ if (this._counters[offset + i] > threshold) {
+ pattern |= bitmask;
+ }
+ bitmask >>= 1;
+ }
+
+ return pattern;
+ };
+
+ CodabarReader.prototype._isStartEnd = function(pattern) {
+ var i;
+
+ for (i = 0; i < this.START_END.length; i++) {
+ if (this.START_END[i] === pattern) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ CodabarReader.prototype._sumCounters = function(start, end) {
+ var i,
+ sum = 0;
+
+ for (i = start; i < end; i++) {
+ sum += this._counters[i];
+ }
+ return sum;
+ };
+
+ CodabarReader.prototype._findStart = function() {
var self = this,
- counterPos = 0,
- isWhite = true,
- offset = self._nextUnset(self._row),
- i;
+ i,
+ pattern,
+ start = self._nextUnset(self._row),
+ end;
- self._counters.length = 0;
- self._counters[counterPos] = 0;
- for (i = offset; i < self._row.length; i++) {
- if (self._row[i] ^ isWhite) {
- this._counters[counterPos]++;
- } else {
- counterPos++;
- this._counters[counterPos] = 1;
- isWhite = !isWhite;
+ for (i = 1; i < this._counters.length; i++) {
+ pattern = self._toPattern(i);
+ if (pattern !== -1 && self._isStartEnd(pattern)) {
+ // TODO: Look for whitespace ahead
+ start += self._sumCounters(0, i);
+ end = start + self._sumCounters(i, i + 8);
+ return {
+ start: start,
+ end: end,
+ startCounter: i,
+ endCounter: i + 8
+ };
}
}
};
- CodabarReader.prototype._patternToChar = function(pattern) {
+ return (CodabarReader);
+ }
+);
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
+define(
+ 'upc_reader',[
+ "./ean_reader"
+ ],
+ function(EANReader) {
+ "use strict";
+
+ function UPCReader() {
+ EANReader.call(this);
+ }
+
+ var properties = {
+ FORMAT: {value: "upc_a", writeable: false}
+ };
+
+ UPCReader.prototype = Object.create(EANReader.prototype, properties);
+ UPCReader.prototype.constructor = UPCReader;
+
+ UPCReader.prototype._decode = function() {
+ var result = EANReader.prototype._decode.call(this);
+
+ if (result && result.code && result.code.length === 13 && result.code.charAt(0) === "0") {
+
+ result.code = result.code.substring(1);
+ return result;
+ }
+ return null;
+ };
+
+ return (UPCReader);
+ }
+);
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
+define(
+ 'ean_8_reader',[
+ "./ean_reader"
+ ],
+ function(EANReader) {
+ "use strict";
+
+ function EAN8Reader() {
+ EANReader.call(this);
+ }
+
+ var properties = {
+ FORMAT: {value: "ean_8", writeable: false}
+ };
+
+ EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
+ EAN8Reader.prototype.constructor = EAN8Reader;
+
+ EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
self = this;
- for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
- if (self.CHARACTER_ENCODINGS[i] === pattern) {
- return String.fromCharCode(self.ALPHABET[i]);
+ for ( i = 0; i < 4; i++) {
+ code = self._decodeCode(code.end, self.CODE_G_START);
+ if (!code) {
+ return null;
}
+ result.push(code.code);
+ decodedCodes.push(code);
}
- return -1;
+
+ code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
+ if (code === null) {
+ return null;
+ }
+ decodedCodes.push(code);
+
+ for ( i = 0; i < 4; i++) {
+ code = self._decodeCode(code.end, self.CODE_G_START);
+ if (!code) {
+ return null;
+ }
+ decodedCodes.push(code);
+ result.push(code.code);
+ }
+
+ return code;
+ };
+
+ return (EAN8Reader);
+ }
+);
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
+define(
+ 'upc_e_reader',[
+ "./ean_reader"
+ ],
+ function(EANReader) {
+ "use strict";
+
+ function UPCEReader() {
+ EANReader.call(this);
+ }
+
+ var properties = {
+ CODE_FREQUENCY : {value: [
+ [ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
+ [7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]},
+ STOP_PATTERN: { value: [1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7]},
+ FORMAT: {value: "upc_e", writeable: false}
};
- CodabarReader.prototype._computeAlternatingThreshold = function(offset, end) {
+ UPCEReader.prototype = Object.create(EANReader.prototype, properties);
+ UPCEReader.prototype.constructor = UPCEReader;
+
+ UPCEReader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
- min = Number.MAX_VALUE,
- max = 0,
- counter;
+ self = this,
+ codeFrequency = 0x0;
- for (i = offset; i < end; i += 2){
- counter = this._counters[i];
- if (counter > max) {
- max = counter;
+ for ( i = 0; i < 6; i++) {
+ code = self._decodeCode(code.end);
+ if (!code) {
+ return null;
}
- if (counter < min) {
- min = counter;
+ if (code.code >= self.CODE_G_START) {
+ code.code = code.code - self.CODE_G_START;
+ codeFrequency |= 1 << (5 - i);
}
+ result.push(code.code);
+ decodedCodes.push(code);
+ }
+ if (!self._determineParity(codeFrequency, result)) {
+ return null;
}
- return ((min + max) / 2.0) | 0;
+ return code;
};
- CodabarReader.prototype._toPattern = function(offset) {
- var numCounters = 7,
- end = offset + numCounters,
- barThreshold,
- spaceThreshold,
- bitmask = 1 << (numCounters - 1),
- pattern = 0,
+ UPCEReader.prototype._determineParity = function(codeFrequency, result) {
+ var self =this,
i,
- threshold;
-
- if (end > this._counters.length) {
- return -1;
- }
-
- barThreshold = this._computeAlternatingThreshold(offset, end);
- spaceThreshold = this._computeAlternatingThreshold(offset + 1, end);
+ nrSystem;
- for (i = 0; i < numCounters; i++){
- threshold = (i & 1) === 0 ? barThreshold : spaceThreshold;
- if (this._counters[offset + i] > threshold) {
- pattern |= bitmask;
+ for (nrSystem = 0; nrSystem < self.CODE_FREQUENCY.length; nrSystem++){
+ for ( i = 0; i < self.CODE_FREQUENCY[nrSystem].length; i++) {
+ if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) {
+ result.unshift(nrSystem);
+ result.push(i);
+ return true;
+ }
}
- bitmask >>= 1;
}
-
- return pattern;
+ return false;
};
- CodabarReader.prototype._isStartEnd = function(pattern) {
- var i;
+ UPCEReader.prototype._convertToUPCA = function(result) {
+ var upca = [result[0]],
+ lastDigit = result[result.length - 2];
- for (i = 0; i < this.START_END.length; i++) {
- if (this.START_END[i] === pattern) {
- return true;
- }
+ if (lastDigit <= 2) {
+ upca = upca.concat(result.slice(1, 3))
+ .concat([lastDigit, 0, 0, 0, 0])
+ .concat(result.slice(3, 6));
+ } else if (lastDigit === 3) {
+ upca = upca.concat(result.slice(1, 4))
+ .concat([0 ,0, 0, 0, 0])
+ .concat(result.slice(4,6));
+ } else if (lastDigit === 4) {
+ upca = upca.concat(result.slice(1, 5))
+ .concat([0, 0, 0, 0, 0, result[5]]);
+ } else {
+ upca = upca.concat(result.slice(1, 6))
+ .concat([0, 0, 0, 0, lastDigit]);
}
- return false;
+
+ upca.push(result[result.length - 1]);
+ return upca;
};
- CodabarReader.prototype._sumCounters = function(start, end) {
- var i,
- sum = 0;
+ UPCEReader.prototype._checksum = function(result) {
+ return EANReader.prototype._checksum.call(this, this._convertToUPCA(result));
+ };
- for (i = start; i < end; i++) {
- sum += this._counters[i];
- }
- return sum;
+ UPCEReader.prototype._findEnd = function(offset, isWhite) {
+ isWhite = true;
+ return EANReader.prototype._findEnd.call(this, offset, isWhite);
};
- CodabarReader.prototype._findStart = function() {
+ UPCEReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this,
- i,
- pattern,
- start = self._nextUnset(self._row),
- end;
+ trailingWhitespaceEnd;
- for (i = 1; i < this._counters.length; i++) {
- pattern = self._toPattern(i);
- if (pattern !== -1 && self._isStartEnd(pattern)) {
- // TODO: Look for whitespace ahead
- start += self._sumCounters(0, i);
- end = start + self._sumCounters(i, i + 8);
- return {
- start: start,
- end: end,
- startCounter: i,
- endCounter: i + 8
- };
+ trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start)/2);
+ if (trailingWhitespaceEnd < self._row.length) {
+ if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
+ return endInfo;
}
}
};
- return (CodabarReader);
+ return (UPCEReader);
}
);
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
+define('html_utils',[], function() {
+ "use strict";
+
+ function createNode(htmlStr) {
+ var temp = document.createElement('div');
+
+ temp.innerHTML = htmlStr;
+ while (temp.firstChild) {
+ return temp.firstChild;
+ }
+ }
+
+ function mergeObjects(obj1, obj2) {
+ for (var p in obj2) {
+ try {
+ if (obj2[p].constructor == Object) {
+ obj1[p] = mergeObjects(obj1[p], obj2[p]);
+ } else {
+ obj1[p] = obj2[p];
+ }
+ } catch(e) {
+ obj1[p] = obj2[p];
+ }
+ }
+
+ return obj1;
+ }
+
+ return {
+ createNode : function(htmlStr) {
+ return createNode(htmlStr);
+ },
+ mergeObjects : function(obj1, obj2) {
+ return mergeObjects(obj1, obj2);
+ }
+ };
+});
+/* jshint undef: true, unused: true, browser:true, devel: true */
+/* global define */
+
define(
- 'upc_reader',[
- "./ean_reader"
+ 'i2of5_reader',[
+ "./barcode_reader",
+ "./html_utils"
],
- function(EANReader) {
+ function(BarcodeReader, HTMLUtils) {
"use strict";
- function UPCReader() {
- EANReader.call(this);
+ function I2of5Reader(opts) {
+ opts = HTMLUtils.mergeObjects(getDefaulConfig(), opts);
+ BarcodeReader.call(this, opts);
+ this.barSpaceRatio = [1, 1];
+ if (opts.normalizeBarSpaceWidth) {
+ this.SINGLE_CODE_ERROR = 0.38;
+ this.AVG_CODE_ERROR = 0.09;
+ }
}
- var properties = {
- FORMAT: {value: "upc_a", writeable: false}
+ function getDefaulConfig() {
+ var config = {};
+
+ Object.keys(I2of5Reader.CONFIG_KEYS).forEach(function(key) {
+ config[key] = I2of5Reader.CONFIG_KEYS[key]['default'];
+ });
+ return config;
+ }
+
+ var N = 1,
+ W = 3,
+ properties = {
+ MODULO : {value: 10},
+ START_PATTERN : {value: [N*2.5, N*2.5, N*2.5, N*2.5]},
+ STOP_PATTERN : {value: [N*2, N*2, W*2]},
+ CODE_PATTERN : {value: [
+ [N, N, W, W, N],
+ [W, N, N, N, W],
+ [N, W, N, N, W],
+ [W, W, N, N, N],
+ [N, N, W, N, W],
+ [W, N, W, N, N],
+ [N, W, W, N, N],
+ [N, N, N, W, W],
+ [W, N, N, W, N],
+ [N, W, N, W, N]
+ ]},
+ SINGLE_CODE_ERROR: {value: 0.78, writable: true},
+ AVG_CODE_ERROR: {value: 0.38, writable: true},
+ MAX_CORRECTION_FACTOR: {value: 5},
+ FORMAT: {value: "i2of5"}
+ };
+
+ I2of5Reader.prototype = Object.create(BarcodeReader.prototype, properties);
+ I2of5Reader.prototype.constructor = I2of5Reader;
+
+ I2of5Reader.prototype._matchPattern = function(counter, code) {
+ if (this.config.normalizeBarSpaceWidth) {
+ var i,
+ counterSum = [0, 0],
+ codeSum = [0, 0],
+ correction = [0, 0],
+ correctionRatio = this.MAX_CORRECTION_FACTOR,
+ correctionRatioInverse = 1 / correctionRatio;
+
+ for (i = 0; i < counter.length; i++) {
+ counterSum[i % 2] += counter[i];
+ codeSum[i % 2] += code[i];
+ }
+ correction[0] = codeSum[0] / counterSum[0];
+ correction[1] = codeSum[1] / counterSum[1];
+
+ correction[0] = Math.max(Math.min(correction[0], correctionRatio), correctionRatioInverse);
+ correction[1] = Math.max(Math.min(correction[1], correctionRatio), correctionRatioInverse);
+ this.barSpaceRatio = correction;
+ for (i = 0; i < counter.length; i++) {
+ counter[i] *= this.barSpaceRatio[i % 2];
+ }
+ }
+ return BarcodeReader.prototype._matchPattern.call(this, counter, code);
+ };
+
+ I2of5Reader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder) {
+ var counter = [],
+ self = this,
+ i,
+ counterPos = 0,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : 0,
+ end : 0
+ },
+ error,
+ j,
+ sum,
+ normalized,
+ epsilon = self.AVG_CODE_ERROR;
+
+ isWhite = isWhite || false;
+ tryHarder = tryHarder || false;
+
+ if (!offset) {
+ offset = self._nextSet(self._row);
+ }
+
+ for ( i = 0; i < pattern.length; i++) {
+ counter[i] = 0;
+ }
+
+ for ( i = offset; i < self._row.length; i++) {
+ if (self._row[i] ^ isWhite) {
+ counter[counterPos]++;
+ } else {
+ if (counterPos === counter.length - 1) {
+ sum = 0;
+ for ( j = 0; j < counter.length; j++) {
+ sum += counter[j];
+ }
+ normalized = self._normalize(counter);
+ if (normalized) {
+ error = self._matchPattern(normalized, pattern);
+
+ if (error < epsilon) {
+ bestMatch.error = error;
+ bestMatch.start = i - sum;
+ bestMatch.end = i;
+ return bestMatch;
+ }
+ }
+ if (tryHarder) {
+ for (j = 0; j < counter.length - 2; j++) {
+ counter[j] = counter[j + 2];
+ }
+ counter[counter.length - 2] = 0;
+ counter[counter.length - 1] = 0;
+ counterPos--;
+ } else {
+ return null;
+ }
+ } else {
+ counterPos++;
+ }
+ counter[counterPos] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ return null;
};
- UPCReader.prototype = Object.create(EANReader.prototype, properties);
- UPCReader.prototype.constructor = UPCReader;
+ I2of5Reader.prototype._findStart = function() {
+ var self = this,
+ leadingWhitespaceStart,
+ offset = self._nextSet(self._row),
+ startInfo,
+ narrowBarWidth = 1;
- UPCReader.prototype._decode = function() {
- var result = EANReader.prototype._decode.call(this);
+ while(!startInfo) {
+ startInfo = self._findPattern(self.START_PATTERN, offset, false, true);
+ if (!startInfo) {
+ return null;
+ }
+ narrowBarWidth = Math.floor((startInfo.end - startInfo.start) / 4);
+ leadingWhitespaceStart = startInfo.start - narrowBarWidth*10;
+ if (leadingWhitespaceStart >= 0) {
+ if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
+ return startInfo;
+ }
+ }
+ offset = startInfo.end;
+ startInfo = null;
+ }
+ };
- if (result && result.code && result.code.length === 13 && result.code.charAt(0) === "0") {
+ I2of5Reader.prototype._verifyTrailingWhitespace = function(endInfo) {
+ var self = this,
+ trailingWhitespaceEnd;
- result.code = result.code.substring(1);
- return result;
+ trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
+ if (trailingWhitespaceEnd < self._row.length) {
+ if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
+ return endInfo;
+ }
}
return null;
};
- return (UPCReader);
- }
-);
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
+ I2of5Reader.prototype._findEnd = function() {
+ var self = this,
+ endInfo,
+ tmp;
-define(
- 'ean_8_reader',[
- "./ean_reader"
- ],
- function(EANReader) {
- "use strict";
+ self._row.reverse();
+ endInfo = self._findPattern(self.STOP_PATTERN);
+ self._row.reverse();
- function EAN8Reader() {
- EANReader.call(this);
- }
+ if (endInfo === null) {
+ return null;
+ }
- var properties = {
- FORMAT: {value: "ean_8", writeable: false}
- };
+ // reverse numbers
+ tmp = endInfo.start;
+ endInfo.start = self._row.length - endInfo.end;
+ endInfo.end = self._row.length - tmp;
- EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
- EAN8Reader.prototype.constructor = EAN8Reader;
+ return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
+ };
- EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
+ I2of5Reader.prototype._decodePair = function(counterPair) {
var i,
+ code,
+ codes = [],
self = this;
- for ( i = 0; i < 4; i++) {
- code = self._decodeCode(code.end, self.CODE_G_START);
+ for (i = 0; i < counterPair.length; i++) {
+ code = self._decodeCode(counterPair[i]);
if (!code) {
return null;
}
- result.push(code.code);
- decodedCodes.push(code);
+ codes.push(code);
}
+ return codes;
+ };
- code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
- if (code === null) {
- return null;
- }
- decodedCodes.push(code);
+ I2of5Reader.prototype._decodeCode = function(counter) {
+ var j,
+ self = this,
+ sum = 0,
+ normalized,
+ error,
+ epsilon = self.AVG_CODE_ERROR,
+ code,
+ bestMatch = {
+ error : Number.MAX_VALUE,
+ code : -1,
+ start : 0,
+ end : 0
+ };
- for ( i = 0; i < 4; i++) {
- code = self._decodeCode(code.end, self.CODE_G_START);
- if (!code) {
- return null;
+ for ( j = 0; j < counter.length; j++) {
+ sum += counter[j];
+ }
+ normalized = self._normalize(counter);
+ if (normalized) {
+ for (code = 0; code < self.CODE_PATTERN.length; code++) {
+ error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
+ if (error < bestMatch.error) {
+ bestMatch.code = code;
+ bestMatch.error = error;
+ }
+ }
+ if (bestMatch.error < epsilon) {
+ return bestMatch;
}
- decodedCodes.push(code);
- result.push(code.code);
}
-
- return code;
- };
-
- return (EAN8Reader);
- }
-);
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-
-define(
- 'upc_e_reader',[
- "./ean_reader"
- ],
- function(EANReader) {
- "use strict";
-
- function UPCEReader() {
- EANReader.call(this);
- }
-
- var properties = {
- CODE_FREQUENCY : {value: [
- [ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
- [7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]},
- STOP_PATTERN: { value: [1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7]},
- FORMAT: {value: "upc_e", writeable: false}
+ return null;
};
- UPCEReader.prototype = Object.create(EANReader.prototype, properties);
- UPCEReader.prototype.constructor = UPCEReader;
-
- UPCEReader.prototype._decodePayload = function(code, result, decodedCodes) {
+ I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) {
var i,
self = this,
- codeFrequency = 0x0;
-
- for ( i = 0; i < 6; i++) {
- code = self._decodeCode(code.end);
- if (!code) {
+ pos = 0,
+ counterLength = counters.length,
+ counterPair = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]],
+ codes;
+
+ while (pos < counterLength) {
+ for (i = 0; i < 5; i++) {
+ counterPair[0][i] = counters[pos]*this.barSpaceRatio[0];
+ counterPair[1][i] = counters[pos + 1]*this.barSpaceRatio[1];
+ pos += 2;
+ }
+ codes = self._decodePair(counterPair);
+ if (!codes) {
return null;
}
- if (code.code >= self.CODE_G_START) {
- code.code = code.code - self.CODE_G_START;
- codeFrequency |= 1 << (5 - i);
+ for (i = 0; i < codes.length; i++) {
+ result.push(codes[i].code + "");
+ decodedCodes.push(codes[i]);
}
- result.push(code.code);
- decodedCodes.push(code);
- }
- if (!self._determineParity(codeFrequency, result)) {
- return null;
}
-
- return code;
+ return codes;
};
- UPCEReader.prototype._determineParity = function(codeFrequency, result) {
- var self =this,
- i,
- nrSystem;
-
- for (nrSystem = 0; nrSystem < self.CODE_FREQUENCY.length; nrSystem++){
- for ( i = 0; i < self.CODE_FREQUENCY[nrSystem].length; i++) {
- if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) {
- result.unshift(nrSystem);
- result.push(i);
- return true;
- }
- }
- }
- return false;
+ I2of5Reader.prototype._verifyCounterLength = function(counters) {
+ return (counters.length % 10 === 0);
};
- UPCEReader.prototype._convertToUPCA = function(result) {
- var upca = [result[0]],
- lastDigit = result[result.length - 2];
+ I2of5Reader.prototype._decode = function() {
+ var startInfo,
+ endInfo,
+ self = this,
+ code,
+ result = [],
+ decodedCodes = [],
+ counters;
- if (lastDigit <= 2) {
- upca = upca.concat(result.slice(1, 3))
- .concat([lastDigit, 0, 0, 0, 0])
- .concat(result.slice(3, 6));
- } else if (lastDigit === 3) {
- upca = upca.concat(result.slice(1, 4))
- .concat([0 ,0, 0, 0, 0])
- .concat(result.slice(4,6));
- } else if (lastDigit === 4) {
- upca = upca.concat(result.slice(1, 5))
- .concat([0, 0, 0, 0, 0, result[5]]);
- } else {
- upca = upca.concat(result.slice(1, 6))
- .concat([0, 0, 0, 0, lastDigit]);
+ startInfo = self._findStart();
+ if (!startInfo) {
+ return null;
}
+ decodedCodes.push(startInfo);
- upca.push(result[result.length - 1]);
- return upca;
- };
+ endInfo = self._findEnd();
+ if (!endInfo) {
+ return null;
+ }
- UPCEReader.prototype._checksum = function(result) {
- return EANReader.prototype._checksum.call(this, this._convertToUPCA(result));
- };
+ counters = self._fillCounters(startInfo.end, endInfo.start, false);
+ if (!self._verifyCounterLength(counters)) {
+ return null;
+ }
+ code = self._decodePayload(counters, result, decodedCodes);
+ if (!code) {
+ return null;
+ }
+ if (result.length % 2 !== 0 ||
+ result.length < 6) {
+ return null;
+ }
- UPCEReader.prototype._findEnd = function(offset, isWhite) {
- isWhite = true;
- return EANReader.prototype._findEnd.call(this, offset, isWhite);
+ decodedCodes.push(endInfo);
+ return {
+ code : result.join(""),
+ start : startInfo.start,
+ end : endInfo.end,
+ startInfo : startInfo,
+ decodedCodes : decodedCodes
+ };
};
- UPCEReader.prototype._verifyTrailingWhitespace = function(endInfo) {
- var self = this,
- trailingWhitespaceEnd;
-
- trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start)/2);
- if (trailingWhitespaceEnd < self._row.length) {
- if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
- return endInfo;
- }
+ I2of5Reader.CONFIG_KEYS = {
+ normalizeBarSpaceWidth: {
+ 'type': 'boolean',
+ 'default': false,
+ 'description': 'If true, the reader tries to normalize the' +
+ 'width-difference between bars and spaces'
}
};
- return (UPCEReader);
+ return (I2of5Reader);
}
);
/* jshint undef: true, unused: true, browser:true, devel: true */
@@ -7632,7 +8022,8 @@ define('barcode_decoder',[
'codabar_reader',
'upc_reader',
'ean_8_reader',
- 'upc_e_reader'
+ 'upc_e_reader',
+ 'i2of5_reader'
], function(
Bresenham,
ImageDebug,
@@ -7643,7 +8034,8 @@ define('barcode_decoder',[
CodabarReader,
UPCReader,
EAN8Reader,
- UPCEReader) {
+ UPCEReader,
+ I2of5Reader) {
"use strict";
var readers = {
@@ -7654,7 +8046,8 @@ define('barcode_decoder',[
code_39_vin_reader: Code39VINReader,
codabar_reader: CodabarReader,
upc_reader: UPCReader,
- upc_e_reader: UPCEReader
+ upc_e_reader: UPCEReader,
+ i2of5_reader: I2of5Reader
};
var BarcodeDecoder = {
create : function(config, inputImageWrapper) {
@@ -7707,11 +8100,21 @@ define('barcode_decoder',[
}
function initReaders() {
- var i;
- for ( i = 0; i < config.readers.length; i++) {
- console.log(config.readers[i]);
- _barcodeReaders.push(new readers[config.readers[i]]());
- }
+ config.readers.forEach(function(readerConfig) {
+ var reader,
+ config = {};
+
+ if (typeof readerConfig === 'object') {
+ reader = readerConfig.format;
+ config = readerConfig.config;
+ } else if (typeof readerConfig === 'string') {
+ reader = readerConfig;
+ }
+ _barcodeReaders.push(new readers[reader](config));
+ });
+ console.log("Registered Readers: " + _barcodeReaders
+ .map(function(reader) {return JSON.stringify({format: reader.FORMAT, config: reader.config});})
+ .join(', '));
}
function initConfig() {
@@ -7991,46 +8394,6 @@ define('frame_grabber',["cv_utils"], function(CVUtils) {
return (FrameGrabber);
});
-/* jshint undef: true, unused: true, browser:true, devel: true */
-/* global define */
-
-define('html_utils',[], function() {
- "use strict";
-
- function createNode(htmlStr) {
- var temp = document.createElement('div');
-
- temp.innerHTML = htmlStr;
- while (temp.firstChild) {
- return temp.firstChild;
- }
- }
-
- function mergeObjects(obj1, obj2) {
- for (var p in obj2) {
- try {
- if (obj2[p].constructor == Object) {
- obj1[p] = mergeObjects(obj1[p], obj2[p]);
- } else {
- obj1[p] = obj2[p];
- }
- } catch(e) {
- obj1[p] = obj2[p];
- }
- }
-
- return obj1;
- }
-
- return {
- createNode : function(htmlStr) {
- return createNode(htmlStr);
- },
- mergeObjects : function(obj1, obj2) {
- return mergeObjects(obj1, obj2);
- }
- };
-});
/**
* The basic configuration
*/
@@ -8392,8 +8755,6 @@ define('result_collector',["image_debug"], function(ImageDebug) {
define('quagga',[
- "code_128_reader",
- "ean_reader",
"input_stream",
"image_wrapper",
"barcode_locator",
@@ -8405,9 +8766,7 @@ define('quagga',[
"camera_access",
"image_debug",
"result_collector"],
-function(Code128Reader,
- EANReader,
- InputStream,
+function(InputStream,
ImageWrapper,
BarcodeLocator,
BarcodeDecoder,
@@ -8878,10 +9237,6 @@ function(Code128Reader,
start();
});
},
- Reader: {
- EANReader : EANReader,
- Code128Reader : Code128Reader
- },
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug,
ResultCollector: ResultCollector
diff --git a/dist/quagga.min.js b/dist/quagga.min.js
index 712e26a1..9a38871f 100644
--- a/dist/quagga.min.js
+++ b/dist/quagga.min.js
@@ -1,9 +1,11 @@
-/*! quagga 2015-07-08 */
+/*! quagga 2015-07-28 */
!function(a,b){var c=b.toString();"undefined"!=typeof module?module.exports=b(c):a.Quagga=b(c)}(this,function(a){/**
* @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/almond for details
*/
-var b,c,d;!function(a){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(n=n.slice(0,n.length-1),a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.concat(a),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(b,c){return function(){return n.apply(a,v.call(arguments,0).concat([b,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(b){if(e(r,b)){var c=r[b];delete r[b],t[b]=!0,m.apply(a,c)}if(!e(q,b)&&!e(t,b))throw new Error("No "+b);return q[b]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(b,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||b,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;ng)return Number.MAX_VALUE;d+=e}return d/f},a.prototype._nextSet=function(a,b){var c;for(b=b||0,c=b;c1)for(c=0;cd?(j.start=c-g,j.end=c,j.counter=e,j):null;i++,e[i]=1,h=!h}}else for(e.push(0),c=g;ca?0:a,d=a;b>d;d++)if(this._row[d]!==c)return!1;return!0},Object.defineProperty(a.prototype,"FORMAT",{value:"unknown",writeable:!1}),a.DIRECTION={FORWARD:1,REVERSE:-1},a.Exception={StartNotFoundException:"Start-Info was not found!",CodeNotFoundException:"Code could not be found!",PatternNotFoundException:"Pattern could not be found!"},a}),d("code_128_reader",["./barcode_reader"],function(a){"use strict";function b(){a.call(this)}var c={CODE_SHIFT:{value:98},CODE_C:{value:99},CODE_B:{value:100},CODE_A:{value:101},START_CODE_A:{value:103},START_CODE_B:{value:104},START_CODE_C:{value:105},STOP_CODE:{value:106},MODULO:{value:11},CODE_PATTERN:{value:[[2,1,2,2,2,2],[2,2,2,1,2,2],[2,2,2,2,2,1],[1,2,1,2,2,3],[1,2,1,3,2,2],[1,3,1,2,2,2],[1,2,2,2,1,3],[1,2,2,3,1,2],[1,3,2,2,1,2],[2,2,1,2,1,3],[2,2,1,3,1,2],[2,3,1,2,1,2],[1,1,2,2,3,2],[1,2,2,1,3,2],[1,2,2,2,3,1],[1,1,3,2,2,2],[1,2,3,1,2,2],[1,2,3,2,2,1],[2,2,3,2,1,1],[2,2,1,1,3,2],[2,2,1,2,3,1],[2,1,3,2,1,2],[2,2,3,1,1,2],[3,1,2,1,3,1],[3,1,1,2,2,2],[3,2,1,1,2,2],[3,2,1,2,2,1],[3,1,2,2,1,2],[3,2,2,1,1,2],[3,2,2,2,1,1],[2,1,2,1,2,3],[2,1,2,3,2,1],[2,3,2,1,2,1],[1,1,1,3,2,3],[1,3,1,1,2,3],[1,3,1,3,2,1],[1,1,2,3,1,3],[1,3,2,1,1,3],[1,3,2,3,1,1],[2,1,1,3,1,3],[2,3,1,1,1,3],[2,3,1,3,1,1],[1,1,2,1,3,3],[1,1,2,3,3,1],[1,3,2,1,3,1],[1,1,3,1,2,3],[1,1,3,3,2,1],[1,3,3,1,2,1],[3,1,3,1,2,1],[2,1,1,3,3,1],[2,3,1,1,3,1],[2,1,3,1,1,3],[2,1,3,3,1,1],[2,1,3,1,3,1],[3,1,1,1,2,3],[3,1,1,3,2,1],[3,3,1,1,2,1],[3,1,2,1,1,3],[3,1,2,3,1,1],[3,3,2,1,1,1],[3,1,4,1,1,1],[2,2,1,4,1,1],[4,3,1,1,1,1],[1,1,1,2,2,4],[1,1,1,4,2,2],[1,2,1,1,2,4],[1,2,1,4,2,1],[1,4,1,1,2,2],[1,4,1,2,2,1],[1,1,2,2,1,4],[1,1,2,4,1,2],[1,2,2,1,1,4],[1,2,2,4,1,1],[1,4,2,1,1,2],[1,4,2,2,1,1],[2,4,1,2,1,1],[2,2,1,1,1,4],[4,1,3,1,1,1],[2,4,1,1,1,2],[1,3,4,1,1,1],[1,1,1,2,4,2],[1,2,1,1,4,2],[1,2,1,2,4,1],[1,1,4,2,1,2],[1,2,4,1,1,2],[1,2,4,2,1,1],[4,1,1,2,1,2],[4,2,1,1,1,2],[4,2,1,2,1,1],[2,1,2,1,4,1],[2,1,4,1,2,1],[4,1,2,1,2,1],[1,1,1,1,4,3],[1,1,1,3,4,1],[1,3,1,1,4,1],[1,1,4,1,1,3],[1,1,4,3,1,1],[4,1,1,1,1,3],[4,1,1,3,1,1],[1,1,3,1,4,1],[1,1,4,1,3,1],[3,1,1,1,4,1],[4,1,1,1,3,1],[2,1,1,4,1,2],[2,1,1,2,1,4],[2,1,1,2,3,2],[2,3,3,1,1,1,2]]},SINGLE_CODE_ERROR:{value:1},AVG_CODE_ERROR:{value:.5},FORMAT:{value:"code_128",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decodeCode=function(a){var b,c,d,e,f=[0,0,0,0,0,0],g=this,h=a,i=!g._row[h],j=0,k={error:Number.MAX_VALUE,code:-1,start:a,end:a};for(b=h;bd;d++)g[d]=g[d+2];g[4]=0,g[5]=0,k--}else k++;g[k]=1,j=!j}return null},b.prototype._decode=function(){var a,b,c,d=this,e=d._findStart(),f=null,g=!1,h=[],i=0,j=0,k=[],l=[],m=!1;if(null===e)return null;switch(f={code:e.code,start:e.start,end:e.end},l.push(f),j=f.code,f.code){case d.START_CODE_A:a=d.CODE_A;break;case d.START_CODE_B:a=d.CODE_B;break;case d.START_CODE_C:a=d.CODE_C;break;default:return null}for(;!g;){if(b=m,m=!1,f=d._decodeCode(f.end),null!==f)switch(f.code!==d.STOP_CODE&&(k.push(f.code),i++,j+=i*f.code),l.push(f),a){case d.CODE_A:if(f.code<64)h.push(String.fromCharCode(32+f.code));else if(f.code<96)h.push(String.fromCharCode(f.code-64));else switch(f.code){case d.CODE_SHIFT:m=!0,a=d.CODE_B;break;case d.CODE_B:a=d.CODE_B;break;case d.CODE_C:a=d.CODE_C;break;case d.STOP_CODE:g=!0}break;case d.CODE_B:if(f.code<96)h.push(String.fromCharCode(32+f.code));else switch(f.code!=d.STOP_CODE&&(c=!1),f.code){case d.CODE_SHIFT:m=!0,a=d.CODE_A;break;case d.CODE_A:a=d.CODE_A;break;case d.CODE_C:a=d.CODE_C;break;case d.STOP_CODE:g=!0}break;case d.CODE_C:switch(f.code<100&&h.push(f.code<10?"0"+f.code:f.code),f.code){case d.CODE_A:a=d.CODE_A;break;case d.CODE_B:a=d.CODE_B;break;case d.STOP_CODE:g=!0}}else g=!0;b&&(a=a==d.CODE_A?d.CODE_B:d.CODE_A)}return null===f?null:(f.end=d._nextUnset(d._row,f.end),d._verifyTrailingWhitespace(f)?(j-=i*k[k.length-1],j%103!=k[k.length-1]?null:h.length?(h.splice(h.length-1,1),{code:h.join(""),start:e.start,end:f.end,codeset:a,startInfo:e,decodedCodes:l,endInfo:f}):null):null)},a.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start)/2,bd;d++)e=h._matchPattern(f,h.CODE_PATTERN[d]),eh.AVG_CODE_ERROR?null:l}}else k++;g[k]=1,j=!j}return null},b.prototype._findPattern=function(a,b,c,d,e){var f,g,h,i,j,k=[],l=this,m=0,n={error:Number.MAX_VALUE,code:-1,start:0,end:0};for(b||(b=l._nextSet(l._row)),void 0===c&&(c=!1),void 0===d&&(d=!0),void 0===e&&(e=l.AVG_CODE_ERROR),f=0;fg))return n.error=g,n.start=f-i,n.end=f,n;if(!d)return null;for(h=0;h=0&&c._matchRange(a,b.start,0))return b;d=b.end,b=null}},b.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start),bd;d++){if(a=f._decodeCode(a.end),!a)return null;a.code>=f.CODE_G_START?(a.code=a.code-f.CODE_G_START,g|=1<<5-d):g|=0<<5-d,b.push(a.code),c.push(a)}if(e=f._calculateFirstDigit(g),null===e)return null;if(b.unshift(e),a=f._findPattern(f.MIDDLE_PATTERN,a.end,!0,!1),null===a)return null;for(c.push(a),d=0;6>d;d++){if(a=f._decodeCode(a.end,f.CODE_G_START),!a)return null;c.push(a),b.push(a.code)}return a},b.prototype._decode=function(){var a,b,c=this,d=[],e=[];return(a=c._findStart())?(b={code:a.code,start:a.start,end:a.end},e.push(b),(b=c._decodePayload(b,d,e))&&(b=c._findEnd(b.end,!1))?(e.push(b),c._checksum(d)?{code:d.join(""),start:a.start,end:b.end,codeset:"",startInfo:a,decodedCodes:e}:null):null):null},b.prototype._checksum=function(a){var b,c=0;for(b=a.length-2;b>=0;b-=2)c+=a[b];for(c*=3,b=a.length-1;b>=0;b-=2)c+=a[b];return c%10===0},b}),d("image_loader",[],function(){"use strict";function a(a,b){a.onload=function(){b.loaded(this)}}var b={};return b.load=function(b,c,d,e,f){var g,h,i,j=new Array(e),k=new Array(j.length);if(f===!1)j[0]=b;else for(g=0;g1?f.size:Math.floor(b/e*f.size):b,d=f.size?b/e>1?Math.floor(e/b*f.size):f.size:e,j.x=c,j.y=d}var c,d,e={},f=null,g=["canrecord","ended"],h={},i={x:0,y:0},j={x:0,y:0};return e.getRealWidth=function(){return a.videoWidth},e.getRealHeight=function(){return a.videoHeight},e.getWidth=function(){return c},e.getHeight=function(){return d},e.setWidth=function(a){c=a},e.setHeight=function(a){d=a},e.setInputStream=function(b){f=b,a.src="undefined"!=typeof b.src?b.src:""},e.ended=function(){return a.ended},e.getConfig=function(){return f},e.setAttribute=function(b,c){a.setAttribute(b,c)},e.pause=function(){a.pause()},e.play=function(){a.play()},e.setCurrentTime=function(b){"LiveStream"!==f.type&&(a.currentTime=b)},e.addEventListener=function(b,c,d){-1!==g.indexOf(b)?(h[b]||(h[b]=[]),h[b].push(c)):a.addEventListener(b,c,d)},e.clearEventHandlers=function(){g.forEach(function(b){var c=h[b];c&&c.length>0&&c.forEach(function(c){a.removeEventListener(b,c)})})},e.trigger=function(a,c){var d,f=h[a];if("canrecord"===a&&b(),f&&f.length>0)for(d=0;d1?g.size:Math.floor(h/i*g.size):h,e=g.size?h/i>1?Math.floor(i/h*g.size):g.size:i,u.x=d,u.y=e,l=!0,j=0,setTimeout(function(){c("canrecord",[])},0)},o,n,g.sequence)}function c(a,b){var c,d=s[a];if(d&&d.length>0)for(c=0;cj?j++:setTimeout(function(){q=!0,c("ended",[])},0)),a):null},f},b}),glMatrixArrayType=Float32Array,"undefined"!=typeof window&&(window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL),d("typedefs",function(a){return function(){var b;return b||a.typedefs}}(this)),d("subImage",["typedefs"],function(){"use strict";function a(a,b,c){c||(c={data:null,size:b}),this.data=c.data,this.originalSize=c.size,this.I=c,this.from=a,this.size=b}return a.prototype.show=function(a,b){var c,d,e,f,g,h,i;for(b||(b=1),c=a.getContext("2d"),a.width=this.size.x,a.height=this.size.y,d=c.getImageData(0,0,a.width,a.height),e=d.data,f=0,g=0;gb?!0:!1},getPoints:function(){return f},getCenter:function(){return g}}},createPoint:function(a,b,c){return{rad:a[c],point:a,id:b}}};return a});var e={};e.create=function(a){var b;return a?(b=new glMatrixArrayType(3),b[0]=a[0],b[1]=a[1],b[2]=a[2]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0]:3),b},e.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b},e.add=function(a,b,c){return c&&a!=c?(c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2],c):(a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a)},e.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2],c):(a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a)},e.negate=function(a,b){return b||(b=a),b[0]=-a[0],b[1]=-a[1],b[2]=-a[2],b},e.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a)},e.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=Math.sqrt(c*c+d*d+e*e);return f?1==f?(b[0]=c,b[1]=d,b[2]=e,b):(f=1/f,b[0]=c*f,b[1]=d*f,b[2]=e*f,b):(b[0]=0,b[1]=0,b[2]=0,b)},e.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=b[0],h=b[1],i=b[2];return c[0]=e*i-f*h,c[1]=f*g-d*i,c[2]=d*h-e*g,c},e.length=function(a){var b=a[0],c=a[1],d=a[2];return Math.sqrt(b*b+c*c+d*d)},e.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]},e.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1],f=a[2]-b[2],g=Math.sqrt(d*d+e*e+f*f);return g?(g=1/g,c[0]=d*g,c[1]=e*g,c[2]=f*g,c):(c[0]=0,c[1]=0,c[2]=0,c)},e.lerp=function(a,b,c,d){return d||(d=a),d[0]=a[0]+c*(b[0]-a[0]),d[1]=a[1]+c*(b[1]-a[1]),d[2]=a[2]+c*(b[2]-a[2]),d},e.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};var f={};f.create=function(a){var b;return a?(b=new glMatrixArrayType(9),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0,0,0,0,0,0]:9),b},f.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b},f.identity=function(a){return a[0]=1,a[1]=0,a[2]=0,a[3]=0,a[4]=1,a[5]=0,a[6]=0,a[7]=0,a[8]=1,a},f.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[5];return a[1]=a[3],a[2]=a[6],a[3]=c,a[5]=a[7],a[6]=d,a[7]=e,a}return b[0]=a[0],b[1]=a[3],b[2]=a[6],b[3]=a[1],b[4]=a[4],b[5]=a[7],b[6]=a[2],b[7]=a[5],b[8]=a[8],b},f.toMat4=function(a,b){return b||(b=g.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=0,b[4]=a[3],b[5]=a[4],b[6]=a[5],b[7]=0,b[8]=a[6],b[9]=a[7],b[10]=a[8],b[11]=0,b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},f.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};var g={};g.create=function(a){var b;return a?(b=new glMatrixArrayType(16),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]:16),b},g.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15],b},g.identity=function(a){return a[0]=1,a[1]=0,a[2]=0,a[3]=0,a[4]=0,a[5]=1,a[6]=0,a[7]=0,a[8]=0,a[9]=0,a[10]=1,a[11]=0,a[12]=0,a[13]=0,a[14]=0,a[15]=1,a},g.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[3],f=a[6],g=a[7],h=a[11];return a[1]=a[4],a[2]=a[8],a[3]=a[12],a[4]=c,a[6]=a[9],a[7]=a[13],a[8]=d,a[9]=f,a[11]=a[14],a[12]=e,a[13]=g,a[14]=h,a}return b[0]=a[0],b[1]=a[4],b[2]=a[8],b[3]=a[12],b[4]=a[1],b[5]=a[5],b[6]=a[9],b[7]=a[13],b[8]=a[2],b[9]=a[6],b[10]=a[10],b[11]=a[14],b[12]=a[3],b[13]=a[7],b[14]=a[11],b[15]=a[15],b},g.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],f=a[4],g=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],m=a[11],n=a[12],o=a[13],p=a[14],q=a[15];return n*k*h*e-j*o*h*e-n*g*l*e+f*o*l*e+j*g*p*e-f*k*p*e-n*k*d*i+j*o*d*i+n*c*l*i-b*o*l*i-j*c*p*i+b*k*p*i+n*g*d*m-f*o*d*m-n*c*h*m+b*o*h*m+f*c*p*m-b*g*p*m-j*g*d*q+f*k*d*q+j*c*h*q-b*k*h*q-f*c*l*q+b*g*l*q},g.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],m=a[10],n=a[11],o=a[12],p=a[13],q=a[14],r=a[15],s=c*h-d*g,t=c*i-e*g,u=c*j-f*g,v=d*i-e*h,w=d*j-f*h,x=e*j-f*i,y=k*p-l*o,z=k*q-m*o,A=k*r-n*o,B=l*q-m*p,C=l*r-n*p,D=m*r-n*q,E=1/(s*D-t*C+u*B+v*A-w*z+x*y);return b[0]=(h*D-i*C+j*B)*E,b[1]=(-d*D+e*C-f*B)*E,b[2]=(p*x-q*w+r*v)*E,b[3]=(-l*x+m*w-n*v)*E,b[4]=(-g*D+i*A-j*z)*E,b[5]=(c*D-e*A+f*z)*E,b[6]=(-o*x+q*u-r*t)*E,b[7]=(k*x-m*u+n*t)*E,b[8]=(g*C-h*A+j*y)*E,b[9]=(-c*C+d*A-f*y)*E,b[10]=(o*w-p*u+r*s)*E,b[11]=(-k*w+l*u-n*s)*E,b[12]=(-g*B+h*z-i*y)*E,b[13]=(c*B-d*z+e*y)*E,b[14]=(-o*v+p*t-q*s)*E,b[15]=(k*v-l*t+m*s)*E,b},g.toRotationMat=function(a,b){return b||(b=g.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},g.toMat3=function(a,b){return b||(b=f.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[4],b[4]=a[5],b[5]=a[6],b[6]=a[8],b[7]=a[9],b[8]=a[10],b},g.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],h=a[5],i=a[6],j=a[8],k=a[9],l=a[10],m=l*h-i*k,n=-l*g+i*j,o=k*g-h*j,p=c*m+d*n+e*o;if(!p)return null;var q=1/p;return b||(b=f.create()),b[0]=m*q,b[1]=(-l*d+e*k)*q,b[2]=(i*d-e*h)*q,b[3]=n*q,b[4]=(l*c-e*j)*q,b[5]=(-i*c+e*g)*q,b[6]=o*q,b[7]=(-k*c+d*j)*q,b[8]=(h*c-d*g)*q,b},g.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],m=a[9],n=a[10],o=a[11],p=a[12],q=a[13],r=a[14],s=a[15],t=b[0],u=b[1],v=b[2],w=b[3],x=b[4],y=b[5],z=b[6],A=b[7],B=b[8],C=b[9],D=b[10],E=b[11],F=b[12],G=b[13],H=b[14],I=b[15];return c[0]=t*d+u*h+v*l+w*p,c[1]=t*e+u*i+v*m+w*q,c[2]=t*f+u*j+v*n+w*r,c[3]=t*g+u*k+v*o+w*s,c[4]=x*d+y*h+z*l+A*p,c[5]=x*e+y*i+z*m+A*q,c[6]=x*f+y*j+z*n+A*r,c[7]=x*g+y*k+z*o+A*s,c[8]=B*d+C*h+D*l+E*p,c[9]=B*e+C*i+D*m+E*q,c[10]=B*f+C*j+D*n+E*r,c[11]=B*g+C*k+D*o+E*s,c[12]=F*d+G*h+H*l+I*p,c[13]=F*e+G*i+H*m+I*q,c[14]=F*f+G*j+H*n+I*r,c[15]=F*g+G*k+H*o+I*s,c},g.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[4]*e+a[8]*f+a[12],c[1]=a[1]*d+a[5]*e+a[9]*f+a[13],c[2]=a[2]*d+a[6]*e+a[10]*f+a[14],c},g.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3];return c[0]=a[0]*d+a[4]*e+a[8]*f+a[12]*g,c[1]=a[1]*d+a[5]*e+a[9]*f+a[13]*g,c[2]=a[2]*d+a[6]*e+a[10]*f+a[14]*g,c[3]=a[3]*d+a[7]*e+a[11]*f+a[15]*g,c},g.translate=function(a,b,c){var d=b[0],e=b[1],f=b[2];if(!c||a==c)return a[12]=a[0]*d+a[4]*e+a[8]*f+a[12],a[13]=a[1]*d+a[5]*e+a[9]*f+a[13],a[14]=a[2]*d+a[6]*e+a[10]*f+a[14],a[15]=a[3]*d+a[7]*e+a[11]*f+a[15],a;var g=a[0],h=a[1],i=a[2],j=a[3],k=a[4],l=a[5],m=a[6],n=a[7],o=a[8],p=a[9],q=a[10],r=a[11];return c[0]=g,c[1]=h,c[2]=i,c[3]=j,c[4]=k,c[5]=l,c[6]=m,c[7]=n,c[8]=o,c[9]=p,c[10]=q,c[11]=r,c[12]=g*d+k*e+o*f+a[12],c[13]=h*d+l*e+p*f+a[13],c[14]=i*d+m*e+q*f+a[14],c[15]=j*d+n*e+r*f+a[15],c},g.scale=function(a,b,c){var d=b[0],e=b[1],f=b[2];return c&&a!=c?(c[0]=a[0]*d,c[1]=a[1]*d,c[2]=a[2]*d,c[3]=a[3]*d,c[4]=a[4]*e,c[5]=a[5]*e,c[6]=a[6]*e,c[7]=a[7]*e,c[8]=a[8]*f,c[9]=a[9]*f,c[10]=a[10]*f,c[11]=a[11]*f,c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15],c):(a[0]*=d,a[1]*=d,a[2]*=d,a[3]*=d,a[4]*=e,a[5]*=e,a[6]*=e,a[7]*=e,a[8]*=f,a[9]*=f,a[10]*=f,a[11]*=f,a)},g.rotate=function(a,b,c,d){var e=c[0],f=c[1],g=c[2],h=Math.sqrt(e*e+f*f+g*g);if(!h)return null;1!=h&&(h=1/h,e*=h,f*=h,g*=h);var i=Math.sin(b),j=Math.cos(b),k=1-j,l=a[0],m=a[1],n=a[2],o=a[3],p=a[4],q=a[5],r=a[6],s=a[7],t=a[8],u=a[9],v=a[10],w=a[11],x=e*e*k+j,y=f*e*k+g*i,z=g*e*k-f*i,A=e*f*k-g*i,B=f*f*k+j,C=g*f*k+e*i,D=e*g*k+f*i,E=f*g*k-e*i,F=g*g*k+j;return d?a!=d&&(d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a,d[0]=l*x+p*y+t*z,d[1]=m*x+q*y+u*z,d[2]=n*x+r*y+v*z,d[3]=o*x+s*y+w*z,d[4]=l*A+p*B+t*C,d[5]=m*A+q*B+u*C,d[6]=n*A+r*B+v*C,d[7]=o*A+s*B+w*C,d[8]=l*D+p*E+t*F,d[9]=m*D+q*E+u*F,d[10]=n*D+r*E+v*F,d[11]=o*D+s*E+w*F,d},g.rotateX=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[4],g=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],m=a[11];return c?a!=c&&(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[4]=f*e+j*d,c[5]=g*e+k*d,c[6]=h*e+l*d,c[7]=i*e+m*d,c[8]=f*-d+j*e,c[9]=g*-d+k*e,c[10]=h*-d+l*e,c[11]=i*-d+m*e,c},g.rotateY=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[0],g=a[1],h=a[2],i=a[3],j=a[8],k=a[9],l=a[10],m=a[11];return c?a!=c&&(c[4]=a[4],c[5]=a[5],c[6]=a[6],c[7]=a[7],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[0]=f*e+j*-d,c[1]=g*e+k*-d,c[2]=h*e+l*-d,c[3]=i*e+m*-d,c[8]=f*d+j*e,c[9]=g*d+k*e,c[10]=h*d+l*e,c[11]=i*d+m*e,c},g.rotateZ=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[0],g=a[1],h=a[2],i=a[3],j=a[4],k=a[5],l=a[6],m=a[7];return c?a!=c&&(c[8]=a[8],c[9]=a[9],c[10]=a[10],c[11]=a[11],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[0]=f*e+j*d,c[1]=g*e+k*d,c[2]=h*e+l*d,c[3]=i*e+m*d,c[4]=f*-d+j*e,c[5]=g*-d+k*e,c[6]=h*-d+l*e,c[7]=i*-d+m*e,c},g.frustum=function(a,b,c,d,e,f,h){h||(h=g.create());var i=b-a,j=d-c,k=f-e;return h[0]=2*e/i,h[1]=0,h[2]=0,h[3]=0,h[4]=0,h[5]=2*e/j,h[6]=0,h[7]=0,h[8]=(b+a)/i,h[9]=(d+c)/j,h[10]=-(f+e)/k,h[11]=-1,h[12]=0,h[13]=0,h[14]=-(f*e*2)/k,h[15]=0,h},g.perspective=function(a,b,c,d,e){var f=c*Math.tan(a*Math.PI/360),h=f*b;return g.frustum(-h,h,-f,f,c,d,e)},g.ortho=function(a,b,c,d,e,f,h){h||(h=g.create());var i=b-a,j=d-c,k=f-e;return h[0]=2/i,h[1]=0,h[2]=0,h[3]=0,h[4]=0,h[5]=2/j,h[6]=0,h[7]=0,h[8]=0,h[9]=0,h[10]=-2/k,h[11]=0,h[12]=-(a+b)/i,h[13]=-(d+c)/j,h[14]=-(f+e)/k,h[15]=1,h},g.lookAt=function(a,b,c,d){d||(d=g.create());var e=a[0],f=a[1],h=a[2],i=c[0],j=c[1],k=c[2],l=b[0],m=b[1],n=b[2];if(e==l&&f==m&&h==n)return g.identity(d);var o,p,q,r,s,t,u,v,w,x;return o=e-b[0],p=f-b[1],q=h-b[2],x=1/Math.sqrt(o*o+p*p+q*q),o*=x,p*=x,q*=x,r=j*q-k*p,s=k*o-i*q,t=i*p-j*o,x=Math.sqrt(r*r+s*s+t*t),x?(x=1/x,r*=x,s*=x,t*=x):(r=0,s=0,t=0),u=p*t-q*s,v=q*r-o*t,w=o*s-p*r,x=Math.sqrt(u*u+v*v+w*w),x?(x=1/x,u*=x,v*=x,w*=x):(u=0,v=0,w=0),d[0]=r,d[1]=u,d[2]=o,d[3]=0,d[4]=s,d[5]=v,d[6]=p,d[7]=0,d[8]=t,d[9]=w,d[10]=q,d[11]=0,d[12]=-(r*e+s*f+t*h),d[13]=-(u*e+v*f+w*h),d[14]=-(o*e+p*f+q*h),d[15]=1,d},g.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+",\n "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+",\n "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+",\n "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"},quat4={},quat4.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},quat4.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b},quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];return b&&a!=b?(b[0]=c,b[1]=d,b[2]=e,b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),b):(a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),a)},quat4.inverse=function(a,b){return b&&a!=b?(b[0]=-a[0],b[1]=-a[1],b[2]=-a[2],b[3]=a[3],b):(a[0]*=-1,a[1]*=-1,a[2]*=-1,a)},quat4.length=function(a){var b=a[0],c=a[1],d=a[2],e=a[3];return Math.sqrt(b*b+c*c+d*d+e*e)},quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=Math.sqrt(c*c+d*d+e*e+f*f);return 0==g?(b[0]=0,b[1]=0,b[2]=0,b[3]=0,b):(g=1/g,b[0]=c*g,b[1]=d*g,b[2]=e*g,b[3]=f*g,b)},quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=b[0],i=b[1],j=b[2],k=b[3];return c[0]=d*k+g*h+e*j-f*i,c[1]=e*k+g*i+f*h-d*j,c[2]=f*k+g*j+d*i-e*h,c[3]=g*k-d*h-e*i-f*j,c},quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=a[0],h=a[1],i=a[2],j=a[3],k=j*d+h*f-i*e,l=j*e+i*d-g*f,m=j*f+g*e-h*d,n=-g*d-h*e-i*f;return c[0]=k*j+n*-g+l*-i-m*-h,c[1]=l*j+n*-h+m*-g-k*-i,c[2]=m*j+n*-i+k*-h-l*-g,c},quat4.toMat3=function(a,b){b||(b=f.create());var c=a[0],d=a[1],e=a[2],g=a[3],h=c+c,i=d+d,j=e+e,k=c*h,l=c*i,m=c*j,n=d*i,o=d*j,p=e*j,q=g*h,r=g*i,s=g*j;return b[0]=1-(n+p),b[1]=l-s,b[2]=m+r,b[3]=l+s,b[4]=1-(k+p),b[5]=o-q,b[6]=m-r,b[7]=o+q,b[8]=1-(k+n),b},quat4.toMat4=function(a,b){b||(b=g.create());var c=a[0],d=a[1],e=a[2],f=a[3],h=c+c,i=d+d,j=e+e,k=c*h,l=c*i,m=c*j,n=d*i,o=d*j,p=e*j,q=f*h,r=f*i,s=f*j;return b[0]=1-(n+p),b[1]=l-s,b[2]=m+r,b[3]=0,b[4]=l+s,b[5]=1-(k+p),b[6]=o-q,b[7]=0,b[8]=m-r,b[9]=o+q,b[10]=1-(k+n),b[11]=0,b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},quat4.slerp=function(a,b,c,d){d||(d=a);var e=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];if(Math.abs(e)>=1)return d!=a&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3]),d;var f=Math.acos(e),g=Math.sqrt(1-e*e);if(Math.abs(g)<.001)return d[0]=.5*a[0]+.5*b[0],d[1]=.5*a[1]+.5*b[1],d[2]=.5*a[2]+.5*b[2],d[3]=.5*a[3]+.5*b[3],d;var h=Math.sin((1-c)*f)/g,i=Math.sin(c*f)/g;return d[0]=a[0]*h+b[0]*i,d[1]=a[1]*h+b[1]*i,d[2]=a[2]*h+b[2]*i,d[3]=a[3]*h+b[3]*i,d},quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"},d("glMatrix",["typedefs"],function(a){return function(){var b;return b||a.glMatrix}}(this)),g.xVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3];return c[0]=a[0]*d+a[1]*e+a[2]*f+a[3]*g,c[1]=a[4]*d+a[5]*e+a[6]*f+a[7]*g,c[2]=a[8]*d+a[9]*e+a[10]*f+a[11]*g,c[3]=a[12]*d+a[13]*e+a[14]*f+a[15]*g,c},f.scale=function(a,b,c){return c&&a!=c?(c=f.create(),c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c[4]=a[4]*b,c[5]=a[5]*b,c[6]=a[6]*b,c[7]=a[7]*b,c[8]=a[8]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a[4]*=b,a[5]*=b,a[6]*=b,a[7]*=b,a[8]*=b,a)},f.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=1/(c*g*k+d*h*i+e*f*j-e*g*i-d*f*k-c*h*j);return b[0]=(g*k-h*j)*l,b[1]=(e*j-d*k)*l,b[2]=(d*h-e*g)*l,b[3]=(h*i-f*k)*l,b[4]=(c*k-e*i)*l,b[5]=(e*f-c*h)*l,b[6]=(f*j-g*i)*l,b[7]=(d*i-c*j)*l,b[8]=(c*g-d*f)*l,b},f.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],m=b[0],n=b[1],o=b[2],p=b[3],q=b[4],r=b[5],s=b[6],t=b[7],u=b[8];return c[0]=d*m+e*p+f*s,c[1]=d*n+e*q+f*t,c[2]=d*o+e*r+f*u,c[3]=g*m+h*p+i*s,c[4]=g*n+h*q+i*t,c[5]=g*o+h*r+i*u,c[6]=j*m+k*p+l*s,c[7]=j*n+k*q+l*t,c[8]=j*o+k*r+l*u,c},f.xVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[1]*e+a[2]*f,c[1]=a[3]*d+a[4]*e+a[5]*f,c[2]=a[6]*d+a[7]*e+a[8]*f,c};var h={};h.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},h.project=function(a,b){return b||(b=a),b[0]=a[0]/a[3],b[1]=a[1]/a[3],b[2]=a[2]/a[3],b
-},h.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[4]*=b,a)},h.xMat4=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3];return c[0]=b[0]*d+b[4]*e+b[8]*f+b[12]*g,c[1]=b[1]*d+b[5]*e+b[9]*f+b[13]*g,c[2]=b[2]*d+b[6]*e+b[10]*f+b[14]*g,c[3]=b[3]*d+b[7]*e+b[11]*f+b[15]*g,c};var i={};i.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},i.xVec2=function(a,b,c){c||(c=b);var d=b[0],e=b[1];return c[0]=a[0]*d+a[1]*e,c[1]=a[2]*d+a[3]*e,c},i.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a)},i.determinant=function(a){return a[0]*a[3]-a[1]*a[2]},i.inverse=function(a){var b=1/i.determinant(a),c=a[3]*b,d=-a[1]*b,e=-a[2]*b,f=a[0];return a[0]=c,a[1]=d,a[2]=e,a[3]=f,a};var j={};j.create=function(a){var b;return a?(b=new glMatrixArrayType(2),b[0]=a[0],b[1]=a[1]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0]:2),b},j.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c):(a[0]-=b[0],a[1]-=b[1],a)},j.add=function(a,b,c){return c&&a!=c?(c[0]=a[0]+b[0],c[1]=a[1]+b[1],c):(a[0]+=b[0],a[1]+=b[1],a)},j.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c):(a[0]*=b,a[1]*=b,a)},j.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=Math.sqrt(c*c+d*d);return e?1==e?(b[0]=c,b[1]=d,b):(e=1/e,b[0]=c*e,b[1]=d*e,b):(b[0]=0,b[1]=0,b)},j.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]},j.multiply=function(a,b,c){return c||(c=a),c[0]=a[0]*b[0],c[1]=a[1]*b[1],c},j.unproject=function(a){return e.create([a[0],a[1],1])},j.length=function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},j.perspectiveProject=function(a){var b=j.create(a);return j.scale(b,1/a[2])},e.project=function(a){return j.scale(j.create(a),1/a[2])};var k={};k.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c[4]=a[4]*b,c[5]=a[5]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a[4]*=b,a[5]*=b,a)},k.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2],c[3]=a[3]-b[3],c[4]=a[4]-b[4],c[5]=a[5]-b[5],c):(a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a[3]-=b[3],a[4]-=b[4],a[5]-=b[5],a)},k.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]+a[4]*b[4]+a[5]*b[5]};var l={};return l.xVec6=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],i=b[5];return c[0]=a[0]*d+a[1]*e+a[2]*f+a[3]*g+a[4]*h+a[5]*i,c[1]=a[6]*d+a[7]*e+a[8]*f+a[9]*g+a[10]*h+a[11]*i,c[2]=a[12]*d+a[13]*e+a[14]*f+a[15]*g+a[16]*h+a[17]*i,c[3]=a[18]*d+a[19]*e+a[20]*f+a[21]*g+a[22]*h+a[23]*i,c[4]=a[24]*d+a[25]*e+a[26]*f+a[27]*g+a[28]*h+a[29]*i,c[5]=a[30]*d+a[31]*e+a[32]*f+a[33]*g+a[34]*h+a[35]*i,c},f.xVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[1]*e+a[2]*f,c[1]=a[3]*d+a[4]*e+a[5]*f,c[2]=a[6]*d+a[7]*e+a[8]*f,c},d("glMatrixAddon",["glMatrix"],function(a){return function(){var b;return b||a.glMatrixAddon}}(this)),d("array_helper",[],function(){"use strict";return{init:function(a,b){for(var c=a.length;c--;)a[c]=b},shuffle:function(a){var b,c,d=a.length-1;for(d;d>=0;d--)b=Math.floor(Math.random()*d),c=a[d],a[d]=a[b],a[b]=c;return a},toPointList:function(a){var b,c,d=[],e=[];for(b=0;b=b&&e.push(a[d]);return e},maxIndex:function(a){var b,c=0;for(b=0;ba[c]&&(c=b);return c},max:function(a){var b,c=0;for(b=0;bc&&(c=a[b]);return c},sum:function(a){for(var b=a.length,c=0;b--;)c+=a[b];return c}}}),d("cv_utils",["cluster","glMatrixAddon","array_helper"],function(a,b,c){"use strict";var d={};return d.imageRef=function(a,b){var c={x:a,y:b,toVec2:function(){return j.create([this.x,this.y])},toVec3:function(){return e.create([this.x,this.y,1])},round:function(){return this.x=Math.floor(this.x>0?this.x+.5:this.x-.5),this.y=Math.floor(this.y>0?this.y+.5:this.y-.5),this}};return c},d.computeIntegralImage2=function(a,b){var c,d,e=a.data,f=a.size.x,g=a.size.y,h=b.data,i=0,j=0,k=0,l=0,m=0;for(k=f,i=0,d=1;g>d;d++)i+=e[j],h[k]+=i,j+=f,k+=f;for(j=0,k=1,i=0,c=1;f>c;c++)i+=e[j],h[k]+=i,j++,k++;for(d=1;g>d;d++)for(j=d*f+1,k=(d-1)*f+1,l=d*f,m=(d-1)*f,c=1;f>c;c++)h[j]+=e[j]+h[k]+h[l]-h[m],j++,k++,l++,m++},d.computeIntegralImage=function(a,b){for(var c=a.data,d=a.size.x,e=a.size.y,f=b.data,g=0,h=0;d>h;h++)g+=c[h],f[h]=g;for(var i=1;e>i;i++){g=0;for(var j=0;d>j;j++)g+=c[i*d+j],f[i*d+j]=g+f[(i-1)*d+j]}},d.thresholdImage=function(a,b,c){c||(c=a);for(var d=a.data,e=d.length,f=c.data;e--;)f[e]=d[e]>e]++;return g},d.sharpenLine=function(a){var b,c,d=a.length,e=a[0],f=a[1];for(b=1;d-1>b;b++)c=a[b+1],a[b-1]=2*f-e-c&255,e=f,f=c;return a},d.determineOtsuThreshold=function(a,b){function e(a,b){var c,d=0;for(c=a;b>=c;c++)d+=h[c];return d}function f(a,b){var c,d=0;for(c=a;b>=c;c++)d+=c*h[c];return d}function g(){var g,i,j,k,l,m,n,o=[0],p=(1<k;k++)g=e(0,k),i=e(k+1,p),j=g*i,0===j&&(j=1),l=f(0,k)*i,m=f(k+1,p)*g,n=l-m,o[k]=n*n/j;return c.maxIndex(o)}b||(b=8);var h,i,j=8-b;return i=g(),i<=e;e++)for(f=0;n>f;f++)m[e*n+f]=0,m[(o-1-e)*n+f]=0;for(e=r;o-r>e;e++)for(f=0;r>=f;f++)m[e*n+f]=0,m[e*n+(n-1-f)]=0;for(e=r+1;o-r-1>e;e++)for(f=r+1;n-r>f;f++)g=p[(e-r-1)*n+(f-r-1)],h=p[(e-r-1)*n+(f+r)],i=p[(e+r)*n+(f-r-1)],j=p[(e+r)*n+(f+r)],q=j-i-h+g,k=q/s,m[e*n+f]=l[e*n+f]>k+5?0:1},d.cluster=function(b,c,d){function e(a){var b=!1;for(g=0;gb.x-j&&a.xb.y-k&&a.yd;d++){for(h=Math.floor(Math.random()*a.length),f=[],i=h,f.push(a[i]);null!==(i=c(i,!0));)f.push(a[i]);if(h>0)for(i=h;null!==(i=c(i,!1));)f.push(a[i]);f.length>g.length&&(g=f)}return g}},d.DILATE=1,d.ERODE=2,d.dilate=function(a,b){var c,d,e,f,g,h,i,j=a.data,k=b.data,l=a.size.y,m=a.size.x;for(c=1;l-1>c;c++)for(d=1;m-1>d;d++)f=c-1,g=c+1,h=d-1,i=d+1,e=j[f*m+h]+j[f*m+i]+j[c*m+d]+j[g*m+h]+j[g*m+i],k[c*m+d]=e>0?1:0},d.erode=function(a,b){var c,d,e,f,g,h,i,j=a.data,k=b.data,l=a.size.y,m=a.size.x;for(c=1;l-1>c;c++)for(d=1;m-1>d;d++)f=c-1,g=c+1,h=d-1,i=d+1,e=j[f*m+h]+j[f*m+i]+j[c*m+d]+j[g*m+h]+j[g*m+i],k[c*m+d]=5===e?1:0},d.subtract=function(a,b,c){c||(c=a);for(var d=a.data.length,e=a.data,f=b.data,g=c.data;d--;)g[d]=e[d]-f[d]},d.bitwiseOr=function(a,b,c){c||(c=a);for(var d=a.data.length,e=a.data,f=b.data,g=c.data;d--;)g[d]=e[d]||f[d]},d.countNonZero=function(a){for(var b=a.data.length,c=a.data,d=0;b--;)d+=c[b];return d},d.topGeneric=function(a,b,c){var d,e,f,g,h=0,i=0,j=[];for(d=0;b>d;d++)j[d]={score:0,item:null};for(d=0;di)for(f=j[h],f.score=e,f.item=a[d],i=Number.MAX_VALUE,g=0;b>g;g++)j[g].scoref;){for(d=0;h>d;d++)c[i]=Math.floor((.299*a[4*e+0]+.587*a[4*e+1]+.114*a[4*e+2]+(.299*a[4*(e+1)+0]+.587*a[4*(e+1)+1]+.114*a[4*(e+1)+2])+(.299*a[4*f+0]+.587*a[4*f+1]+.114*a[4*f+2])+(.299*a[4*(f+1)+0]+.587*a[4*(f+1)+1]+.114*a[4*(f+1)+2]))/4),i++,e+=2,f+=2;e+=j,f+=j}},d.computeGray=function(a,b,c){var d,e=a.length/4|0,f=c&&c.singleChannel===!0;if(f)for(d=0;e>d;d++)b[d]=a[4*d+0];else for(d=0;e>d;d++)b[d]=Math.floor(.299*a[4*d+0]+.587*a[4*d+1]+.114*a[4*d+2])},d.loadImageArray=function(a,b,c){c||(c=document.createElement("canvas"));var e=new Image;e.callback=b,e.onload=function(){c.width=this.width,c.height=this.height;var a=c.getContext("2d");a.drawImage(this,0,0);var b=new Uint8Array(this.width*this.height);a.drawImage(this,0,0);var e=a.getImageData(0,0,this.width,this.height).data;d.computeGray(e,b),this.callback(b,{x:this.width,y:this.height},this)},e.src=a},d.halfSample=function(a,b){for(var c=a.data,d=a.size.x,e=b.data,f=0,g=d,h=c.length,i=d/2,j=0;h>g;){for(var k=0;i>k;k++)e[j]=Math.floor((c[f]+c[f+1]+c[g]+c[g+1])/4),j++,f+=2,g+=2;f+=d,g+=d}},d.hsv2rgb=function(a,b){var c=a[0],d=a[1],e=a[2],f=e*d,g=f*(1-Math.abs(c/60%2-1)),h=e-f,i=0,j=0,k=0;return b=b||[0,0,0],60>c?(i=f,j=g):120>c?(i=g,j=f):180>c?(j=f,k=g):240>c?(j=g,k=f):300>c?(i=g,k=f):360>c&&(i=f,k=g),b[0]=255*(i+h)|0,b[1]=255*(j+h)|0,b[2]=255*(k+h)|0,b},d._computeDivisors=function(a){var b,c=[],d=[];for(b=1;bb[d]?d++:c++;return e},d.calculatePatchSize=function(a,b){function c(a){for(var b=0,c=a[Math.floor(a.length/2)];b0&&(c=Math.abs(a[b]-m)>Math.abs(a[b-1]-m)?a[b-1]:a[b]),m/ci[k-1]/i[k]?{x:c,y:c}:null}var d,e=this._computeDivisors(b.x),f=this._computeDivisors(b.y),g=Math.max(b.x,b.y),h=this._computeIntersection(e,f),i=[8,10,15,20,32,60,80],j={"x-small":5,small:4,medium:3,large:2,"x-large":1},k=j[a]||j.medium,l=i[k],m=Math.floor(g/l);return d=c(h),d||(d=c(this._computeDivisors(g)),d||(d=c(this._computeDivisors(m*l)))),d},d._parseCSSDimensionValues=function(a){var b={value:parseFloat(a),unit:a.indexOf("%")===a.length-1?"%":"%"};return b},d._dimensionsConverters={top:function(a,b){return"%"===a.unit?Math.floor(b.height*(a.value/100)):void 0},right:function(a,b){return"%"===a.unit?Math.floor(b.width-b.width*(a.value/100)):void 0},bottom:function(a,b){return"%"===a.unit?Math.floor(b.height-b.height*(a.value/100)):void 0},left:function(a,b){return"%"===a.unit?Math.floor(b.width*(a.value/100)):void 0}},d.computeImageArea=function(a,b,c){var e={width:a,height:b},f=Object.keys(c).reduce(function(a,b){var f=c[b],g=d._parseCSSDimensionValues(f),h=d._dimensionsConverters[b](g,e);return a[b]=h,a},{});return{sx:f.left,sy:f.top,sw:f.right-f.left,sh:f.bottom-f.top}},d}),d("image_wrapper",["subImage","cv_utils","array_helper"],function(a,b,c){"use strict";function d(a,b,d,e){b?this.data=b:d?(this.data=new d(a.x*a.y),d===Array&&e&&c.init(this.data,0)):(this.data=new Uint8Array(a.x*a.y),Uint8Array===Array&&e&&c.init(this.data,0)),this.size=a}return d.prototype.inImageWithBorder=function(a,b){return a.x>=b&&a.y>=b&&a.x=0&&u>=0&&n-1>v&&o-1>w){for(g=s,h=0;m>h;++h,j.add(g,y))for(k=0;l>k;++k,j.add(g,p))b.set(k,h,x(a,g[0],g[1]));return 0}var z=n-1,A=o-1,B=0;for(g=s,h=0;m>h;++h,j.add(g,y))for(k=0;l>k;++k,j.add(g,p))0<=g[0]&&0<=g[1]&&g[0]c;c++)for(d=0;e>d;d++)a.data[d*f+c]=this.data[(b.y+d)*this.size.x+b.x+c]},d.prototype.copyTo=function(a){for(var b=this.data.length,c=this.data,d=a.data;b--;)d[b]=c[b]},d.prototype.get=function(a,b){return this.data[b*this.size.x+a]},d.prototype.getSafe=function(a,b){var c;if(!this.indexMapping){for(this.indexMapping={x:[],y:[]},c=0;ca;a++)d[a]=d[(c-1)*b+a]=0;for(a=1;c-1>a;a++)d[a*b]=d[a*b+(b-1)]=0},d.prototype.invert=function(){for(var a=this.data,b=a.length;b--;)a[b]=a[b]?0:1},d.prototype.convolve=function(a){var b,c,d,e,f=a.length/2|0,g=0;for(c=0;c=e;e++)for(d=-f;f>=d;d++)g+=a[e+f][d+f]*this.getSafe(b+d,c+e);this.data[c*this.size.x+b]=g}},d.prototype.moments=function(a){var b,c,d,e,f,g,h,i,k,l,m,n,o=this.data,p=this.size.y,q=this.size.x,r=[],s=[],t=Math.PI,u=t/4;if(0>=a)return s;for(f=0;a>f;f++)r[f]={m00:0,m01:0,m10:0,m11:0,m02:0,m20:0,theta:0,rad:0};for(c=0;p>c;c++)for(e=c*c,b=0;q>b;b++)d=o[c*q+b],d>0&&(g=r[d-1],g.m00+=1,g.m01+=c,g.m10+=b,g.m11+=b*c,g.m02+=e,g.m20+=b*b);for(f=0;a>f;f++)g=r[f],isNaN(g.m00)||0===g.m00||(l=g.m10/g.m00,m=g.m01/g.m00,h=g.m11/g.m00-l*m,i=g.m02/g.m00-m*m,k=g.m20/g.m00-l*l,n=(i-k)/(2*h),n=.5*Math.atan(n)+(h>=0?u:-u)+t,g.theta=(180*n/t+90)%180-90,g.theta<0&&(g.theta+=180),g.rad=n>t?n-t:n,g.vec=j.create([Math.cos(n),Math.sin(n)]),s.push(g));return s},d.prototype.show=function(a,b){var c,d,e,f,g,h,i;for(b||(b=1),c=a.getContext("2d"),a.width=this.size.x,a.height=this.size.y,d=c.getImageData(0,0,a.width,a.height),e=d.data,f=0,i=0;ic||c>360)&&(c=360);for(var e=[0,1,1],f=[0,0,0],g=[255,255,255],h=[0,0,0],i=[],j=a.getContext("2d"),k=j.getImageData(d.x,d.y,this.size.x,this.size.y),l=k.data,m=this.data.length;m--;)e[0]=this.data[m]*c,i=e[0]<=0?g:e[0]>=360?h:b.hsv2rgb(e,f),l[4*m+0]=i[0],l[4*m+1]=i[1],l[4*m+2]=i[2],l[4*m+3]=255;j.putImageData(k,d.x,d.y)},d}),d("tracer",[],function(){"use strict";var a={searchDirections:[[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1],[-1,0],[-1,1]],create:function(a,b){function c(a,b,c,d){var e,k,l;for(e=0;7>e;e++){if(k=a.cy+i[a.dir][0],l=a.cx+i[a.dir][1],f=k*j+l,g[f]===b&&(0===h[f]||h[f]===c))return h[f]=c,a.cy=k,a.cx=l,!0;0===h[f]&&(h[f]=d),a.dir=(a.dir+1)%8}return!1}function d(a,b,c){return{dir:c,x:a,y:b,next:null,prev:null}}function e(a,b,e,f,g){var h,i,j,k=null,l={cx:b,cy:a,dir:0};if(c(l,f,e,g)){k=d(b,a,l.dir),h=k,j=l.dir,i=d(l.cx,l.cy,0),i.prev=h,h.next=i,i.next=null,h=i;do l.dir=(l.dir+6)%8,c(l,f,e,g),j!=l.dir?(h.dir=l.dir,i=d(l.cx,l.cy,0),i.prev=h,h.next=i,i.next=null,h=i):(h.dir=j,h.x=l.cx,h.y=l.cy),j=l.dir;while(l.cx!=b||l.cy!=a);k.prev=h.prev,h.prev.next=k}return k}var f,g=a.data,h=b.data,i=this.searchDirections,j=a.size.x;return{trace:function(a,b,d,e){return c(a,b,d,e)},contourTracing:function(a,b,c,d,f){return e(a,b,c,d,f)}}}};return a}),d("rasterizer",["tracer"],function(a){"use strict";var b={createContour2D:function(){return{dir:null,index:null,firstVertex:null,insideContours:null,nextpeer:null,prevpeer:null}},CONTOUR_DIR:{CW_DIR:0,CCW_DIR:1,UNKNOWN_DIR:2},DIR:{OUTSIDE_EDGE:-32767,INSIDE_EDGE:-32766},create:function(c,d){var e=c.data,f=d.data,g=c.size.x,h=c.size.y,i=a.create(c,d);return{rasterize:function(a){var c,d,j,k,l,m,n,o,p,q,r,s,t=[],u=0;for(s=0;400>s;s++)t[s]=0;for(t[0]=e[0],p=null,m=1;h-1>m;m++)for(k=0,d=t[0],l=1;g-1>l;l++)if(r=m*g+l,0===f[r])if(c=e[r],c!==d){if(0===k)j=u+1,t[j]=c,d=c,n=i.contourTracing(m,l,j,c,b.DIR.OUTSIDE_EDGE),null!==n&&(u++,k=j,o=b.createContour2D(),o.dir=b.CONTOUR_DIR.CW_DIR,o.index=k,o.firstVertex=n,o.nextpeer=p,o.insideContours=null,null!==p&&(p.prevpeer=o),p=o);else if(n=i.contourTracing(m,l,b.DIR.INSIDE_EDGE,c,k),null!==n){for(o=b.createContour2D(),o.firstVertex=n,o.insideContours=null,o.dir=0===a?b.CONTOUR_DIR.CCW_DIR:b.CONTOUR_DIR.CW_DIR,o.index=a,q=p;null!==q&&q.index!==k;)q=q.nextpeer;null!==q&&(o.nextpeer=q.insideContours,null!==q.insideContours&&(q.insideContours.prevpeer=o),q.insideContours=o)}}else f[r]=k;else f[r]===b.DIR.OUTSIDE_EDGE||f[r]===b.DIR.INSIDE_EDGE?(k=0,d=f[r]===b.DIR.INSIDE_EDGE?e[r]:t[0]):(k=f[r],d=t[k]);for(q=p;null!==q;)q.index=a,q=q.nextpeer;return{cc:p,count:u}},debug:{drawContour:function(a,c){var d,e,f,g=a.getContext("2d"),h=c;for(g.strokeStyle="red",g.fillStyle="red",g.lineWidth=1,d=null!==h?h.insideContours:null;null!==h;){switch(null!==d?(e=d,d=d.nextpeer):(e=h,h=h.nextpeer,d=null!==h?h.insideContours:null),e.dir){case b.CONTOUR_DIR.CW_DIR:g.strokeStyle="red";break;case b.CONTOUR_DIR.CCW_DIR:g.strokeStyle="blue";break;case b.CONTOUR_DIR.UNKNOWN_DIR:g.strokeStyle="green"}f=e.firstVertex,g.beginPath(),g.moveTo(f.x,f.y);do f=f.next,g.lineTo(f.x,f.y);while(f!==e.firstVertex);g.stroke()}}}}}};return b}),d("skeletonizer",[],function(){"use strict";function a(stdlib, foreign, buffer) {"use asm";var images=new stdlib.Uint8Array(buffer),size=foreign.size|0,imul=stdlib.Math.imul;function erode(inImagePtr, outImagePtr) {inImagePtr=inImagePtr|0;outImagePtr=outImagePtr|0;var v=0,u=0,sum=0,yStart1=0,yStart2=0,xStart1=0,xStart2=0,offset=0;for ( v=1; (v|0)<((size - 1)|0); v=(v+1)|0) {offset=(offset+size)|0;for ( u=1; (u|0)<((size - 1)|0); u=(u+1)|0) {yStart1=(offset - size)|0;yStart2=(offset+size)|0;xStart1=(u - 1)|0;xStart2=(u+1)|0;sum=((images[(inImagePtr+yStart1+xStart1)|0]|0)+(images[(inImagePtr+yStart1+xStart2)|0]|0)+(images[(inImagePtr+offset+u)|0]|0)+(images[(inImagePtr+yStart2+xStart1)|0]|0)+(images[(inImagePtr+yStart2+xStart2)|0]|0))|0;if ((sum|0) == (5|0)) {images[(outImagePtr+offset+u)|0]=1;} else {images[(outImagePtr+offset+u)|0]=0;}}}return;}function subtract(aImagePtr, bImagePtr, outImagePtr) {aImagePtr=aImagePtr|0;bImagePtr=bImagePtr|0;outImagePtr=outImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(outImagePtr+length)|0]=((images[(aImagePtr+length)|0]|0) - (images[(bImagePtr+length)|0]|0))|0;}}function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) {aImagePtr=aImagePtr|0;bImagePtr=bImagePtr|0;outImagePtr=outImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(outImagePtr+length)|0]=((images[(aImagePtr+length)|0]|0)|(images[(bImagePtr+length)|0]|0))|0;}}function countNonZero(imagePtr) {imagePtr=imagePtr|0;var sum=0,length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;sum=((sum|0)+(images[(imagePtr+length)|0]|0))|0;}return (sum|0);}function init(imagePtr, value) {imagePtr=imagePtr|0;value=value|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(imagePtr+length)|0]=value;}}function dilate(inImagePtr, outImagePtr) {inImagePtr=inImagePtr|0;outImagePtr=outImagePtr|0;var v=0,u=0,sum=0,yStart1=0,yStart2=0,xStart1=0,xStart2=0,offset=0;for ( v=1; (v|0)<((size - 1)|0); v=(v+1)|0) {offset=(offset+size)|0;for ( u=1; (u|0)<((size - 1)|0); u=(u+1)|0) {yStart1=(offset - size)|0;yStart2=(offset+size)|0;xStart1=(u - 1)|0;xStart2=(u+1)|0;sum=((images[(inImagePtr+yStart1+xStart1)|0]|0)+(images[(inImagePtr+yStart1+xStart2)|0]|0)+(images[(inImagePtr+offset+u)|0]|0)+(images[(inImagePtr+yStart2+xStart1)|0]|0)+(images[(inImagePtr+yStart2+xStart2)|0]|0))|0;if ((sum|0)>(0|0)) {images[(outImagePtr+offset+u)|0]=1;} else {images[(outImagePtr+offset+u)|0]=0;}}}return;}function memcpy(srcImagePtr, dstImagePtr) {srcImagePtr=srcImagePtr|0;dstImagePtr=dstImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(dstImagePtr+length)|0]=(images[(srcImagePtr+length)|0]|0);}}function zeroBorder(imagePtr) {imagePtr=imagePtr|0;var x=0,y=0;for ( x=0; (x|0)<((size - 1)|0); x=(x+1)|0) {images[(imagePtr+x)|0]=0;images[(imagePtr+y)|0]=0;y=((y+size) - 1)|0;images[(imagePtr+y)|0]=0;y=(y+1)|0;}for ( x=0; (x|0)<(size|0); x=(x+1)|0) {images[(imagePtr+y)|0]=0;y=(y+1)|0;}}function skeletonize() {var subImagePtr=0,erodedImagePtr=0,tempImagePtr=0,skelImagePtr=0,sum=0,done=0;erodedImagePtr=imul(size, size)|0;tempImagePtr=(erodedImagePtr+erodedImagePtr)|0;skelImagePtr=(tempImagePtr+erodedImagePtr)|0;init(skelImagePtr, 0);zeroBorder(subImagePtr);do {erode(subImagePtr, erodedImagePtr);dilate(erodedImagePtr, tempImagePtr);subtract(subImagePtr, tempImagePtr, tempImagePtr);bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);memcpy(erodedImagePtr, subImagePtr);sum=countNonZero(subImagePtr)|0;done=((sum|0) == 0|0);} while(!done);}return {skeletonize : skeletonize};}
-return a}),d("image_debug",[],function(){"use strict";return{drawRect:function(a,b,c,d){c.strokeStyle=d.color,c.fillStyle=d.color,c.lineWidth=1,c.beginPath(),c.strokeRect(a.x,a.y,b.x,b.y)},drawPath:function(a,b,c,d){c.strokeStyle=d.color,c.fillStyle=d.color,c.lineWidth=d.lineWidth,c.beginPath(),c.moveTo(a[0][b.x],a[0][b.y]);for(var e=1;eb&&(b+=180),b=(180-b)*Math.PI/180,f=i.create([Math.cos(b),-Math.sin(b),Math.sin(b),Math.cos(b)]),c=0;cd;d++)i.xVec2(f,e.box[d]);u.boxFromPatches.showTransformed&&g.drawPath(e.box,{x:0,y:1},G.ctx.binary,{color:"#99ff00",lineWidth:2})}for(c=0;cd;d++)e.box[d][0]n&&(n=e.box[d][0]),e.box[d][1]o&&(o=e.box[d][1]);for(h=[[l,m],[n,m],[n,o],[l,o]],u.boxFromPatches.showTransformedBox&&g.drawPath(h,{x:0,y:1},G.ctx.binary,{color:"#ff0000",lineWidth:2}),k=u.halfSample?2:1,f=i.inverse(f),d=0;4>d;d++)i.xVec2(f,h[d]);for(u.boxFromPatches.showBB&&g.drawPath(h,{x:0,y:1},G.ctx.binary,{color:"#ff0000",lineWidth:2}),d=0;4>d;d++)j.scale(h[d],k);return h}function m(){b.otsuThreshold(v,C),C.zeroBorder(),u.showCanvas&&C.show(G.dom.binary,255)}function n(){var a,b,d,e,h,i,j,k,l=[];for(a=0;ab;b++)d.push(0);for(c=A.data.length;c--;)A.data[c]>0&&d[A.data[c]-1]++;return d=d.map(function(a,b){return{val:a,label:b+1}}),d.sort(function(a,b){return b.val-a.val}),e=d.filter(function(a){return a.val>=5})}function p(a,c){var d,e,f,h,i,j=[],k=[],m=[0,1,1],n=[0,0,0];for(d=0;d=2){for(e=0;em&&k.push(a[e]);if(k.length>=2){for(i=k.length,g=q(k),f=0,e=0;e1&&g.length>=k.length/4*3&&g.length>a.length/4&&(f/=g.length,h={index:b[1]*H.x+b[0],pos:{x:c,y:d},box:[j.create([c,d]),j.create([c+x.size.x,d]),j.create([c+x.size.x,d+x.size.y]),j.create([c,d+x.size.y])],moments:g,rad:f,vec:j.create([Math.cos(f),Math.sin(f)])},l.push(h))}}return l}function t(a){function c(){var a;for(a=0;al&&e(h))):A.data[h]=Number.MAX_VALUE}var h,i,k=0,l=.95,m=0,n=[0,1,1],o=[0,0,0];for(f.init(z.data,0),f.init(A.data,0),f.init(B.data,null),h=0;h0&&A.data[h]<=k&&(i=B.data[h],n[0]=A.data[h]/(k+1)*360,b.hsv2rgb(n,o),g.drawRect(i.pos,x.size,G.ctx.binary,{color:"rgb("+o.join(",")+")",lineWidth:2}));return k}var u,v,w,x,y,z,A,B,C,D,E,F,G={ctx:{binary:null},dom:{binary:null}},H={x:0,y:0},I=this;return{init:function(a,b){u=b,E=a,h(),k()},locate:function(){var a,c,d;if(u.halfSample&&b.halfSample(E,v),m(),a=n(),a.lengthe?null:(c=o(e),0===c.length?null:d=p(c,e))},checkImageConstraints:function(a,c){var d,e,f,g=a.getWidth(),h=a.getHeight(),i=c.halfSample?.5:1;if(a.getConfig().area&&(f=b.computeImageArea(g,h,a.getConfig().area),a.setTopRight({x:f.sx,y:f.sy}),a.setCanvasSize({x:g,y:h}),g=f.sw,h=f.sh),e={x:Math.floor(g*i),y:Math.floor(h*i)},d=b.calculatePatchSize(c.patchSize,e),console.log("Patch-Size: "+JSON.stringify(d)),a.setWidth(Math.floor(Math.floor(e.x/d.x)*(1/i)*d.x)),a.setHeight(Math.floor(Math.floor(e.y/d.y)*(1/i)*d.y)),a.getWidth()%d.x===0&&a.getHeight()%d.y===0)return!0;throw new Error("Image dimensions do not comply with the current settings: Width ("+g+" )and height ("+h+") must a multiple of "+d.x)}}}),d("bresenham",["cv_utils","image_wrapper"],function(a,b){"use strict";var c={},d={DIR:{UP:1,DOWN:-1}};return c.getBarcodeLine=function(a,b,c){function d(a,b){l=s[b*t+a],u+=l,v=v>l?l:v,w=l>w?l:w,r.push(l)}var e,f,g,h,i,j,k,l,m=0|b.x,n=0|b.y,o=0|c.x,p=0|c.y,q=Math.abs(p-n)>Math.abs(o-m),r=[],s=a.data,t=a.size.x,u=0,v=255,w=0;for(q&&(j=m,m=n,n=j,j=o,o=p,p=j),m>o&&(j=m,m=o,o=j,j=n,n=p,p=j),e=o-m,f=Math.abs(p-n),g=e/2|0,i=n,h=p>n?1:-1,k=m;o>k;k++)q?d(i,k):d(k,i),g-=f,0>g&&(i+=h,g+=e);return{line:r,min:v,max:w}},c.toOtsuBinaryLine=function(c){var d=c.line,e=new b({x:d.length-1,y:1},d),f=a.determineOtsuThreshold(e,5);return d=a.sharpenLine(d),a.thresholdImage(e,f),{line:d,threshold:f}},c.toBinaryLine=function(a){var b,c,e,f,g,h,i=a.min,j=a.max,k=a.line,l=i+(j-i)/2,m=[],n=(j-i)/12,o=-n;for(e=k[0]>l?d.DIR.UP:d.DIR.DOWN,m.push({pos:0,val:k[0]}),g=0;gb+c&&k[g+1]<1.5*l?d.DIR.DOWN:b+c>n&&k[g+1]>.5*l?d.DIR.UP:e,e!==f&&(m.push({pos:g,val:k[g]}),e=f);for(m.push({pos:k.length,val:k[k.length-1]}),h=m[0].pos;hl?0:1;for(g=1;gm[g].val?m[g].val+(m[g+1].val-m[g].val)/3*2|0:m[g+1].val+(m[g].val-m[g+1].val)/3|0,h=m[g].pos;hn?0:1;return{line:k,threshold:n}},c.debug={printFrequency:function(a,b){var c,d=b.getContext("2d");for(b.width=a.length,b.height=256,d.beginPath(),d.strokeStyle="blue",c=0;cd;d++)if(e._row[d]^h)c[i]++;else{if(i++,i===f)break;c[i]=1,h=!h}return c},c.prototype._decode=function(){var a,c,d,e,f=this,g=[0,0,0,0,0,0,0,0,0],h=[],i=f._findStart();if(!i)return null;e=f._nextSet(f._row,i.end);do{if(g=f._toCounters(e,g),d=f._toPattern(g),0>d)return null;if(a=f._patternToChar(d),0>a)return null;h.push(a),c=e,e+=b.sum(g),e=f._nextSet(f._row,e)}while("*"!==a);return h.pop(),h.length&&f._verifyTrailingWhitespace(c,e,g)?{code:h.join(""),start:i.start,end:e,startInfo:i,decodedCodes:h}:null},c.prototype._verifyTrailingWhitespace=function(a,c,d){var e,f=b.sum(d);return e=c-a-f,3*e>=f?!0:!1},c.prototype._patternToChar=function(a){var b,c=this;for(b=0;bb&&(d=a[c]);return d},c.prototype._toPattern=function(a){for(var b,c,d=a.length,e=0,f=d,g=0,h=this;f>3;){for(e=h._findNextWidth(a,e),f=0,b=0,c=0;d>c;c++)a[c]>e&&(b|=1<c&&f>0;c++)if(a[c]>e&&(f--,2*a[c]>=g))return-1;return b}}return-1},c.prototype._findStart=function(){var a,b,c,d=this,e=d._nextSet(d._row),f=e,g=[0,0,0,0,0,0,0,0,0],h=0,i=!1;for(a=e;ab;b++)g[b]=g[b+2];g[7]=0,g[8]=0,h--}else h++;g[h]=1,i=!i}return null},c}),d("code_39_vin_reader",["./code_39_reader"],function(a){"use strict";function b(){a.call(this)}var c={IOQ:/[IOQ]/g,AZ09:/[A-Z0-9]{17}/};return b.prototype=Object.create(a.prototype),b.prototype.constructor=b,b.prototype._decode=function(){var b=a.prototype._decode.apply(this);if(!b)return null;var d=b.code;if(d)return d=d.replace(c.IOQ,""),d.match(c.AZ09)?this._checkChecksum(d)?(b.code=d,b):null:(console.log("Failed AZ09 pattern code:",d),null)},b.prototype._checkChecksum=function(a){return!!a},b}),d("codabar_reader",["./barcode_reader"],function(a){"use strict";function b(){a.call(this),this._counters=[]}var c={ALPHABETH_STRING:{value:"0123456789-$:/.+ABCD"},ALPHABET:{value:[48,49,50,51,52,53,54,55,56,57,45,36,58,47,46,43,65,66,67,68]},CHARACTER_ENCODINGS:{value:[3,6,9,96,18,66,33,36,48,72,12,24,69,81,84,21,26,41,11,14]},START_END:{value:[26,41,11,14]},MIN_ENCODED_CHARS:{value:4},MAX_ACCEPTABLE:{value:2},PADDING:{value:1.5},FORMAT:{value:"codabar",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decode=function(){var a,b,c,d,e,f=this,g=[];if(f._fillCounters(),a=f._findStart(),!a)return null;d=a.startCounter;do{if(c=f._toPattern(d),0>c)return null;if(b=f._patternToChar(c),0>b)return null;if(g.push(b),d+=8,g.length>1&&f._isStartEnd(c))break}while(df._counters.length?f._counters.length:d,e=a.start+f._sumCounters(a.startCounter,d-8),{code:g.join(""),start:a.start,end:e,startInfo:a,decodedCodes:g}):null},b.prototype._verifyWhitespace=function(a,b){return(0>=a-1||this._counters[a-1]>=this._calculatePatternLength(a)/2)&&(b+8>=this._counters.length||this._counters[b+7]>=this._calculatePatternLength(b)/2)?!0:!1},b.prototype._calculatePatternLength=function(a){var b,c=0;for(b=a;a+7>b;b++)c+=this._counters[b];return c},b.prototype._thresholdResultPattern=function(a,b){var c,d,e,f,g,h=this,i={space:{narrow:{size:0,counts:0,min:0,max:Number.MAX_VALUE},wide:{size:0,counts:0,min:0,max:Number.MAX_VALUE}},bar:{narrow:{size:0,counts:0,min:0,max:Number.MAX_VALUE},wide:{size:0,counts:0,min:0,max:Number.MAX_VALUE}}},j=b;for(e=0;e=0;f--)c=2===(1&f)?i.bar:i.space,d=1===(1&g)?c.wide:c.narrow,d.size+=h._counters[j+f],d.counts++,g>>=1;j+=8}return["space","bar"].forEach(function(a){var b=i[a];b.wide.min=Math.floor((b.narrow.size/b.narrow.counts+b.wide.size/b.wide.counts)/2),b.narrow.max=Math.ceil(b.wide.min),b.wide.max=Math.ceil((b.wide.size*h.MAX_ACCEPTABLE+h.PADDING)/b.wide.counts)}),i},b.prototype._charToPattern=function(a){var b,c=this,d=a.charCodeAt(0);for(b=0;b=0;d--){if(e=0===(1&d)?j.bar:j.space,f=1===(1&h)?e.wide:e.narrow,g=i._counters[k+d],gf.max)return!1;h>>=1}k+=8}return!0},b.prototype._fillCounters=function(){var a,b=this,c=0,d=!0,e=b._nextUnset(b._row);for(b._counters.length=0,b._counters[c]=0,a=e;ac;c+=2)d=this._counters[c],d>f&&(f=d),e>d&&(e=d);return(e+f)/2|0},b.prototype._toPattern=function(a){var b,c,d,e,f=7,g=a+f,h=1<this._counters.length)return-1;for(b=this._computeAlternatingThreshold(a,g),c=this._computeAlternatingThreshold(a+1,g),d=0;f>d;d++)e=0===(1&d)?b:c,this._counters[a+d]>e&&(i|=h),h>>=1;return i},b.prototype._isStartEnd=function(a){var b;for(b=0;bc;c++)d+=this._counters[c];return d},b.prototype._findStart=function(){var a,b,c,d=this,e=d._nextUnset(d._row);for(a=1;ad;d++){if(a=e._decodeCode(a.end,e.CODE_G_START),!a)return null;b.push(a.code),c.push(a)}if(a=e._findPattern(e.MIDDLE_PATTERN,a.end,!0,!1),null===a)return null;for(c.push(a),d=0;4>d;d++){if(a=e._decodeCode(a.end,e.CODE_G_START),!a)return null;c.push(a),b.push(a.code)}return a},b}),d("upc_e_reader",["./ean_reader"],function(a){"use strict";function b(){a.call(this)}var c={CODE_FREQUENCY:{value:[[56,52,50,49,44,38,35,42,41,37],[7,11,13,14,19,25,28,21,22,26]]},STOP_PATTERN:{value:[1/6*7,1/6*7,1/6*7,1/6*7,1/6*7,1/6*7]},FORMAT:{value:"upc_e",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decodePayload=function(a,b,c){var d,e=this,f=0;for(d=0;6>d;d++){if(a=e._decodeCode(a.end),!a)return null;a.code>=e.CODE_G_START&&(a.code=a.code-e.CODE_G_START,f|=1<<5-d),b.push(a.code),c.push(a)}return e._determineParity(f,b)?a:null},b.prototype._determineParity=function(a,b){var c,d,e=this;for(d=0;d=c?b.concat(a.slice(1,3)).concat([c,0,0,0,0]).concat(a.slice(3,6)):3===c?b.concat(a.slice(1,4)).concat([0,0,0,0,0]).concat(a.slice(4,6)):4===c?b.concat(a.slice(1,5)).concat([0,0,0,0,0,a[5]]):b.concat(a.slice(1,6)).concat([0,0,0,0,c]),b.push(a[a.length-1]),b},b.prototype._checksum=function(b){return a.prototype._checksum.call(this,this._convertToUPCA(b))},b.prototype._findEnd=function(b,c){return c=!0,a.prototype._findEnd.call(this,b,c)},b.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start)/2,b1&&(!d.inImageWithBorder(a[0],0)||!d.inImageWithBorder(a[1],0));)c-=Math.ceil(c/2),e(-c);return a}function i(a){return[{x:(a[1][0]-a[0][0])/2+a[0][0],y:(a[1][1]-a[0][1])/2+a[0][1]},{x:(a[3][0]-a[2][0])/2+a[2][0],y:(a[3][1]-a[2][1])/2+a[2][1]}]}function j(e){var f,g=null,h=a.getBarcodeLine(d,e[0],e[1]);for(c.showFrequency&&(b.drawPath(e,{x:"x",y:"y"},o.ctx.overlay,{color:"red",lineWidth:3}),a.debug.printFrequency(h.line,o.dom.frequency)),a.toBinaryLine(h),c.showPattern&&a.debug.printPattern(h.line,o.dom.pattern),f=0;fd&&null===i;d++)e=g/h*d*(d%2===0?-1:1),f={y:e*k,x:e*l},b[0].y+=f.x,b[0].x-=f.y,b[1].y+=f.x,b[1].x-=f.y,i=j(b);return i}function m(a){return Math.sqrt(Math.pow(Math.abs(a[1].y-a[0].y),2)+Math.pow(Math.abs(a[1].x-a[0].x),2))}function n(a){var d,e,f,g,k=o.ctx.overlay;return c.drawBoundingBox&&k&&b.drawPath(a,{x:0,y:1},k,{color:"blue",lineWidth:2}),d=i(a),g=m(d),e=Math.atan2(d[1].y-d[0].y,d[1].x-d[0].x),d=h(d,e,Math.floor(.1*g)),null===d?null:(f=j(d),null===f&&(f=l(a,d,e)),null===f?null:(f&&c.drawScanline&&k&&b.drawPath(d,{x:"x",y:"y"},k,{color:"red",lineWidth:3}),{codeResult:f.codeResult,line:d,angle:e,pattern:f.barcodeLine.line,threshold:f.barcodeLine.threshold}))}var o={ctx:{frequency:null,pattern:null,overlay:null},dom:{frequency:null,pattern:null,overlay:null}},p=[];return e(),f(),g(),{decodeFromBoundingBox:function(a){return n(a)},decodeFromBoundingBoxes:function(a){var b,c;for(b=0;b0?a.videoWidth>0&&a.videoHeight>0?(console.log(a.videoWidth+"px x "+a.videoHeight+"px"),b()):window.setTimeout(c,500):b("Unable to play video stream. Is webcam working?"),d--}var d=10;c()}function d(a,d,e){b(a,function(a){d.src=a,h&&d.removeEventListener("loadeddata",h,!1),h=c.bind(null,d,e),d.addEventListener("loadeddata",h,!1),d.play()},function(a){e(a)})}function e(b,c){var d={audio:!1,video:!0},e=a.mergeObjects({width:640,height:480,minAspectRatio:0,maxAspectRatio:100,facing:"environment"},b);return"undefined"==typeof MediaStreamTrack||"undefined"==typeof MediaStreamTrack.getSources?(d.video={mediaSource:"camera",width:{min:e.width,max:e.width},height:{min:e.height,max:e.height},require:["width","height"]},c(d)):void MediaStreamTrack.getSources(function(a){for(var b,f=0;f!=a.length;++f){var g=a[f];"video"==g.kind&&g.facing==e.facing&&(b=g.id)}return d.video={mandatory:{minWidth:e.width,minHeight:e.height,minAspectRatio:e.minAspectRatio,maxAspectRatio:e.maxAspectRatio},optional:[{sourceId:b}]},c(d)})}function f(a,b,c){e(b,function(b){d(b,a,c)})}var g,h;return{request:function(a,b,c){f(a,b,c)},release:function(){var a=g&&g.getVideoTracks();a.length&&a[0].stop(),g=null}}}),d("result_collector",["image_debug"],function(a){"use strict";function b(a,b){return b?b.some(function(b){return Object.keys(b).every(function(c){return b[c]===a[c]})}):!1}function c(a,b){return"function"==typeof b?b(a):!0}return{create:function(d){function e(a){return i&&a&&!b(a,d.blacklist)&&c(a,d.filter)}var f=document.createElement("canvas"),g=f.getContext("2d"),h=[],i=d.capacity||20,j=d.capture===!0;return{addResult:function(b,c,d){var k={};e(d)&&(i--,k.codeResult=d,j&&(f.width=c.x,f.height=c.y,a.drawImage(b,c,g),k.frame=f.toDataURL()),h.push(k))},getResults:function(){return h}}}}}),d("quagga",["code_128_reader","ean_reader","input_stream","image_wrapper","barcode_locator","barcode_decoder","frame_grabber","html_utils","config","events","camera_access","image_debug","result_collector"],function(b,c,d,e,f,g,h,i,k,l,m,n,o){"use strict";function p(a){v(a),M=g.create(k.decoder,K)}function q(){if("undefined"!=typeof document)for(var a=[{node:document.querySelector("div[data-controls]"),prop:k.controls},{node:O.dom.overlay,prop:k.visual.show}],b=0;b0?C(function(){console.log("Workers created"),t(a)}):(p(),t(a))}function t(a){H.play(),a()}function u(){if("undefined"!=typeof document){var a=document.querySelector("#interactive.viewport");if(O.dom.image=document.querySelector("canvas.imgBuffer"),O.dom.image||(O.dom.image=document.createElement("canvas"),O.dom.image.className="imgBuffer",a&&"ImageStream"==k.inputStream.type&&a.appendChild(O.dom.image)),O.ctx.image=O.dom.image.getContext("2d"),O.dom.image.width=H.getCanvasSize().x,O.dom.image.height=H.getCanvasSize().y,O.dom.overlay=document.querySelector("canvas.drawingBuffer"),!O.dom.overlay){O.dom.overlay=document.createElement("canvas"),O.dom.overlay.className="drawingBuffer",a&&a.appendChild(O.dom.overlay);var b=document.createElement("br");b.setAttribute("clear","all"),a&&a.appendChild(b)}O.ctx.overlay=O.dom.overlay.getContext("2d"),O.dom.overlay.width=H.getCanvasSize().x,O.dom.overlay.height=H.getCanvasSize().y}}function v(a){K=a?a:new e({x:H.getWidth(),y:H.getHeight()}),console.log(K.size),L=[j.create([0,0]),j.create([0,K.size.y]),j.create([K.size.x,K.size.y]),j.create([K.size.x,0])],f.init(K,k.locator)}function w(){return k.locate?f.locate():[[j.create(L[0]),j.create(L[1]),j.create(L[2]),j.create(L[3])]]}function x(a){function b(a){for(var b=a.length;b--;)a[b][0]+=f,a[b][1]+=g}function c(a){a[0].x+=f,a[0].y+=g,a[1].x+=f,a[1].y+=g}var d,e=H.getTopRight(),f=e.x,g=e.y;if(a&&(0!==f||0!==g)&&(a.line&&2===a.line.length&&c(a.line),a.boxes&&a.boxes.length>0))for(d=0;d0){if(a=P.filter(function(a){return!a.busy})[0],!a)return;I.attachData(a.imageData)}else I.attachData(K.data);I.grab()&&(a?(a.busy=!0,a.worker.postMessage({cmd:"process",imageData:a.imageData},[a.imageData.buffer])):z())}else z()}function B(){J=!1,function a(){J||(A(),Q&&"LiveStream"==k.inputStream.type&&window.requestAnimFrame(a))}()}function C(a){function b(b){P.push(b),P.length>=k.numOfWorkers&&a()}var c;for(P=[],c=0;c0&&P.forEach(function(b){b.worker.postMessage({cmd:"setReaders",readers:a})})}var H,I,J,K,L,M,N,O={ctx:{image:null,overlay:null},dom:{image:null,overlay:null}},P=[],Q=!0;return{init:function(a,b,c){return k=i.mergeObjects(k,a),c?(Q=!1,p(c),b()):void r(b)},start:function(){B()},stop:function(){J=!0,P.forEach(function(a){a.worker.terminate(),console.log("Worker terminated!")}),P.length=0,"LiveStream"===k.inputStream.type&&(m.release(),H.clearEventHandlers())},pause:function(){J=!0},onDetected:function(a){l.subscribe("detected",a)},onProcessed:function(a){l.subscribe("processed",a)},setReaders:function(a){G(a)},registerResultCollector:function(a){a&&"function"==typeof a.addResult&&(N=a)},canvas:O,decodeSingle:function(a,b){a=i.mergeObjects({inputStream:{type:"ImageStream",sequence:!1,size:800,src:a.src},numOfWorkers:1,locator:{halfSample:!1}},a),this.init(a,function(){l.once("processed",function(a){J=!0,b.call(null,a)},!0),B()})},Reader:{EANReader:c,Code128Reader:b},ImageWrapper:e,ImageDebug:n,ResultCollector:o}}),c("quagga")});
\ No newline at end of file
+var b,c,d;!function(a){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(n=n.slice(0,n.length-1),a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.concat(a),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(b,c){return function(){return n.apply(a,v.call(arguments,0).concat([b,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(b){if(e(r,b)){var c=r[b];delete r[b],t[b]=!0,m.apply(a,c)}if(!e(q,b)&&!e(t,b))throw new Error("No "+b);return q[b]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(b,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||b,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n1?f.size:Math.floor(b/e*f.size):b,d=f.size?b/e>1?Math.floor(e/b*f.size):f.size:e,j.x=c,j.y=d}var c,d,e={},f=null,g=["canrecord","ended"],h={},i={x:0,y:0},j={x:0,y:0};return e.getRealWidth=function(){return a.videoWidth},e.getRealHeight=function(){return a.videoHeight},e.getWidth=function(){return c},e.getHeight=function(){return d},e.setWidth=function(a){c=a},e.setHeight=function(a){d=a},e.setInputStream=function(b){f=b,a.src="undefined"!=typeof b.src?b.src:""},e.ended=function(){return a.ended},e.getConfig=function(){return f},e.setAttribute=function(b,c){a.setAttribute(b,c)},e.pause=function(){a.pause()},e.play=function(){a.play()},e.setCurrentTime=function(b){"LiveStream"!==f.type&&(a.currentTime=b)},e.addEventListener=function(b,c,d){-1!==g.indexOf(b)?(h[b]||(h[b]=[]),h[b].push(c)):a.addEventListener(b,c,d)},e.clearEventHandlers=function(){g.forEach(function(b){var c=h[b];c&&c.length>0&&c.forEach(function(c){a.removeEventListener(b,c)})})},e.trigger=function(a,c){var d,f=h[a];if("canrecord"===a&&b(),f&&f.length>0)for(d=0;d1?g.size:Math.floor(h/i*g.size):h,e=g.size?h/i>1?Math.floor(i/h*g.size):g.size:i,u.x=d,u.y=e,l=!0,j=0,setTimeout(function(){c("canrecord",[])},0)},o,n,g.sequence)}function c(a,b){var c,d=s[a];if(d&&d.length>0)for(c=0;cj?j++:setTimeout(function(){q=!0,c("ended",[])},0)),a):null},f},b}),glMatrixArrayType=Float32Array,"undefined"!=typeof window&&(window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a,b){window.setTimeout(a,1e3/60)}}(),navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL),d("typedefs",function(a){return function(){var b;return b||a.typedefs}}(this)),d("subImage",["typedefs"],function(){"use strict";function a(a,b,c){c||(c={data:null,size:b}),this.data=c.data,this.originalSize=c.size,this.I=c,this.from=a,this.size=b}return a.prototype.show=function(a,b){var c,d,e,f,g,h,i;for(b||(b=1),c=a.getContext("2d"),a.width=this.size.x,a.height=this.size.y,d=c.getImageData(0,0,a.width,a.height),e=d.data,f=0,g=0;gb?!0:!1},getPoints:function(){return f},getCenter:function(){return g}}},createPoint:function(a,b,c){return{rad:a[c],point:a,id:b}}};return a});var e={};e.create=function(a){var b;return a?(b=new glMatrixArrayType(3),b[0]=a[0],b[1]=a[1],b[2]=a[2]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0]:3),b},e.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b},e.add=function(a,b,c){return c&&a!=c?(c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2],c):(a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a)},e.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2],c):(a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a)},e.negate=function(a,b){return b||(b=a),b[0]=-a[0],b[1]=-a[1],b[2]=-a[2],b},e.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a)},e.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=Math.sqrt(c*c+d*d+e*e);return f?1==f?(b[0]=c,b[1]=d,b[2]=e,b):(f=1/f,b[0]=c*f,b[1]=d*f,b[2]=e*f,b):(b[0]=0,b[1]=0,b[2]=0,b)},e.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=b[0],h=b[1],i=b[2];return c[0]=e*i-f*h,c[1]=f*g-d*i,c[2]=d*h-e*g,c},e.length=function(a){var b=a[0],c=a[1],d=a[2];return Math.sqrt(b*b+c*c+d*d)},e.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]},e.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1],f=a[2]-b[2],g=Math.sqrt(d*d+e*e+f*f);return g?(g=1/g,c[0]=d*g,c[1]=e*g,c[2]=f*g,c):(c[0]=0,c[1]=0,c[2]=0,c)},e.lerp=function(a,b,c,d){return d||(d=a),d[0]=a[0]+c*(b[0]-a[0]),d[1]=a[1]+c*(b[1]-a[1]),d[2]=a[2]+c*(b[2]-a[2]),d},e.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};var f={};f.create=function(a){var b;return a?(b=new glMatrixArrayType(9),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0,0,0,0,0,0]:9),b},f.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b},f.identity=function(a){return a[0]=1,a[1]=0,a[2]=0,a[3]=0,a[4]=1,a[5]=0,a[6]=0,a[7]=0,a[8]=1,a},f.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[5];return a[1]=a[3],a[2]=a[6],a[3]=c,a[5]=a[7],a[6]=d,a[7]=e,a}return b[0]=a[0],b[1]=a[3],b[2]=a[6],b[3]=a[1],b[4]=a[4],b[5]=a[7],b[6]=a[2],b[7]=a[5],b[8]=a[8],b},f.toMat4=function(a,b){return b||(b=g.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=0,b[4]=a[3],b[5]=a[4],b[6]=a[5],b[7]=0,b[8]=a[6],b[9]=a[7],b[10]=a[8],b[11]=0,b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},f.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};var g={};g.create=function(a){var b;return a?(b=new glMatrixArrayType(16),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]:16),b},g.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15],b},g.identity=function(a){return a[0]=1,a[1]=0,a[2]=0,a[3]=0,a[4]=0,a[5]=1,a[6]=0,a[7]=0,a[8]=0,a[9]=0,a[10]=1,a[11]=0,a[12]=0,a[13]=0,a[14]=0,a[15]=1,a},g.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[3],f=a[6],g=a[7],h=a[11];return a[1]=a[4],a[2]=a[8],a[3]=a[12],a[4]=c,a[6]=a[9],a[7]=a[13],a[8]=d,a[9]=f,a[11]=a[14],a[12]=e,a[13]=g,a[14]=h,a}return b[0]=a[0],b[1]=a[4],b[2]=a[8],b[3]=a[12],b[4]=a[1],b[5]=a[5],b[6]=a[9],b[7]=a[13],b[8]=a[2],b[9]=a[6],b[10]=a[10],b[11]=a[14],b[12]=a[3],b[13]=a[7],b[14]=a[11],b[15]=a[15],b},g.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],f=a[4],g=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],m=a[11],n=a[12],o=a[13],p=a[14],q=a[15];return n*k*h*e-j*o*h*e-n*g*l*e+f*o*l*e+j*g*p*e-f*k*p*e-n*k*d*i+j*o*d*i+n*c*l*i-b*o*l*i-j*c*p*i+b*k*p*i+n*g*d*m-f*o*d*m-n*c*h*m+b*o*h*m+f*c*p*m-b*g*p*m-j*g*d*q+f*k*d*q+j*c*h*q-b*k*h*q-f*c*l*q+b*g*l*q},g.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],m=a[10],n=a[11],o=a[12],p=a[13],q=a[14],r=a[15],s=c*h-d*g,t=c*i-e*g,u=c*j-f*g,v=d*i-e*h,w=d*j-f*h,x=e*j-f*i,y=k*p-l*o,z=k*q-m*o,A=k*r-n*o,B=l*q-m*p,C=l*r-n*p,D=m*r-n*q,E=1/(s*D-t*C+u*B+v*A-w*z+x*y);return b[0]=(h*D-i*C+j*B)*E,b[1]=(-d*D+e*C-f*B)*E,b[2]=(p*x-q*w+r*v)*E,b[3]=(-l*x+m*w-n*v)*E,b[4]=(-g*D+i*A-j*z)*E,b[5]=(c*D-e*A+f*z)*E,b[6]=(-o*x+q*u-r*t)*E,b[7]=(k*x-m*u+n*t)*E,b[8]=(g*C-h*A+j*y)*E,b[9]=(-c*C+d*A-f*y)*E,b[10]=(o*w-p*u+r*s)*E,b[11]=(-k*w+l*u-n*s)*E,b[12]=(-g*B+h*z-i*y)*E,b[13]=(c*B-d*z+e*y)*E,b[14]=(-o*v+p*t-q*s)*E,b[15]=(k*v-l*t+m*s)*E,b},g.toRotationMat=function(a,b){return b||(b=g.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},g.toMat3=function(a,b){return b||(b=f.create()),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[4],b[4]=a[5],b[5]=a[6],b[6]=a[8],b[7]=a[9],b[8]=a[10],b},g.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],h=a[5],i=a[6],j=a[8],k=a[9],l=a[10],m=l*h-i*k,n=-l*g+i*j,o=k*g-h*j,p=c*m+d*n+e*o;if(!p)return null;var q=1/p;return b||(b=f.create()),b[0]=m*q,b[1]=(-l*d+e*k)*q,b[2]=(i*d-e*h)*q,b[3]=n*q,b[4]=(l*c-e*j)*q,b[5]=(-i*c+e*g)*q,b[6]=o*q,b[7]=(-k*c+d*j)*q,b[8]=(h*c-d*g)*q,b},g.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],m=a[9],n=a[10],o=a[11],p=a[12],q=a[13],r=a[14],s=a[15],t=b[0],u=b[1],v=b[2],w=b[3],x=b[4],y=b[5],z=b[6],A=b[7],B=b[8],C=b[9],D=b[10],E=b[11],F=b[12],G=b[13],H=b[14],I=b[15];return c[0]=t*d+u*h+v*l+w*p,c[1]=t*e+u*i+v*m+w*q,c[2]=t*f+u*j+v*n+w*r,c[3]=t*g+u*k+v*o+w*s,c[4]=x*d+y*h+z*l+A*p,c[5]=x*e+y*i+z*m+A*q,c[6]=x*f+y*j+z*n+A*r,c[7]=x*g+y*k+z*o+A*s,c[8]=B*d+C*h+D*l+E*p,c[9]=B*e+C*i+D*m+E*q,c[10]=B*f+C*j+D*n+E*r,c[11]=B*g+C*k+D*o+E*s,c[12]=F*d+G*h+H*l+I*p,c[13]=F*e+G*i+H*m+I*q,c[14]=F*f+G*j+H*n+I*r,c[15]=F*g+G*k+H*o+I*s,c},g.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[4]*e+a[8]*f+a[12],c[1]=a[1]*d+a[5]*e+a[9]*f+a[13],c[2]=a[2]*d+a[6]*e+a[10]*f+a[14],c},g.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3];return c[0]=a[0]*d+a[4]*e+a[8]*f+a[12]*g,c[1]=a[1]*d+a[5]*e+a[9]*f+a[13]*g,c[2]=a[2]*d+a[6]*e+a[10]*f+a[14]*g,c[3]=a[3]*d+a[7]*e+a[11]*f+a[15]*g,c},g.translate=function(a,b,c){var d=b[0],e=b[1],f=b[2];if(!c||a==c)return a[12]=a[0]*d+a[4]*e+a[8]*f+a[12],a[13]=a[1]*d+a[5]*e+a[9]*f+a[13],a[14]=a[2]*d+a[6]*e+a[10]*f+a[14],a[15]=a[3]*d+a[7]*e+a[11]*f+a[15],a;var g=a[0],h=a[1],i=a[2],j=a[3],k=a[4],l=a[5],m=a[6],n=a[7],o=a[8],p=a[9],q=a[10],r=a[11];return c[0]=g,c[1]=h,c[2]=i,c[3]=j,c[4]=k,c[5]=l,c[6]=m,c[7]=n,c[8]=o,c[9]=p,c[10]=q,c[11]=r,c[12]=g*d+k*e+o*f+a[12],c[13]=h*d+l*e+p*f+a[13],c[14]=i*d+m*e+q*f+a[14],c[15]=j*d+n*e+r*f+a[15],c},g.scale=function(a,b,c){var d=b[0],e=b[1],f=b[2];return c&&a!=c?(c[0]=a[0]*d,c[1]=a[1]*d,c[2]=a[2]*d,c[3]=a[3]*d,c[4]=a[4]*e,c[5]=a[5]*e,c[6]=a[6]*e,c[7]=a[7]*e,c[8]=a[8]*f,c[9]=a[9]*f,c[10]=a[10]*f,c[11]=a[11]*f,c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15],c):(a[0]*=d,a[1]*=d,a[2]*=d,a[3]*=d,a[4]*=e,a[5]*=e,a[6]*=e,a[7]*=e,a[8]*=f,a[9]*=f,a[10]*=f,a[11]*=f,a)},g.rotate=function(a,b,c,d){var e=c[0],f=c[1],g=c[2],h=Math.sqrt(e*e+f*f+g*g);if(!h)return null;1!=h&&(h=1/h,e*=h,f*=h,g*=h);var i=Math.sin(b),j=Math.cos(b),k=1-j,l=a[0],m=a[1],n=a[2],o=a[3],p=a[4],q=a[5],r=a[6],s=a[7],t=a[8],u=a[9],v=a[10],w=a[11],x=e*e*k+j,y=f*e*k+g*i,z=g*e*k-f*i,A=e*f*k-g*i,B=f*f*k+j,C=g*f*k+e*i,D=e*g*k+f*i,E=f*g*k-e*i,F=g*g*k+j;return d?a!=d&&(d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a,d[0]=l*x+p*y+t*z,d[1]=m*x+q*y+u*z,d[2]=n*x+r*y+v*z,d[3]=o*x+s*y+w*z,d[4]=l*A+p*B+t*C,d[5]=m*A+q*B+u*C,d[6]=n*A+r*B+v*C,d[7]=o*A+s*B+w*C,d[8]=l*D+p*E+t*F,d[9]=m*D+q*E+u*F,d[10]=n*D+r*E+v*F,d[11]=o*D+s*E+w*F,d},g.rotateX=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[4],g=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],m=a[11];return c?a!=c&&(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[4]=f*e+j*d,c[5]=g*e+k*d,c[6]=h*e+l*d,c[7]=i*e+m*d,c[8]=f*-d+j*e,c[9]=g*-d+k*e,c[10]=h*-d+l*e,c[11]=i*-d+m*e,c},g.rotateY=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[0],g=a[1],h=a[2],i=a[3],j=a[8],k=a[9],l=a[10],m=a[11];return c?a!=c&&(c[4]=a[4],c[5]=a[5],c[6]=a[6],c[7]=a[7],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[0]=f*e+j*-d,c[1]=g*e+k*-d,c[2]=h*e+l*-d,c[3]=i*e+m*-d,c[8]=f*d+j*e,c[9]=g*d+k*e,c[10]=h*d+l*e,c[11]=i*d+m*e,c},g.rotateZ=function(a,b,c){var d=Math.sin(b),e=Math.cos(b),f=a[0],g=a[1],h=a[2],i=a[3],j=a[4],k=a[5],l=a[6],m=a[7];return c?a!=c&&(c[8]=a[8],c[9]=a[9],c[10]=a[10],c[11]=a[11],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a,c[0]=f*e+j*d,c[1]=g*e+k*d,c[2]=h*e+l*d,c[3]=i*e+m*d,c[4]=f*-d+j*e,c[5]=g*-d+k*e,c[6]=h*-d+l*e,c[7]=i*-d+m*e,c},g.frustum=function(a,b,c,d,e,f,h){h||(h=g.create());var i=b-a,j=d-c,k=f-e;return h[0]=2*e/i,h[1]=0,h[2]=0,h[3]=0,h[4]=0,h[5]=2*e/j,h[6]=0,h[7]=0,h[8]=(b+a)/i,h[9]=(d+c)/j,h[10]=-(f+e)/k,h[11]=-1,h[12]=0,h[13]=0,h[14]=-(f*e*2)/k,h[15]=0,h},g.perspective=function(a,b,c,d,e){var f=c*Math.tan(a*Math.PI/360),h=f*b;return g.frustum(-h,h,-f,f,c,d,e)},g.ortho=function(a,b,c,d,e,f,h){h||(h=g.create());var i=b-a,j=d-c,k=f-e;return h[0]=2/i,h[1]=0,h[2]=0,h[3]=0,h[4]=0,h[5]=2/j,h[6]=0,h[7]=0,h[8]=0,h[9]=0,h[10]=-2/k,h[11]=0,h[12]=-(a+b)/i,h[13]=-(d+c)/j,h[14]=-(f+e)/k,h[15]=1,h},g.lookAt=function(a,b,c,d){d||(d=g.create());var e=a[0],f=a[1],h=a[2],i=c[0],j=c[1],k=c[2],l=b[0],m=b[1],n=b[2];if(e==l&&f==m&&h==n)return g.identity(d);var o,p,q,r,s,t,u,v,w,x;return o=e-b[0],p=f-b[1],q=h-b[2],x=1/Math.sqrt(o*o+p*p+q*q),o*=x,p*=x,q*=x,r=j*q-k*p,s=k*o-i*q,t=i*p-j*o,x=Math.sqrt(r*r+s*s+t*t),x?(x=1/x,r*=x,s*=x,t*=x):(r=0,s=0,t=0),u=p*t-q*s,v=q*r-o*t,w=o*s-p*r,x=Math.sqrt(u*u+v*v+w*w),x?(x=1/x,u*=x,v*=x,w*=x):(u=0,v=0,w=0),d[0]=r,d[1]=u,d[2]=o,d[3]=0,d[4]=s,d[5]=v,d[6]=p,d[7]=0,d[8]=t,d[9]=w,d[10]=q,d[11]=0,d[12]=-(r*e+s*f+t*h),d[13]=-(u*e+v*f+w*h),d[14]=-(o*e+p*f+q*h),d[15]=1,d},g.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+",\n "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+",\n "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+",\n "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"},quat4={},quat4.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},quat4.set=function(a,b){return b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b},quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];return b&&a!=b?(b[0]=c,b[1]=d,b[2]=e,b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),b):(a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),a)},quat4.inverse=function(a,b){return b&&a!=b?(b[0]=-a[0],b[1]=-a[1],b[2]=-a[2],b[3]=a[3],b):(a[0]*=-1,a[1]*=-1,a[2]*=-1,a)},quat4.length=function(a){var b=a[0],c=a[1],d=a[2],e=a[3];return Math.sqrt(b*b+c*c+d*d+e*e)},quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=Math.sqrt(c*c+d*d+e*e+f*f);return 0==g?(b[0]=0,b[1]=0,b[2]=0,b[3]=0,b):(g=1/g,b[0]=c*g,b[1]=d*g,b[2]=e*g,b[3]=f*g,b)},quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=b[0],i=b[1],j=b[2],k=b[3];return c[0]=d*k+g*h+e*j-f*i,c[1]=e*k+g*i+f*h-d*j,c[2]=f*k+g*j+d*i-e*h,c[3]=g*k-d*h-e*i-f*j,c},quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=a[0],h=a[1],i=a[2],j=a[3],k=j*d+h*f-i*e,l=j*e+i*d-g*f,m=j*f+g*e-h*d,n=-g*d-h*e-i*f;return c[0]=k*j+n*-g+l*-i-m*-h,c[1]=l*j+n*-h+m*-g-k*-i,c[2]=m*j+n*-i+k*-h-l*-g,c},quat4.toMat3=function(a,b){b||(b=f.create());var c=a[0],d=a[1],e=a[2],g=a[3],h=c+c,i=d+d,j=e+e,k=c*h,l=c*i,m=c*j,n=d*i,o=d*j,p=e*j,q=g*h,r=g*i,s=g*j;return b[0]=1-(n+p),b[1]=l-s,b[2]=m+r,b[3]=l+s,b[4]=1-(k+p),b[5]=o-q,b[6]=m-r,b[7]=o+q,b[8]=1-(k+n),b},quat4.toMat4=function(a,b){b||(b=g.create());var c=a[0],d=a[1],e=a[2],f=a[3],h=c+c,i=d+d,j=e+e,k=c*h,l=c*i,m=c*j,n=d*i,o=d*j,p=e*j,q=f*h,r=f*i,s=f*j;return b[0]=1-(n+p),b[1]=l-s,b[2]=m+r,b[3]=0,b[4]=l+s,b[5]=1-(k+p),b[6]=o-q,b[7]=0,b[8]=m-r,b[9]=o+q,b[10]=1-(k+n),b[11]=0,b[12]=0,b[13]=0,b[14]=0,b[15]=1,b},quat4.slerp=function(a,b,c,d){d||(d=a);var e=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];if(Math.abs(e)>=1)return d!=a&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3]),d;var f=Math.acos(e),g=Math.sqrt(1-e*e);if(Math.abs(g)<.001)return d[0]=.5*a[0]+.5*b[0],d[1]=.5*a[1]+.5*b[1],d[2]=.5*a[2]+.5*b[2],d[3]=.5*a[3]+.5*b[3],d;var h=Math.sin((1-c)*f)/g,i=Math.sin(c*f)/g;return d[0]=a[0]*h+b[0]*i,d[1]=a[1]*h+b[1]*i,d[2]=a[2]*h+b[2]*i,d[3]=a[3]*h+b[3]*i,d},quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"},d("glMatrix",["typedefs"],function(a){return function(){var b;return b||a.glMatrix}}(this)),g.xVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3];return c[0]=a[0]*d+a[1]*e+a[2]*f+a[3]*g,c[1]=a[4]*d+a[5]*e+a[6]*f+a[7]*g,c[2]=a[8]*d+a[9]*e+a[10]*f+a[11]*g,c[3]=a[12]*d+a[13]*e+a[14]*f+a[15]*g,c},f.scale=function(a,b,c){return c&&a!=c?(c=f.create(),c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c[4]=a[4]*b,c[5]=a[5]*b,c[6]=a[6]*b,c[7]=a[7]*b,c[8]=a[8]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a[4]*=b,a[5]*=b,a[6]*=b,a[7]*=b,a[8]*=b,a)},f.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],f=a[3],g=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=1/(c*g*k+d*h*i+e*f*j-e*g*i-d*f*k-c*h*j);return b[0]=(g*k-h*j)*l,b[1]=(e*j-d*k)*l,b[2]=(d*h-e*g)*l,b[3]=(h*i-f*k)*l,b[4]=(c*k-e*i)*l,b[5]=(e*f-c*h)*l,b[6]=(f*j-g*i)*l,b[7]=(d*i-c*j)*l,b[8]=(c*g-d*f)*l,b},f.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],m=b[0],n=b[1],o=b[2],p=b[3],q=b[4],r=b[5],s=b[6],t=b[7],u=b[8];return c[0]=d*m+e*p+f*s,c[1]=d*n+e*q+f*t,c[2]=d*o+e*r+f*u,c[3]=g*m+h*p+i*s,c[4]=g*n+h*q+i*t,c[5]=g*o+h*r+i*u,c[6]=j*m+k*p+l*s,c[7]=j*n+k*q+l*t,c[8]=j*o+k*r+l*u,c},f.xVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[1]*e+a[2]*f,c[1]=a[3]*d+a[4]*e+a[5]*f,c[2]=a[6]*d+a[7]*e+a[8]*f,c};var h={};h.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},h.project=function(a,b){return b||(b=a),b[0]=a[0]/a[3],b[1]=a[1]/a[3],b[2]=a[2]/a[3],b},h.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[4]*=b,a)},h.xMat4=function(a,b,c){c||(c=a);var d=a[0],e=a[1],f=a[2],g=a[3];return c[0]=b[0]*d+b[4]*e+b[8]*f+b[12]*g,c[1]=b[1]*d+b[5]*e+b[9]*f+b[13]*g,c[2]=b[2]*d+b[6]*e+b[10]*f+b[14]*g,c[3]=b[3]*d+b[7]*e+b[11]*f+b[15]*g,c};var i={};i.create=function(a){var b;return a?(b=new glMatrixArrayType(4),b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0,0,0]:4),b},i.xVec2=function(a,b,c){c||(c=b);var d=b[0],e=b[1];return c[0]=a[0]*d+a[1]*e,c[1]=a[2]*d+a[3]*e,c},i.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a)},i.determinant=function(a){return a[0]*a[3]-a[1]*a[2]},i.inverse=function(a){var b=1/i.determinant(a),c=a[3]*b,d=-a[1]*b,e=-a[2]*b,f=a[0];return a[0]=c,a[1]=d,a[2]=e,a[3]=f,a};var j={};j.create=function(a){var b;return a?(b=new glMatrixArrayType(2),b[0]=a[0],b[1]=a[1]):b=new glMatrixArrayType(glMatrixArrayType===Array?[0,0]:2),b},j.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c):(a[0]-=b[0],a[1]-=b[1],a)},j.add=function(a,b,c){return c&&a!=c?(c[0]=a[0]+b[0],c[1]=a[1]+b[1],c):(a[0]+=b[0],a[1]+=b[1],a)},j.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c):(a[0]*=b,a[1]*=b,a)},j.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=Math.sqrt(c*c+d*d);return e?1==e?(b[0]=c,b[1]=d,b):(e=1/e,b[0]=c*e,b[1]=d*e,b):(b[0]=0,b[1]=0,b)},j.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]},j.multiply=function(a,b,c){return c||(c=a),c[0]=a[0]*b[0],c[1]=a[1]*b[1],c},j.unproject=function(a){return e.create([a[0],a[1],1])},j.length=function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},j.perspectiveProject=function(a){var b=j.create(a);return j.scale(b,1/a[2])},e.project=function(a){return j.scale(j.create(a),1/a[2])};var k={};k.scale=function(a,b,c){return c&&a!=c?(c[0]=a[0]*b,c[1]=a[1]*b,c[2]=a[2]*b,c[3]=a[3]*b,c[4]=a[4]*b,c[5]=a[5]*b,c):(a[0]*=b,a[1]*=b,a[2]*=b,a[3]*=b,a[4]*=b,a[5]*=b,a)},k.subtract=function(a,b,c){return c&&a!=c?(c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2],c[3]=a[3]-b[3],c[4]=a[4]-b[4],c[5]=a[5]-b[5],c):(a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a[3]-=b[3],a[4]-=b[4],a[5]-=b[5],a)},k.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]+a[4]*b[4]+a[5]*b[5]};var l={};return l.xVec6=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],i=b[5];return c[0]=a[0]*d+a[1]*e+a[2]*f+a[3]*g+a[4]*h+a[5]*i,c[1]=a[6]*d+a[7]*e+a[8]*f+a[9]*g+a[10]*h+a[11]*i,c[2]=a[12]*d+a[13]*e+a[14]*f+a[15]*g+a[16]*h+a[17]*i,c[3]=a[18]*d+a[19]*e+a[20]*f+a[21]*g+a[22]*h+a[23]*i,c[4]=a[24]*d+a[25]*e+a[26]*f+a[27]*g+a[28]*h+a[29]*i,c[5]=a[30]*d+a[31]*e+a[32]*f+a[33]*g+a[34]*h+a[35]*i,c},f.xVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],f=b[2];return c[0]=a[0]*d+a[1]*e+a[2]*f,c[1]=a[3]*d+a[4]*e+a[5]*f,c[2]=a[6]*d+a[7]*e+a[8]*f,c},d("glMatrixAddon",["glMatrix"],function(a){return function(){var b;return b||a.glMatrixAddon}}(this)),d("array_helper",[],function(){"use strict";return{init:function(a,b){for(var c=a.length;c--;)a[c]=b},shuffle:function(a){var b,c,d=a.length-1;for(d;d>=0;d--)b=Math.floor(Math.random()*d),c=a[d],a[d]=a[b],a[b]=c;return a},toPointList:function(a){var b,c,d=[],e=[];for(b=0;b=b&&e.push(a[d]);return e},maxIndex:function(a){var b,c=0;for(b=0;ba[c]&&(c=b);return c},max:function(a){var b,c=0;for(b=0;bc&&(c=a[b]);return c},sum:function(a){for(var b=a.length,c=0;b--;)c+=a[b];return c}}}),d("cv_utils",["cluster","glMatrixAddon","array_helper"],function(a,b,c){"use strict";var d={};return d.imageRef=function(a,b){var c={x:a,y:b,toVec2:function(){return j.create([this.x,this.y])},toVec3:function(){return e.create([this.x,this.y,1])},round:function(){return this.x=Math.floor(this.x>0?this.x+.5:this.x-.5),this.y=Math.floor(this.y>0?this.y+.5:this.y-.5),this}};return c},d.computeIntegralImage2=function(a,b){var c,d,e=a.data,f=a.size.x,g=a.size.y,h=b.data,i=0,j=0,k=0,l=0,m=0;for(k=f,i=0,d=1;g>d;d++)i+=e[j],h[k]+=i,j+=f,k+=f;for(j=0,k=1,i=0,c=1;f>c;c++)i+=e[j],h[k]+=i,j++,k++;for(d=1;g>d;d++)for(j=d*f+1,k=(d-1)*f+1,l=d*f,m=(d-1)*f,c=1;f>c;c++)h[j]+=e[j]+h[k]+h[l]-h[m],j++,k++,l++,m++},d.computeIntegralImage=function(a,b){for(var c=a.data,d=a.size.x,e=a.size.y,f=b.data,g=0,h=0;d>h;h++)g+=c[h],f[h]=g;for(var i=1;e>i;i++){g=0;for(var j=0;d>j;j++)g+=c[i*d+j],f[i*d+j]=g+f[(i-1)*d+j]}},d.thresholdImage=function(a,b,c){c||(c=a);for(var d=a.data,e=d.length,f=c.data;e--;)f[e]=d[e]>e]++;return g},d.sharpenLine=function(a){var b,c,d=a.length,e=a[0],f=a[1];for(b=1;d-1>b;b++)c=a[b+1],a[b-1]=2*f-e-c&255,e=f,f=c;return a},d.determineOtsuThreshold=function(a,b){function e(a,b){var c,d=0;for(c=a;b>=c;c++)d+=h[c];return d}function f(a,b){var c,d=0;for(c=a;b>=c;c++)d+=c*h[c];return d}function g(){var g,i,j,k,l,m,n,o=[0],p=(1<k;k++)g=e(0,k),i=e(k+1,p),j=g*i,0===j&&(j=1),l=f(0,k)*i,m=f(k+1,p)*g,n=l-m,o[k]=n*n/j;return c.maxIndex(o)}b||(b=8);var h,i,j=8-b;return i=g(),i<=e;e++)for(f=0;n>f;f++)m[e*n+f]=0,m[(o-1-e)*n+f]=0;for(e=r;o-r>e;e++)for(f=0;r>=f;f++)m[e*n+f]=0,m[e*n+(n-1-f)]=0;for(e=r+1;o-r-1>e;e++)for(f=r+1;n-r>f;f++)g=p[(e-r-1)*n+(f-r-1)],h=p[(e-r-1)*n+(f+r)],i=p[(e+r)*n+(f-r-1)],j=p[(e+r)*n+(f+r)],q=j-i-h+g,k=q/s,m[e*n+f]=l[e*n+f]>k+5?0:1},d.cluster=function(b,c,d){function e(a){var b=!1;for(g=0;gb.x-j&&a.xb.y-k&&a.yd;d++){for(h=Math.floor(Math.random()*a.length),f=[],i=h,f.push(a[i]);null!==(i=c(i,!0));)f.push(a[i]);if(h>0)for(i=h;null!==(i=c(i,!1));)f.push(a[i]);f.length>g.length&&(g=f)}return g}},d.DILATE=1,d.ERODE=2,d.dilate=function(a,b){var c,d,e,f,g,h,i,j=a.data,k=b.data,l=a.size.y,m=a.size.x;for(c=1;l-1>c;c++)for(d=1;m-1>d;d++)f=c-1,g=c+1,h=d-1,i=d+1,e=j[f*m+h]+j[f*m+i]+j[c*m+d]+j[g*m+h]+j[g*m+i],k[c*m+d]=e>0?1:0},d.erode=function(a,b){var c,d,e,f,g,h,i,j=a.data,k=b.data,l=a.size.y,m=a.size.x;for(c=1;l-1>c;c++)for(d=1;m-1>d;d++)f=c-1,g=c+1,h=d-1,i=d+1,e=j[f*m+h]+j[f*m+i]+j[c*m+d]+j[g*m+h]+j[g*m+i],k[c*m+d]=5===e?1:0},d.subtract=function(a,b,c){c||(c=a);for(var d=a.data.length,e=a.data,f=b.data,g=c.data;d--;)g[d]=e[d]-f[d]},d.bitwiseOr=function(a,b,c){c||(c=a);for(var d=a.data.length,e=a.data,f=b.data,g=c.data;d--;)g[d]=e[d]||f[d]},d.countNonZero=function(a){for(var b=a.data.length,c=a.data,d=0;b--;)d+=c[b];return d},d.topGeneric=function(a,b,c){var d,e,f,g,h=0,i=0,j=[];for(d=0;b>d;d++)j[d]={score:0,item:null};for(d=0;di)for(f=j[h],f.score=e,f.item=a[d],i=Number.MAX_VALUE,g=0;b>g;g++)j[g].scoref;){for(d=0;h>d;d++)c[i]=Math.floor((.299*a[4*e+0]+.587*a[4*e+1]+.114*a[4*e+2]+(.299*a[4*(e+1)+0]+.587*a[4*(e+1)+1]+.114*a[4*(e+1)+2])+(.299*a[4*f+0]+.587*a[4*f+1]+.114*a[4*f+2])+(.299*a[4*(f+1)+0]+.587*a[4*(f+1)+1]+.114*a[4*(f+1)+2]))/4),i++,e+=2,f+=2;e+=j,f+=j}},d.computeGray=function(a,b,c){var d,e=a.length/4|0,f=c&&c.singleChannel===!0;if(f)for(d=0;e>d;d++)b[d]=a[4*d+0];else for(d=0;e>d;d++)b[d]=Math.floor(.299*a[4*d+0]+.587*a[4*d+1]+.114*a[4*d+2])},d.loadImageArray=function(a,b,c){c||(c=document.createElement("canvas"));var e=new Image;e.callback=b,e.onload=function(){c.width=this.width,c.height=this.height;var a=c.getContext("2d");a.drawImage(this,0,0);var b=new Uint8Array(this.width*this.height);a.drawImage(this,0,0);var e=a.getImageData(0,0,this.width,this.height).data;d.computeGray(e,b),this.callback(b,{x:this.width,y:this.height},this)},e.src=a},d.halfSample=function(a,b){for(var c=a.data,d=a.size.x,e=b.data,f=0,g=d,h=c.length,i=d/2,j=0;h>g;){for(var k=0;i>k;k++)e[j]=Math.floor((c[f]+c[f+1]+c[g]+c[g+1])/4),j++,f+=2,g+=2;f+=d,g+=d}},d.hsv2rgb=function(a,b){var c=a[0],d=a[1],e=a[2],f=e*d,g=f*(1-Math.abs(c/60%2-1)),h=e-f,i=0,j=0,k=0;return b=b||[0,0,0],60>c?(i=f,j=g):120>c?(i=g,j=f):180>c?(j=f,k=g):240>c?(j=g,k=f):300>c?(i=g,k=f):360>c&&(i=f,k=g),b[0]=255*(i+h)|0,b[1]=255*(j+h)|0,b[2]=255*(k+h)|0,b},d._computeDivisors=function(a){var b,c=[],d=[];for(b=1;bb[d]?d++:c++;return e},d.calculatePatchSize=function(a,b){function c(a){for(var b=0,c=a[Math.floor(a.length/2)];b0&&(c=Math.abs(a[b]-m)>Math.abs(a[b-1]-m)?a[b-1]:a[b]),m/ci[k-1]/i[k]?{x:c,y:c}:null}var d,e=this._computeDivisors(b.x),f=this._computeDivisors(b.y),g=Math.max(b.x,b.y),h=this._computeIntersection(e,f),i=[8,10,15,20,32,60,80],j={"x-small":5,small:4,medium:3,large:2,"x-large":1},k=j[a]||j.medium,l=i[k],m=Math.floor(g/l);return d=c(h),d||(d=c(this._computeDivisors(g)),d||(d=c(this._computeDivisors(m*l)))),d},d._parseCSSDimensionValues=function(a){var b={value:parseFloat(a),unit:(a.indexOf("%")===a.length-1,"%")};return b},d._dimensionsConverters={top:function(a,b){return"%"===a.unit?Math.floor(b.height*(a.value/100)):void 0},right:function(a,b){return"%"===a.unit?Math.floor(b.width-b.width*(a.value/100)):void 0},bottom:function(a,b){return"%"===a.unit?Math.floor(b.height-b.height*(a.value/100)):void 0},left:function(a,b){return"%"===a.unit?Math.floor(b.width*(a.value/100)):void 0}},d.computeImageArea=function(a,b,c){var e={width:a,height:b},f=Object.keys(c).reduce(function(a,b){var f=c[b],g=d._parseCSSDimensionValues(f),h=d._dimensionsConverters[b](g,e);return a[b]=h,a},{});return{sx:f.left,sy:f.top,sw:f.right-f.left,sh:f.bottom-f.top}},d}),d("image_wrapper",["subImage","cv_utils","array_helper"],function(a,b,c){"use strict";function d(a,b,d,e){b?this.data=b:d?(this.data=new d(a.x*a.y),d===Array&&e&&c.init(this.data,0)):(this.data=new Uint8Array(a.x*a.y),Uint8Array===Array&&e&&c.init(this.data,0)),this.size=a}return d.prototype.inImageWithBorder=function(a,b){return a.x>=b&&a.y>=b&&a.x=0&&u>=0&&n-1>v&&o-1>w){for(g=s,h=0;m>h;++h,j.add(g,y))for(k=0;l>k;++k,j.add(g,p))b.set(k,h,x(a,g[0],g[1]));return 0}var z=n-1,A=o-1,B=0;for(g=s,h=0;m>h;++h,j.add(g,y))for(k=0;l>k;++k,j.add(g,p))0<=g[0]&&0<=g[1]&&g[0]c;c++)for(d=0;e>d;d++)a.data[d*f+c]=this.data[(b.y+d)*this.size.x+b.x+c]},d.prototype.copyTo=function(a){for(var b=this.data.length,c=this.data,d=a.data;b--;)d[b]=c[b]},d.prototype.get=function(a,b){return this.data[b*this.size.x+a]},d.prototype.getSafe=function(a,b){var c;if(!this.indexMapping){for(this.indexMapping={x:[],y:[]},c=0;ca;a++)d[a]=d[(c-1)*b+a]=0;for(a=1;c-1>a;a++)d[a*b]=d[a*b+(b-1)]=0},d.prototype.invert=function(){for(var a=this.data,b=a.length;b--;)a[b]=a[b]?0:1},d.prototype.convolve=function(a){var b,c,d,e,f=a.length/2|0,g=0;for(c=0;c=e;e++)for(d=-f;f>=d;d++)g+=a[e+f][d+f]*this.getSafe(b+d,c+e);this.data[c*this.size.x+b]=g}},d.prototype.moments=function(a){var b,c,d,e,f,g,h,i,k,l,m,n,o=this.data,p=this.size.y,q=this.size.x,r=[],s=[],t=Math.PI,u=t/4;if(0>=a)return s;for(f=0;a>f;f++)r[f]={m00:0,m01:0,m10:0,m11:0,m02:0,m20:0,theta:0,rad:0};for(c=0;p>c;c++)for(e=c*c,b=0;q>b;b++)d=o[c*q+b],d>0&&(g=r[d-1],g.m00+=1,g.m01+=c,g.m10+=b,g.m11+=b*c,g.m02+=e,g.m20+=b*b);for(f=0;a>f;f++)g=r[f],isNaN(g.m00)||0===g.m00||(l=g.m10/g.m00,m=g.m01/g.m00,h=g.m11/g.m00-l*m,i=g.m02/g.m00-m*m,k=g.m20/g.m00-l*l,n=(i-k)/(2*h),n=.5*Math.atan(n)+(h>=0?u:-u)+t,g.theta=(180*n/t+90)%180-90,g.theta<0&&(g.theta+=180),g.rad=n>t?n-t:n,g.vec=j.create([Math.cos(n),Math.sin(n)]),s.push(g));return s},d.prototype.show=function(a,b){var c,d,e,f,g,h,i;for(b||(b=1),c=a.getContext("2d"),a.width=this.size.x,a.height=this.size.y,d=c.getImageData(0,0,a.width,a.height),e=d.data,f=0,i=0;ic||c>360)&&(c=360);for(var e=[0,1,1],f=[0,0,0],g=[255,255,255],h=[0,0,0],i=[],j=a.getContext("2d"),k=j.getImageData(d.x,d.y,this.size.x,this.size.y),l=k.data,m=this.data.length;m--;)e[0]=this.data[m]*c,i=e[0]<=0?g:e[0]>=360?h:b.hsv2rgb(e,f),l[4*m+0]=i[0],l[4*m+1]=i[1],l[4*m+2]=i[2],l[4*m+3]=255;j.putImageData(k,d.x,d.y)},d}),d("tracer",[],function(){"use strict";var a={searchDirections:[[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1],[-1,0],[-1,1]],create:function(a,b){function c(a,b,c,d){var e,k,l;for(e=0;7>e;e++){if(k=a.cy+i[a.dir][0],l=a.cx+i[a.dir][1],f=k*j+l,g[f]===b&&(0===h[f]||h[f]===c))return h[f]=c,a.cy=k,a.cx=l,!0;0===h[f]&&(h[f]=d),a.dir=(a.dir+1)%8}return!1}function d(a,b,c){return{dir:c,x:a,y:b,next:null,prev:null}}function e(a,b,e,f,g){var h,i,j,k=null,l={cx:b,cy:a,dir:0};if(c(l,f,e,g)){k=d(b,a,l.dir),h=k,j=l.dir,i=d(l.cx,l.cy,0),i.prev=h,h.next=i,i.next=null,h=i;do l.dir=(l.dir+6)%8,c(l,f,e,g),j!=l.dir?(h.dir=l.dir,i=d(l.cx,l.cy,0),i.prev=h,h.next=i,i.next=null,h=i):(h.dir=j,h.x=l.cx,h.y=l.cy),j=l.dir;while(l.cx!=b||l.cy!=a);k.prev=h.prev,h.prev.next=k}return k}var f,g=a.data,h=b.data,i=this.searchDirections,j=a.size.x;return{trace:function(a,b,d,e){return c(a,b,d,e)},contourTracing:function(a,b,c,d,f){return e(a,b,c,d,f)}}}};return a}),d("rasterizer",["tracer"],function(a){"use strict";var b={createContour2D:function(){return{dir:null,index:null,firstVertex:null,insideContours:null,nextpeer:null,prevpeer:null}},CONTOUR_DIR:{CW_DIR:0,CCW_DIR:1,UNKNOWN_DIR:2},DIR:{OUTSIDE_EDGE:-32767,INSIDE_EDGE:-32766},create:function(c,d){var e=c.data,f=d.data,g=c.size.x,h=c.size.y,i=a.create(c,d);return{rasterize:function(a){var c,d,j,k,l,m,n,o,p,q,r,s,t=[],u=0;for(s=0;400>s;s++)t[s]=0;for(t[0]=e[0],p=null,m=1;h-1>m;m++)for(k=0,d=t[0],l=1;g-1>l;l++)if(r=m*g+l,0===f[r])if(c=e[r],c!==d){if(0===k)j=u+1,t[j]=c,d=c,n=i.contourTracing(m,l,j,c,b.DIR.OUTSIDE_EDGE),null!==n&&(u++,k=j,o=b.createContour2D(),o.dir=b.CONTOUR_DIR.CW_DIR,o.index=k,o.firstVertex=n,o.nextpeer=p,o.insideContours=null,null!==p&&(p.prevpeer=o),p=o);else if(n=i.contourTracing(m,l,b.DIR.INSIDE_EDGE,c,k),null!==n){for(o=b.createContour2D(),o.firstVertex=n,o.insideContours=null,0===a?o.dir=b.CONTOUR_DIR.CCW_DIR:o.dir=b.CONTOUR_DIR.CW_DIR,o.index=a,q=p;null!==q&&q.index!==k;)q=q.nextpeer;null!==q&&(o.nextpeer=q.insideContours,null!==q.insideContours&&(q.insideContours.prevpeer=o),q.insideContours=o)}}else f[r]=k;else f[r]===b.DIR.OUTSIDE_EDGE||f[r]===b.DIR.INSIDE_EDGE?(k=0,d=f[r]===b.DIR.INSIDE_EDGE?e[r]:t[0]):(k=f[r],d=t[k]);for(q=p;null!==q;)q.index=a,q=q.nextpeer;return{cc:p,count:u}},debug:{drawContour:function(a,c){var d,e,f,g=a.getContext("2d"),h=c;for(g.strokeStyle="red",g.fillStyle="red",g.lineWidth=1,d=null!==h?h.insideContours:null;null!==h;){switch(null!==d?(e=d,d=d.nextpeer):(e=h,h=h.nextpeer,d=null!==h?h.insideContours:null),e.dir){case b.CONTOUR_DIR.CW_DIR:g.strokeStyle="red";break;case b.CONTOUR_DIR.CCW_DIR:g.strokeStyle="blue";break;case b.CONTOUR_DIR.UNKNOWN_DIR:g.strokeStyle="green"}f=e.firstVertex,g.beginPath(),g.moveTo(f.x,f.y);do f=f.next,g.lineTo(f.x,f.y);while(f!==e.firstVertex);g.stroke()}}}}}};return b}),d("skeletonizer",[],function(){"use strict";function a(stdlib, foreign, buffer) {"use asm";var images=new stdlib.Uint8Array(buffer),size=foreign.size|0,imul=stdlib.Math.imul;function erode(inImagePtr, outImagePtr) {inImagePtr=inImagePtr|0;outImagePtr=outImagePtr|0;var v=0,u=0,sum=0,yStart1=0,yStart2=0,xStart1=0,xStart2=0,offset=0;for ( v=1; (v|0)<((size - 1)|0); v=(v+1)|0) {offset=(offset+size)|0;for ( u=1; (u|0)<((size - 1)|0); u=(u+1)|0) {yStart1=(offset - size)|0;yStart2=(offset+size)|0;xStart1=(u - 1)|0;xStart2=(u+1)|0;sum=((images[(inImagePtr+yStart1+xStart1)|0]|0)+(images[(inImagePtr+yStart1+xStart2)|0]|0)+(images[(inImagePtr+offset+u)|0]|0)+(images[(inImagePtr+yStart2+xStart1)|0]|0)+(images[(inImagePtr+yStart2+xStart2)|0]|0))|0;if ((sum|0) == (5|0)) {images[(outImagePtr+offset+u)|0]=1;} else {images[(outImagePtr+offset+u)|0]=0;}}}return;}function subtract(aImagePtr, bImagePtr, outImagePtr) {aImagePtr=aImagePtr|0;bImagePtr=bImagePtr|0;outImagePtr=outImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(outImagePtr+length)|0]=((images[(aImagePtr+length)|0]|0) - (images[(bImagePtr+length)|0]|0))|0;}}function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) {aImagePtr=aImagePtr|0;bImagePtr=bImagePtr|0;outImagePtr=outImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(outImagePtr+length)|0]=((images[(aImagePtr+length)|0]|0)|(images[(bImagePtr+length)|0]|0))|0;}}function countNonZero(imagePtr) {imagePtr=imagePtr|0;var sum=0,length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;sum=((sum|0)+(images[(imagePtr+length)|0]|0))|0;}return (sum|0);}function init(imagePtr, value) {imagePtr=imagePtr|0;value=value|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(imagePtr+length)|0]=value;}}function dilate(inImagePtr, outImagePtr) {inImagePtr=inImagePtr|0;outImagePtr=outImagePtr|0;var v=0,u=0,sum=0,yStart1=0,yStart2=0,xStart1=0,xStart2=0,offset=0;for ( v=1; (v|0)<((size - 1)|0); v=(v+1)|0) {offset=(offset+size)|0;for ( u=1; (u|0)<((size - 1)|0); u=(u+1)|0) {yStart1=(offset - size)|0;yStart2=(offset+size)|0;xStart1=(u - 1)|0;xStart2=(u+1)|0;sum=((images[(inImagePtr+yStart1+xStart1)|0]|0)+(images[(inImagePtr+yStart1+xStart2)|0]|0)+(images[(inImagePtr+offset+u)|0]|0)+(images[(inImagePtr+yStart2+xStart1)|0]|0)+(images[(inImagePtr+yStart2+xStart2)|0]|0))|0;if ((sum|0)>(0|0)) {images[(outImagePtr+offset+u)|0]=1;} else {images[(outImagePtr+offset+u)|0]=0;}}}return;}function memcpy(srcImagePtr, dstImagePtr) {srcImagePtr=srcImagePtr|0;dstImagePtr=dstImagePtr|0;var length=0;length=imul(size, size)|0;while ((length|0)>0) {length=(length - 1)|0;images[(dstImagePtr+length)|0]=(images[(srcImagePtr+length)|0]|0);}}function zeroBorder(imagePtr) {imagePtr=imagePtr|0;var x=0,y=0;for ( x=0; (x|0)<((size - 1)|0); x=(x+1)|0) {images[(imagePtr+x)|0]=0;images[(imagePtr+y)|0]=0;y=((y+size) - 1)|0;images[(imagePtr+y)|0]=0;y=(y+1)|0;}for ( x=0; (x|0)<(size|0); x=(x+1)|0) {images[(imagePtr+y)|0]=0;y=(y+1)|0;}}function skeletonize() {var subImagePtr=0,erodedImagePtr=0,tempImagePtr=0,skelImagePtr=0,sum=0,done=0;erodedImagePtr=imul(size, size)|0;tempImagePtr=(erodedImagePtr+erodedImagePtr)|0;skelImagePtr=(tempImagePtr+erodedImagePtr)|0;init(skelImagePtr, 0);zeroBorder(subImagePtr);do {erode(subImagePtr, erodedImagePtr);dilate(erodedImagePtr, tempImagePtr);subtract(subImagePtr, tempImagePtr, tempImagePtr);bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);memcpy(erodedImagePtr, subImagePtr);sum=countNonZero(subImagePtr)|0;done=((sum|0) == 0|0);} while(!done);}return {skeletonize : skeletonize};}
+return a}),d("image_debug",[],function(){"use strict";return{drawRect:function(a,b,c,d){c.strokeStyle=d.color,c.fillStyle=d.color,c.lineWidth=1,c.beginPath(),c.strokeRect(a.x,a.y,b.x,b.y)},drawPath:function(a,b,c,d){c.strokeStyle=d.color,c.fillStyle=d.color,c.lineWidth=d.lineWidth,c.beginPath(),c.moveTo(a[0][b.x],a[0][b.y]);for(var e=1;eb&&(b+=180),b=(180-b)*Math.PI/180,f=i.create([Math.cos(b),-Math.sin(b),Math.sin(b),Math.cos(b)]),c=0;cd;d++)i.xVec2(f,e.box[d]);u.boxFromPatches.showTransformed&&g.drawPath(e.box,{x:0,y:1},G.ctx.binary,{color:"#99ff00",lineWidth:2})}for(c=0;cd;d++)e.box[d][0]n&&(n=e.box[d][0]),e.box[d][1]o&&(o=e.box[d][1]);for(h=[[l,m],[n,m],[n,o],[l,o]],u.boxFromPatches.showTransformedBox&&g.drawPath(h,{x:0,y:1},G.ctx.binary,{color:"#ff0000",lineWidth:2}),k=u.halfSample?2:1,f=i.inverse(f),d=0;4>d;d++)i.xVec2(f,h[d]);for(u.boxFromPatches.showBB&&g.drawPath(h,{x:0,y:1},G.ctx.binary,{color:"#ff0000",lineWidth:2}),d=0;4>d;d++)j.scale(h[d],k);return h}function m(){b.otsuThreshold(v,C),C.zeroBorder(),u.showCanvas&&C.show(G.dom.binary,255)}function n(){var a,b,d,e,h,i,j,k,l=[];for(a=0;ab;b++)d.push(0);for(c=A.data.length;c--;)A.data[c]>0&&d[A.data[c]-1]++;return d=d.map(function(a,b){return{val:a,label:b+1}}),d.sort(function(a,b){return b.val-a.val}),e=d.filter(function(a){return a.val>=5})}function p(a,c){var d,e,f,h,i,j=[],k=[],m=[0,1,1],n=[0,0,0];for(d=0;d=2){for(e=0;em&&k.push(a[e]);if(k.length>=2){for(i=k.length,g=q(k),f=0,e=0;e1&&g.length>=k.length/4*3&&g.length>a.length/4&&(f/=g.length,h={index:b[1]*H.x+b[0],pos:{x:c,y:d},box:[j.create([c,d]),j.create([c+x.size.x,d]),j.create([c+x.size.x,d+x.size.y]),j.create([c,d+x.size.y])],moments:g,rad:f,vec:j.create([Math.cos(f),Math.sin(f)])},l.push(h))}}return l}function t(a){function c(){var a;for(a=0;al&&e(h))):A.data[h]=Number.MAX_VALUE}var h,i,k=0,l=.95,m=0,n=[0,1,1],o=[0,0,0];for(f.init(z.data,0),f.init(A.data,0),f.init(B.data,null),h=0;h0&&A.data[h]<=k&&(i=B.data[h],n[0]=A.data[h]/(k+1)*360,b.hsv2rgb(n,o),g.drawRect(i.pos,x.size,G.ctx.binary,{color:"rgb("+o.join(",")+")",lineWidth:2}));return k}var u,v,w,x,y,z,A,B,C,D,E,F,G={ctx:{binary:null},dom:{binary:null}},H={x:0,y:0},I=this;return{init:function(a,b){u=b,E=a,h(),k()},locate:function(){var a,c,d;if(u.halfSample&&b.halfSample(E,v),m(),a=n(),a.lengthe?null:(c=o(e),0===c.length?null:d=p(c,e))},checkImageConstraints:function(a,c){var d,e,f,g=a.getWidth(),h=a.getHeight(),i=c.halfSample?.5:1;if(a.getConfig().area&&(f=b.computeImageArea(g,h,a.getConfig().area),a.setTopRight({x:f.sx,y:f.sy}),a.setCanvasSize({x:g,y:h}),g=f.sw,h=f.sh),e={x:Math.floor(g*i),y:Math.floor(h*i)},d=b.calculatePatchSize(c.patchSize,e),console.log("Patch-Size: "+JSON.stringify(d)),a.setWidth(Math.floor(Math.floor(e.x/d.x)*(1/i)*d.x)),a.setHeight(Math.floor(Math.floor(e.y/d.y)*(1/i)*d.y)),a.getWidth()%d.x===0&&a.getHeight()%d.y===0)return!0;throw new Error("Image dimensions do not comply with the current settings: Width ("+g+" )and height ("+h+") must a multiple of "+d.x)}}}),d("bresenham",["cv_utils","image_wrapper"],function(a,b){"use strict";var c={},d={DIR:{UP:1,DOWN:-1}};return c.getBarcodeLine=function(a,b,c){function d(a,b){l=s[b*t+a],u+=l,v=v>l?l:v,w=l>w?l:w,r.push(l)}var e,f,g,h,i,j,k,l,m=0|b.x,n=0|b.y,o=0|c.x,p=0|c.y,q=Math.abs(p-n)>Math.abs(o-m),r=[],s=a.data,t=a.size.x,u=0,v=255,w=0;for(q&&(j=m,m=n,n=j,j=o,o=p,p=j),m>o&&(j=m,m=o,o=j,j=n,n=p,p=j),e=o-m,f=Math.abs(p-n),g=e/2|0,i=n,h=p>n?1:-1,k=m;o>k;k++)q?d(i,k):d(k,i),g-=f,0>g&&(i+=h,g+=e);return{line:r,min:v,max:w}},c.toOtsuBinaryLine=function(c){var d=c.line,e=new b({x:d.length-1,y:1},d),f=a.determineOtsuThreshold(e,5);return d=a.sharpenLine(d),a.thresholdImage(e,f),{line:d,threshold:f}},c.toBinaryLine=function(a){var b,c,e,f,g,h,i=a.min,j=a.max,k=a.line,l=i+(j-i)/2,m=[],n=(j-i)/12,o=-n;for(e=k[0]>l?d.DIR.UP:d.DIR.DOWN,m.push({pos:0,val:k[0]}),g=0;gb+c&&k[g+1]<1.5*l?d.DIR.DOWN:b+c>n&&k[g+1]>.5*l?d.DIR.UP:e,e!==f&&(m.push({pos:g,val:k[g]}),e=f);for(m.push({pos:k.length,val:k[k.length-1]}),h=m[0].pos;hl?0:1;for(g=1;gm[g].val?m[g].val+(m[g+1].val-m[g].val)/3*2|0:m[g+1].val+(m[g].val-m[g+1].val)/3|0,h=m[g].pos;hn?0:1;return{line:k,threshold:n}},c.debug={printFrequency:function(a,b){var c,d=b.getContext("2d");for(b.width=a.length,b.height=256,d.beginPath(),d.strokeStyle="blue",c=0;cg)return Number.MAX_VALUE;d+=e}return d/f},a.prototype._nextSet=function(a,b){var c;for(b=b||0,c=b;c1)for(c=0;cd?(j.start=c-g,j.end=c,j.counter=e,j):null;i++,e[i]=1,h=!h}}else for(e.push(0),c=g;ca?0:a,d=a;b>d;d++)if(this._row[d]!==c)return!1;return!0},a.prototype._fillCounters=function(a,b,c){var d,e=this,f=0,g=[];for(c="undefined"!=typeof c?c:!0,a="undefined"!=typeof a?a:e._nextUnset(e._row),b=b||e._row.length,g[f]=0,d=a;b>d;d++)e._row[d]^c?g[f]++:(f++,g[f]=1,c=!c);return g},Object.defineProperty(a.prototype,"FORMAT",{value:"unknown",writeable:!1}),a.DIRECTION={FORWARD:1,REVERSE:-1},a.Exception={StartNotFoundException:"Start-Info was not found!",CodeNotFoundException:"Code could not be found!",PatternNotFoundException:"Pattern could not be found!"},a.CONFIG_KEYS={},a}),d("code_128_reader",["./barcode_reader"],function(a){"use strict";function b(){a.call(this)}var c={CODE_SHIFT:{value:98},CODE_C:{value:99},CODE_B:{value:100},CODE_A:{value:101},START_CODE_A:{value:103},START_CODE_B:{value:104},START_CODE_C:{value:105},STOP_CODE:{value:106},MODULO:{value:11},CODE_PATTERN:{value:[[2,1,2,2,2,2],[2,2,2,1,2,2],[2,2,2,2,2,1],[1,2,1,2,2,3],[1,2,1,3,2,2],[1,3,1,2,2,2],[1,2,2,2,1,3],[1,2,2,3,1,2],[1,3,2,2,1,2],[2,2,1,2,1,3],[2,2,1,3,1,2],[2,3,1,2,1,2],[1,1,2,2,3,2],[1,2,2,1,3,2],[1,2,2,2,3,1],[1,1,3,2,2,2],[1,2,3,1,2,2],[1,2,3,2,2,1],[2,2,3,2,1,1],[2,2,1,1,3,2],[2,2,1,2,3,1],[2,1,3,2,1,2],[2,2,3,1,1,2],[3,1,2,1,3,1],[3,1,1,2,2,2],[3,2,1,1,2,2],[3,2,1,2,2,1],[3,1,2,2,1,2],[3,2,2,1,1,2],[3,2,2,2,1,1],[2,1,2,1,2,3],[2,1,2,3,2,1],[2,3,2,1,2,1],[1,1,1,3,2,3],[1,3,1,1,2,3],[1,3,1,3,2,1],[1,1,2,3,1,3],[1,3,2,1,1,3],[1,3,2,3,1,1],[2,1,1,3,1,3],[2,3,1,1,1,3],[2,3,1,3,1,1],[1,1,2,1,3,3],[1,1,2,3,3,1],[1,3,2,1,3,1],[1,1,3,1,2,3],[1,1,3,3,2,1],[1,3,3,1,2,1],[3,1,3,1,2,1],[2,1,1,3,3,1],[2,3,1,1,3,1],[2,1,3,1,1,3],[2,1,3,3,1,1],[2,1,3,1,3,1],[3,1,1,1,2,3],[3,1,1,3,2,1],[3,3,1,1,2,1],[3,1,2,1,1,3],[3,1,2,3,1,1],[3,3,2,1,1,1],[3,1,4,1,1,1],[2,2,1,4,1,1],[4,3,1,1,1,1],[1,1,1,2,2,4],[1,1,1,4,2,2],[1,2,1,1,2,4],[1,2,1,4,2,1],[1,4,1,1,2,2],[1,4,1,2,2,1],[1,1,2,2,1,4],[1,1,2,4,1,2],[1,2,2,1,1,4],[1,2,2,4,1,1],[1,4,2,1,1,2],[1,4,2,2,1,1],[2,4,1,2,1,1],[2,2,1,1,1,4],[4,1,3,1,1,1],[2,4,1,1,1,2],[1,3,4,1,1,1],[1,1,1,2,4,2],[1,2,1,1,4,2],[1,2,1,2,4,1],[1,1,4,2,1,2],[1,2,4,1,1,2],[1,2,4,2,1,1],[4,1,1,2,1,2],[4,2,1,1,1,2],[4,2,1,2,1,1],[2,1,2,1,4,1],[2,1,4,1,2,1],[4,1,2,1,2,1],[1,1,1,1,4,3],[1,1,1,3,4,1],[1,3,1,1,4,1],[1,1,4,1,1,3],[1,1,4,3,1,1],[4,1,1,1,1,3],[4,1,1,3,1,1],[1,1,3,1,4,1],[1,1,4,1,3,1],[3,1,1,1,4,1],[4,1,1,1,3,1],[2,1,1,4,1,2],[2,1,1,2,1,4],[2,1,1,2,3,2],[2,3,3,1,1,1,2]]},SINGLE_CODE_ERROR:{value:1},AVG_CODE_ERROR:{value:.5},FORMAT:{value:"code_128",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decodeCode=function(a){var b,c,d,e,f=[0,0,0,0,0,0],g=this,h=a,i=!g._row[h],j=0,k={error:Number.MAX_VALUE,code:-1,start:a,end:a};for(b=h;bd;d++)g[d]=g[d+2];g[4]=0,g[5]=0,k--}else k++;g[k]=1,j=!j}return null},b.prototype._decode=function(){var a,b,c,d=this,e=d._findStart(),f=null,g=!1,h=[],i=0,j=0,k=[],l=[],m=!1;if(null===e)return null;switch(f={code:e.code,start:e.start,end:e.end},l.push(f),j=f.code,f.code){case d.START_CODE_A:a=d.CODE_A;break;case d.START_CODE_B:a=d.CODE_B;break;case d.START_CODE_C:a=d.CODE_C;break;default:return null}for(;!g;){if(b=m,m=!1,f=d._decodeCode(f.end),null!==f)switch(f.code!==d.STOP_CODE&&(k.push(f.code),i++,j+=i*f.code),l.push(f),a){case d.CODE_A:if(f.code<64)h.push(String.fromCharCode(32+f.code));else if(f.code<96)h.push(String.fromCharCode(f.code-64));else switch(f.code){case d.CODE_SHIFT:m=!0,a=d.CODE_B;break;case d.CODE_B:a=d.CODE_B;break;case d.CODE_C:a=d.CODE_C;break;case d.STOP_CODE:g=!0}break;case d.CODE_B:if(f.code<96)h.push(String.fromCharCode(32+f.code));else switch(f.code!=d.STOP_CODE&&(c=!1),f.code){case d.CODE_SHIFT:m=!0,a=d.CODE_A;break;case d.CODE_A:a=d.CODE_A;break;case d.CODE_C:a=d.CODE_C;break;case d.STOP_CODE:g=!0}break;case d.CODE_C:switch(f.code<100&&h.push(f.code<10?"0"+f.code:f.code),f.code){case d.CODE_A:a=d.CODE_A;break;case d.CODE_B:a=d.CODE_B;break;case d.STOP_CODE:g=!0}}else g=!0;b&&(a=a==d.CODE_A?d.CODE_B:d.CODE_A)}return null===f?null:(f.end=d._nextUnset(d._row,f.end),d._verifyTrailingWhitespace(f)?(j-=i*k[k.length-1],j%103!=k[k.length-1]?null:h.length?(h.splice(h.length-1,1),{code:h.join(""),start:e.start,end:f.end,codeset:a,startInfo:e,decodedCodes:l,endInfo:f}):null):null)},a.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start)/2,bd;d++)e=h._matchPattern(f,h.CODE_PATTERN[d]),eh.AVG_CODE_ERROR?null:l}}else k++;g[k]=1,j=!j}return null},b.prototype._findPattern=function(a,b,c,d,e){var f,g,h,i,j,k=[],l=this,m=0,n={error:Number.MAX_VALUE,code:-1,start:0,end:0};for(b||(b=l._nextSet(l._row)),void 0===c&&(c=!1),void 0===d&&(d=!0),void 0===e&&(e=l.AVG_CODE_ERROR),f=0;fg))return n.error=g,n.start=f-i,n.end=f,n;if(!d)return null;for(h=0;h=0&&c._matchRange(a,b.start,0))return b;d=b.end,b=null}},b.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start),bd;d++){if(a=f._decodeCode(a.end),!a)return null;a.code>=f.CODE_G_START?(a.code=a.code-f.CODE_G_START,g|=1<<5-d):g|=0<<5-d,b.push(a.code),c.push(a)}if(e=f._calculateFirstDigit(g),null===e)return null;if(b.unshift(e),a=f._findPattern(f.MIDDLE_PATTERN,a.end,!0,!1),null===a)return null;for(c.push(a),d=0;6>d;d++){if(a=f._decodeCode(a.end,f.CODE_G_START),!a)return null;c.push(a),b.push(a.code)}return a},b.prototype._decode=function(){var a,b,c=this,d=[],e=[];return(a=c._findStart())?(b={code:a.code,start:a.start,end:a.end},e.push(b),(b=c._decodePayload(b,d,e))&&(b=c._findEnd(b.end,!1))?(e.push(b),c._checksum(d)?{code:d.join(""),start:a.start,end:b.end,codeset:"",startInfo:a,decodedCodes:e}:null):null):null},b.prototype._checksum=function(a){var b,c=0;for(b=a.length-2;b>=0;b-=2)c+=a[b];for(c*=3,b=a.length-1;b>=0;b-=2)c+=a[b];return c%10===0},b}),d("code_39_reader",["./barcode_reader","./array_helper"],function(a,b){"use strict";function c(){a.call(this)}var d={ALPHABETH_STRING:{value:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"},ALPHABET:{value:[48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,45,46,32,42,36,47,43,37]},CHARACTER_ENCODINGS:{value:[52,289,97,352,49,304,112,37,292,100,265,73,328,25,280,88,13,268,76,28,259,67,322,19,274,82,7,262,70,22,385,193,448,145,400,208,133,388,196,148,168,162,138,42]},ASTERISK:{value:148},FORMAT:{value:"code_39",writeable:!1}};return c.prototype=Object.create(a.prototype,d),c.prototype.constructor=c,c.prototype._toCounters=function(a,c){var d,e=this,f=c.length,g=e._row.length,h=!e._row[a],i=0;for(b.init(c,0),d=a;g>d;d++)if(e._row[d]^h)c[i]++;else{if(i++,i===f)break;c[i]=1,h=!h}return c},c.prototype._decode=function(){var a,c,d,e,f=this,g=[0,0,0,0,0,0,0,0,0],h=[],i=f._findStart();if(!i)return null;e=f._nextSet(f._row,i.end);do{if(g=f._toCounters(e,g),d=f._toPattern(g),0>d)return null;if(a=f._patternToChar(d),0>a)return null;h.push(a),c=e,e+=b.sum(g),e=f._nextSet(f._row,e)}while("*"!==a);return h.pop(),h.length&&f._verifyTrailingWhitespace(c,e,g)?{code:h.join(""),start:i.start,end:e,startInfo:i,decodedCodes:h}:null},c.prototype._verifyTrailingWhitespace=function(a,c,d){var e,f=b.sum(d);return e=c-a-f,3*e>=f?!0:!1},c.prototype._patternToChar=function(a){var b,c=this;for(b=0;bb&&(d=a[c]);return d},c.prototype._toPattern=function(a){for(var b,c,d=a.length,e=0,f=d,g=0,h=this;f>3;){for(e=h._findNextWidth(a,e),f=0,b=0,c=0;d>c;c++)a[c]>e&&(b|=1<c&&f>0;c++)if(a[c]>e&&(f--,2*a[c]>=g))return-1;return b}}return-1},c.prototype._findStart=function(){var a,b,c,d=this,e=d._nextSet(d._row),f=e,g=[0,0,0,0,0,0,0,0,0],h=0,i=!1;for(a=e;ab;b++)g[b]=g[b+2];g[7]=0,g[8]=0,h--}else h++;g[h]=1,i=!i}return null},c}),d("code_39_vin_reader",["./code_39_reader"],function(a){"use strict";function b(){a.call(this)}var c={IOQ:/[IOQ]/g,AZ09:/[A-Z0-9]{17}/};return b.prototype=Object.create(a.prototype),b.prototype.constructor=b,b.prototype._decode=function(){var b=a.prototype._decode.apply(this);if(!b)return null;var d=b.code;if(d)return d=d.replace(c.IOQ,""),d.match(c.AZ09)?this._checkChecksum(d)?(b.code=d,b):null:(console.log("Failed AZ09 pattern code:",d),null)},b.prototype._checkChecksum=function(a){return!!a},b}),d("codabar_reader",["./barcode_reader"],function(a){"use strict";function b(){a.call(this),this._counters=[]}var c={ALPHABETH_STRING:{value:"0123456789-$:/.+ABCD"},ALPHABET:{value:[48,49,50,51,52,53,54,55,56,57,45,36,58,47,46,43,65,66,67,68]},CHARACTER_ENCODINGS:{value:[3,6,9,96,18,66,33,36,48,72,12,24,69,81,84,21,26,41,11,14]},START_END:{value:[26,41,11,14]},MIN_ENCODED_CHARS:{value:4},MAX_ACCEPTABLE:{value:2},PADDING:{value:1.5},FORMAT:{value:"codabar",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decode=function(){var a,b,c,d,e,f=this,g=[];if(this._counters=f._fillCounters(),a=f._findStart(),!a)return null;d=a.startCounter;do{if(c=f._toPattern(d),0>c)return null;if(b=f._patternToChar(c),0>b)return null;if(g.push(b),d+=8,g.length>1&&f._isStartEnd(c))break}while(df._counters.length?f._counters.length:d,e=a.start+f._sumCounters(a.startCounter,d-8),{code:g.join(""),start:a.start,end:e,startInfo:a,decodedCodes:g}):null},b.prototype._verifyWhitespace=function(a,b){return(0>=a-1||this._counters[a-1]>=this._calculatePatternLength(a)/2)&&(b+8>=this._counters.length||this._counters[b+7]>=this._calculatePatternLength(b)/2)?!0:!1},b.prototype._calculatePatternLength=function(a){var b,c=0;for(b=a;a+7>b;b++)c+=this._counters[b];return c},b.prototype._thresholdResultPattern=function(a,b){var c,d,e,f,g,h=this,i={space:{narrow:{size:0,counts:0,min:0,max:Number.MAX_VALUE},wide:{size:0,counts:0,min:0,max:Number.MAX_VALUE}},bar:{narrow:{size:0,counts:0,min:0,max:Number.MAX_VALUE},wide:{size:0,counts:0,min:0,max:Number.MAX_VALUE}}},j=b;for(e=0;e=0;f--)c=2===(1&f)?i.bar:i.space,d=1===(1&g)?c.wide:c.narrow,d.size+=h._counters[j+f],d.counts++,g>>=1;j+=8}return["space","bar"].forEach(function(a){var b=i[a];b.wide.min=Math.floor((b.narrow.size/b.narrow.counts+b.wide.size/b.wide.counts)/2),b.narrow.max=Math.ceil(b.wide.min),b.wide.max=Math.ceil((b.wide.size*h.MAX_ACCEPTABLE+h.PADDING)/b.wide.counts)}),i},b.prototype._charToPattern=function(a){var b,c=this,d=a.charCodeAt(0);for(b=0;b=0;d--){if(e=0===(1&d)?j.bar:j.space,f=1===(1&h)?e.wide:e.narrow,g=i._counters[k+d],gf.max)return!1;h>>=1}k+=8}return!0},b.prototype._patternToChar=function(a){var b,c=this;for(b=0;bc;c+=2)d=this._counters[c],d>f&&(f=d),e>d&&(e=d);return(e+f)/2|0},b.prototype._toPattern=function(a){var b,c,d,e,f=7,g=a+f,h=1<this._counters.length)return-1;for(b=this._computeAlternatingThreshold(a,g),c=this._computeAlternatingThreshold(a+1,g),d=0;f>d;d++)e=0===(1&d)?b:c,this._counters[a+d]>e&&(i|=h),h>>=1;return i},b.prototype._isStartEnd=function(a){var b;for(b=0;bc;c++)d+=this._counters[c];return d},b.prototype._findStart=function(){var a,b,c,d=this,e=d._nextUnset(d._row);for(a=1;ad;d++){if(a=e._decodeCode(a.end,e.CODE_G_START),!a)return null;b.push(a.code),c.push(a)}if(a=e._findPattern(e.MIDDLE_PATTERN,a.end,!0,!1),null===a)return null;for(c.push(a),d=0;4>d;d++){if(a=e._decodeCode(a.end,e.CODE_G_START),!a)return null;c.push(a),b.push(a.code)}return a},b}),d("upc_e_reader",["./ean_reader"],function(a){"use strict";function b(){a.call(this)}var c={CODE_FREQUENCY:{value:[[56,52,50,49,44,38,35,42,41,37],[7,11,13,14,19,25,28,21,22,26]]},STOP_PATTERN:{value:[1/6*7,1/6*7,1/6*7,1/6*7,1/6*7,1/6*7]},FORMAT:{value:"upc_e",writeable:!1}};return b.prototype=Object.create(a.prototype,c),b.prototype.constructor=b,b.prototype._decodePayload=function(a,b,c){var d,e=this,f=0;for(d=0;6>d;d++){if(a=e._decodeCode(a.end),!a)return null;a.code>=e.CODE_G_START&&(a.code=a.code-e.CODE_G_START,f|=1<<5-d),b.push(a.code),c.push(a)}return e._determineParity(f,b)?a:null},b.prototype._determineParity=function(a,b){var c,d,e=this;for(d=0;d=c?b.concat(a.slice(1,3)).concat([c,0,0,0,0]).concat(a.slice(3,6)):3===c?b.concat(a.slice(1,4)).concat([0,0,0,0,0]).concat(a.slice(4,6)):4===c?b.concat(a.slice(1,5)).concat([0,0,0,0,0,a[5]]):b.concat(a.slice(1,6)).concat([0,0,0,0,c]),b.push(a[a.length-1]),b},b.prototype._checksum=function(b){return a.prototype._checksum.call(this,this._convertToUPCA(b))},b.prototype._findEnd=function(b,c){return c=!0,a.prototype._findEnd.call(this,b,c)},b.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start)/2,bf))return m.error=f,m.start=e-h,m.end=e,m;if(!d)return null;for(g=0;g=0&&c._matchRange(a,b.start,0))return b;d=b.end,b=null}},c.prototype._verifyTrailingWhitespace=function(a){var b,c=this;return b=a.end+(a.end-a.start)/2,bg;){for(d=0;5>d;d++)i[0][d]=a[g]*this.barSpaceRatio[0],i[1][d]=a[g+1]*this.barSpaceRatio[1],g+=2;if(e=f._decodePair(i),!e)return null;for(d=0;d1&&(!d.inImageWithBorder(a[0],0)||!d.inImageWithBorder(a[1],0));)c-=Math.ceil(c/2),e(-c);return a}function i(a){return[{x:(a[1][0]-a[0][0])/2+a[0][0],y:(a[1][1]-a[0][1])/2+a[0][1]},{x:(a[3][0]-a[2][0])/2+a[2][0],y:(a[3][1]-a[2][1])/2+a[2][1]}]}function j(e){var f,g=null,h=a.getBarcodeLine(d,e[0],e[1]);for(c.showFrequency&&(b.drawPath(e,{x:"x",y:"y"},o.ctx.overlay,{color:"red",lineWidth:3}),a.debug.printFrequency(h.line,o.dom.frequency)),a.toBinaryLine(h),c.showPattern&&a.debug.printPattern(h.line,o.dom.pattern),f=0;fd&&null===i;d++)e=g/h*d*(d%2===0?-1:1),f={y:e*k,x:e*l},b[0].y+=f.x,b[0].x-=f.y,b[1].y+=f.x,b[1].x-=f.y,i=j(b);return i}function m(a){return Math.sqrt(Math.pow(Math.abs(a[1].y-a[0].y),2)+Math.pow(Math.abs(a[1].x-a[0].x),2))}function n(a){var d,e,f,g,l=o.ctx.overlay;return c.drawBoundingBox&&l&&b.drawPath(a,{x:0,y:1},l,{color:"blue",lineWidth:2}),d=i(a),g=m(d),e=Math.atan2(d[1].y-d[0].y,d[1].x-d[0].x),d=h(d,e,Math.floor(.1*g)),null===d?null:(f=j(d),null===f&&(f=k(a,d,e)),null===f?null:(f&&c.drawScanline&&l&&b.drawPath(d,{x:"x",y:"y"},l,{color:"red",lineWidth:3}),{codeResult:f.codeResult,line:d,angle:e,pattern:f.barcodeLine.line,threshold:f.barcodeLine.threshold}))}var o={ctx:{frequency:null,pattern:null,overlay:null},dom:{frequency:null,pattern:null,overlay:null}},p=[];return e(),f(),g(),{decodeFromBoundingBox:function(a){return n(a)},decodeFromBoundingBoxes:function(a){var b,c;for(b=0;b0?a.videoWidth>0&&a.videoHeight>0?(console.log(a.videoWidth+"px x "+a.videoHeight+"px"),b()):window.setTimeout(c,500):b("Unable to play video stream. Is webcam working?"),d--}var d=10;c()}function d(a,d,e){b(a,function(a){d.src=a,h&&d.removeEventListener("loadeddata",h,!1),h=c.bind(null,d,e),d.addEventListener("loadeddata",h,!1),d.play()},function(a){e(a)})}function e(b,c){var d={audio:!1,video:!0},e=a.mergeObjects({width:640,height:480,minAspectRatio:0,maxAspectRatio:100,facing:"environment"},b);return"undefined"==typeof MediaStreamTrack||"undefined"==typeof MediaStreamTrack.getSources?(d.video={mediaSource:"camera",width:{min:e.width,max:e.width},height:{min:e.height,max:e.height},require:["width","height"]},c(d)):void MediaStreamTrack.getSources(function(a){for(var b,f=0;f!=a.length;++f){var g=a[f];"video"==g.kind&&g.facing==e.facing&&(b=g.id)}return d.video={mandatory:{minWidth:e.width,minHeight:e.height,minAspectRatio:e.minAspectRatio,maxAspectRatio:e.maxAspectRatio},optional:[{sourceId:b}]},c(d)})}function f(a,b,c){e(b,function(b){d(b,a,c)})}var g,h;return{request:function(a,b,c){f(a,b,c)},release:function(){var a=g&&g.getVideoTracks();a.length&&a[0].stop(),g=null}}}),d("result_collector",["image_debug"],function(a){"use strict";function b(a,b){return b?b.some(function(b){return Object.keys(b).every(function(c){return b[c]===a[c]})}):!1}function c(a,b){return"function"==typeof b?b(a):!0}return{create:function(d){function e(a){return i&&a&&!b(a,d.blacklist)&&c(a,d.filter)}var f=document.createElement("canvas"),g=f.getContext("2d"),h=[],i=d.capacity||20,j=d.capture===!0;return{addResult:function(b,c,d){var k={};e(d)&&(i--,k.codeResult=d,j&&(f.width=c.x,f.height=c.y,a.drawImage(b,c,g),k.frame=f.toDataURL()),h.push(k))},getResults:function(){return h}}}}}),d("quagga",["input_stream","image_wrapper","barcode_locator","barcode_decoder","frame_grabber","html_utils","config","events","camera_access","image_debug","result_collector"],function(b,c,d,e,f,g,h,i,k,l,m){"use strict";function n(a){t(a),K=e.create(h.decoder,I)}function o(){if("undefined"!=typeof document)for(var a=[{node:document.querySelector("div[data-controls]"),prop:h.controls},{node:M.dom.overlay,prop:h.visual.show}],b=0;b0?A(function(){console.log("Workers created"),r(a)}):(n(),r(a))}function r(a){F.play(),a()}function s(){if("undefined"!=typeof document){var a=document.querySelector("#interactive.viewport");if(M.dom.image=document.querySelector("canvas.imgBuffer"),M.dom.image||(M.dom.image=document.createElement("canvas"),M.dom.image.className="imgBuffer",a&&"ImageStream"==h.inputStream.type&&a.appendChild(M.dom.image)),M.ctx.image=M.dom.image.getContext("2d"),M.dom.image.width=F.getCanvasSize().x,M.dom.image.height=F.getCanvasSize().y,M.dom.overlay=document.querySelector("canvas.drawingBuffer"),!M.dom.overlay){M.dom.overlay=document.createElement("canvas"),M.dom.overlay.className="drawingBuffer",a&&a.appendChild(M.dom.overlay);var b=document.createElement("br");b.setAttribute("clear","all"),a&&a.appendChild(b)}M.ctx.overlay=M.dom.overlay.getContext("2d"),M.dom.overlay.width=F.getCanvasSize().x,M.dom.overlay.height=F.getCanvasSize().y}}function t(a){I=a?a:new c({x:F.getWidth(),y:F.getHeight()}),console.log(I.size),J=[j.create([0,0]),j.create([0,I.size.y]),j.create([I.size.x,I.size.y]),j.create([I.size.x,0])],d.init(I,h.locator)}function u(){return h.locate?d.locate():[[j.create(J[0]),j.create(J[1]),j.create(J[2]),j.create(J[3])]]}function v(a){function b(a){for(var b=a.length;b--;)a[b][0]+=f,a[b][1]+=g}function c(a){a[0].x+=f,a[0].y+=g,a[1].x+=f,a[1].y+=g}var d,e=F.getTopRight(),f=e.x,g=e.y;if(a&&(0!==f||0!==g)&&(a.line&&2===a.line.length&&c(a.line),a.boxes&&a.boxes.length>0))for(d=0;d0){if(a=N.filter(function(a){return!a.busy})[0],!a)return;G.attachData(a.imageData)}else G.attachData(I.data);G.grab()&&(a?(a.busy=!0,a.worker.postMessage({cmd:"process",imageData:a.imageData},[a.imageData.buffer])):x())}else x()}function z(){H=!1,function a(){H||(y(),O&&"LiveStream"==h.inputStream.type&&window.requestAnimFrame(a))}()}function A(a){function b(b){N.push(b),N.length>=h.numOfWorkers&&a()}var c;for(N=[],c=0;c0&&N.forEach(function(b){b.worker.postMessage({cmd:"setReaders",readers:a})})}var F,G,H,I,J,K,L,M={ctx:{image:null,overlay:null},dom:{image:null,overlay:null}},N=[],O=!0;return{init:function(a,b,c){return h=g.mergeObjects(h,a),c?(O=!1,n(c),b()):void p(b)},start:function(){z()},stop:function(){H=!0,N.forEach(function(a){a.worker.terminate(),console.log("Worker terminated!")}),N.length=0,"LiveStream"===h.inputStream.type&&(k.release(),F.clearEventHandlers())},pause:function(){H=!0},onDetected:function(a){i.subscribe("detected",a)},onProcessed:function(a){i.subscribe("processed",a)},setReaders:function(a){E(a)},registerResultCollector:function(a){a&&"function"==typeof a.addResult&&(L=a)},canvas:M,decodeSingle:function(a,b){a=g.mergeObjects({inputStream:{type:"ImageStream",sequence:!1,size:800,src:a.src},numOfWorkers:1,locator:{halfSample:!1}},a),this.init(a,function(){i.once("processed",function(a){H=!0,b.call(null,a)},!0),z()})},ImageWrapper:c,ImageDebug:l,ResultCollector:m}}),c("quagga")});
\ No newline at end of file
diff --git a/example/file_input_require.js b/example/file_input_require.js
index 91569d1c..cf4655d9 100644
--- a/example/file_input_require.js
+++ b/example/file_input_require.js
@@ -116,12 +116,13 @@ define(['quagga'], function(Quagga) {
singleChannel: false
},
locator: {
- patchSize: "medium",
- halfSample: true
+ patchSize: "large",
+ halfSample: true,
+ showCanvas: true
},
numOfWorkers: 0,
decoder: {
- readers: ["code_128_reader"],
+ readers: ["i2of5_reader"],
showFrequency: true,
showPattern: true
},
diff --git a/example/live_w_locator.html b/example/live_w_locator.html
index 72c8d19e..739c5050 100644
--- a/example/live_w_locator.html
+++ b/example/live_w_locator.html
@@ -41,6 +41,7 @@ The user's camera
+