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

+