Include source-files

pull/5/head
Christoph Oberhofer 11 years ago
parent cfec9f9c11
commit c84044aeb0

2
.gitignore vendored

@ -0,0 +1,2 @@
.sass-cache/
node_modules/

@ -0,0 +1,67 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg : grunt.file.readJSON('package.json'),
uglify : {
options : {
banner : '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build : {
src : 'dist/<%= pkg.name %>.js',
dest : 'dist/<%= pkg.name %>.min.js'
}
},
jshint : {
all : ['Gruntfile.js', 'src/*.js']
},
requirejs : {
compile : {
options : {
almond : true,
wrap : {
startFile : 'build/start.frag',
endFile : 'build/end.frag'
},
"baseUrl" : "src",
"name" : "quagga",
"out" : "dist/quagga.js",
"include" : ['quagga'],
"optimize" : "none",
"findNestedDependencies" : true,
"skipSemiColonInsertion" : true,
"shim" : {
"typedefs" : {
"deps" : [],
"exports" : "typedefs"
},
"glMatrix" : {
"deps" : ["typedefs"],
"exports" : "glMatrix"
},
"glMatrixAddon" : {
"deps" : ["glMatrix"],
"exports" : "glMatrixAddon"
}
},
"paths" : {
"typedefs" : "typedefs",
"glMatrix" : "vendor/glMatrix",
"glMatrixAddon" : "glMatrixAddon"
}
}
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-requirejs');
// Default task(s).
grunt.registerTask('default', ['jshint', 'requirejs', 'uglify']);
};

@ -17,12 +17,10 @@ In order to take full advantage of quaggaJS, the browser needs to support the `g
In cases where real-time decoding is not needed, or the platform does not support `getUserMedia` QuaggaJS is also capable of decoding image-files. In cases where real-time decoding is not needed, or the platform does not support `getUserMedia` QuaggaJS is also capable of decoding image-files.
## Installation
Just clone the repository and include `quagga.min.js` in your project. The code uses [requirejs][requirejs] for dependency management.
## Usage ## Usage
Just download the pre-built version `quagga.min.js` from the `dist` folder and include in your project.
The library exposes the following API The library exposes the following API
### quagga.init(config, callback) ### quagga.init(config, callback)
@ -59,6 +57,10 @@ quagga.decodeSingle({
}); });
``` ```
## Build it yourself
In case you want to build the library directly from the source code, just run `npm install` follewed by the `grunt` task. This places two files in the `dist` folder, of which `quagga.min.js` is the minified version.
[zxing_github]: https://github.com/zxing/zxing [zxing_github]: https://github.com/zxing/zxing
[teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png [teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png
[teaser_right]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-detected.png [teaser_right]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-detected.png

@ -0,0 +1,2 @@
return require('quagga');
}));

@ -0,0 +1,13 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
//Allow using this built library as an AMD module
//in another project. That other project will only
//see this AMD call, not the internal modules in
//the closure below.
define([], factory);
} else {
//Browser globals case. Just assign the
//result to a property on the global.
root.Quagga = factory();
}
}(this, function () {

@ -0,0 +1,27 @@
# Require any additional compass plugins here.
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "css"
sass_dir = "sass"
images_dir = "img"
javascripts_dir = "js"
fonts_dir="fonts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# If you prefer the indented syntax, you might want to regenerate this
# project again passing --syntax sass, or you can uncomment this:
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass

7203
dist/quagga.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,169 @@
Barcode Reader
==============
Barcode Reader is a library written in JavaScript capable of decoding common barcodes (e.g.: Code128 and EAN13) directly in the browser. It can handle various sources of images such as file-input for a single image or preferrably a camera stream (via `getUserMedia`) for real-time decoding.
# How does it work?
For those of you interested in the inner workings of this implementation, please keep on reading.
As already depicted in the image above, this implementation makes use of two subsequent stages, first locating (blue box) and second decoding (red line) the barcode.
## Barcode Locator
The main purpose of the barcode locator is finding a pattern within the source image which looks like a barcode. A barcode is typically characterized by its black bars and white gaps in between. The overall size of the barcode may depend on the amount of information encoded (Code128) or be fixed in width (EAN13). When searching for a barcode in an image we are looking for:
- _lines_, which are
- _close_ _to_ each other
- have a similar _angle_
The process of locating such a barcode is loosely based on a paper called [Locating and decoding EAN-13 barcodes from images captured by digital cameras][douglas_05] which's steps were adapted and modified resulting in:
1. Creating a binary representation of the image
2. Slice the image into a grid (20 x 15 cells)
3. Extract the skeleton of each cell
4. Separate the skeleton into its parts
5. Component labeling
6. Calculate the rotation of each part
7. Determine cell quality (rotation uniformity)
8. Find connecting cells with similar rotation
9. Create bounding-box of connected cells
Let's start with the creation of a binary image, or more precisely, a thresholded image.
### Creating a binary image
There are many different ways of creating a binary representation of an image. The easiest being a global threshold of 127 and determining for each pixel if its brighter (>=127) or darker(<127) than the threshold. Whilst this method might be the simpliest to implement, the outcome might not be sufficient because it does not take into account illumination changes across the image.
In order to account for the brightness changes in the image, a method called [Otsu][otsu_wiki] is employed which operates on the histogram of the source image and tries to separate the foreground from the background. The image below is produced by exactly this method.
![Binary Image][binary_image]
The separation of the barcode from the background works fairly well, resulting in an image which is handed over to the next stage.
### Slicing the image into a grid
The binary image as a whole does not provide much information about its content. In order to make sense of the contained structure, the image is breaken down into smaller chunks which are then described independently from each other. This _description_ is then compared with our hypotheses and either taken into consideration for further processing, or discarded.
During the development, a grid of 20 x 15 cells (assuming a 4:3 aspect ratio of the image) yielded to statisfactory results.
IMG
Having the image sliced up into individual parts, each cell in the grid needs to be evaluated and classified based on the properties described above. Each cell has to be checked if it contains nothing but parallel lines to get passed on to the next stage.
First, the bars have to be normalized in width (1px width), then separated line by line, followed by the estimation of the angle of each line. Then, each line in the cell is compared to the others to determine the parallelity. Finally, if the majority of lines are parallel to each other, the average angle is computed.
### Extract skeleton of each cell
The normalization of the width to 1px is done by a method known as [skeletonizing][skeleton_wiki]. What it does is, that it tries to remove as much of the weight of the bar as possible. In this implementation, this is simply done by eroding and dilating the contents as long as there are unprocessed pixels. This yields to a _skeleton_ of the given image as shown in the next image:
![skeleton_image][skeleton_image]
This clearly shows the location of the barcode, and the bars of the code reduced to their minimum width of 1px.
### Component Labeling
In order to find out if parallel lines are contained within each cell, the skeletonized content, which ideally contains straight lines of the interested area, must be separated into individual pieces (lines). This is achieved by applying a technique called [connected-component labeling][labeling_wiki] which separates a given region into its individual components. In case of the barcode pattern, all lines within the cell are split up into single lines and then weighted by their rotation.
Due to the fact that component labeling is usually quite expensive in terms of computation, a fast algorithm was of the essence to create a real-time application. With that in mind, an implementation of the paper ["A Linear-Time Component-Labeling Algorithm Using Contour Tracing Technique"][labeling_paper] by Fu Chang, Chun-Jen Chen, and Chi-Jen Lu was found on [CodeProject][labeling_codeproject]. Unfortunatelly, the original code was written in C and had to be ported to JavaScript.
The image below visualizes the output of the component-labeling stage.
![component_labeling_image][component_labeling_image]
As mentioned before, each cell is treated invividually, that's why each color is used repeatedly throughout the cells. The color for each component within the cell is unique and denotes the label given by the algorithm. The following images show scaled up versions of two extracted cells, of which the left one indicates a possible barcode area whereas the right one does not contain much but noise.
![Component labeling lines][component_labeling_lines_image]
![Component labeling lines][component_labeling_text_image]
Each label can then be considered as a possible __bar__ of a barcode pattern. To be able to classify such a representation, a quantitative value needs to be computed for each __bar__ which can then be compared with the other components.
### Determining the orientation of such a component
The orientation of a single component within a cell is calculated by using [central image moments][central_image_moments_wiki]. This method is typically used to extract information of the orientation of a binary image. In this case, the binary image is represented by the labeled components. Each component is considered its own binary image of which the central image moment can be computed.
As depicted in the equation below, the orientation (indicated as &theta;) of a binary image can be determined by knowing its central moments (&mu;).
![Calculation of Theta][math_theta]
The central moments (&mu;) are computed by making use of the raw moments (M) and the centroid (x,y) which in turn need to be calculated up front.
![Calculation of mu][math_mu]
The following computes the components of the centroid (x,y) by using the raw moments (M).
![Calculating x bar][math_x_y_bar]
Since we need image moments up to the second order, the following listing shows the computation of each single moment. The sum over x and y denotes an iteration over the entire image, whereas I(x,y) indicates the value of the pixel at the position x,y. In this case, the value can either be 0 or 1, since we are operating on a binary image.
![Calculating M][math_m]
After this step, each component has an angle assigned which is then used in the first step of the classification.
### Determining cell quality
#### Discarding non-representative information
Cells containing none, or just one component are immediatelly discarded and not used for further processing. In addition, components which do not cover at least 6 px (M00) are exlucded from any subsequent calculations of the affected cell. This pre-filtered list serves as basis for determining the uniformity of the component's angles throughout a single cell.
In case of a barcode each component in a cell should be ideally parallel to each other. But due to noise, distortion and other influences, this may vary at some degree. A simple clustering technique is applied to find out the similarity of such components. First off, all angles are clustered with a certain threshold, whereupon the cluster with the highest count of members is selected. Only if this cluster's member-size is greater that 3/4 th of the initial set (without the non-representative components) this cell is finally considered as being part of a barcode pattern. From now on, this cell is referred to as a patch, which contains the following information:
- an index (unique identifier within the grid)
- the bounding box defining the cell
- all components and their associated moments
- an average angle (computed from the cluster)
- a vector pointing in the direction of orientation
The following picture highlights the patches (cells) which were found during the classification process described above.
![Image found patches][found_patches_image]
It's noticable that some cells were falsely classified as being part of a barcode pattern. Those false positives can be taken care of by finding connected areas (consisting of more than one cell) and discarding all remaining cells.
### Finding connected cells
Basically, cells can be considered part of a barcode if they are neighbors and share common properties. This grouping is archived by a simple recursive component-labeling algorithm which operates on similarity of orientation, instead of color. For a patch to become member of a label, its vector must point in the same direction as its neighbor's. In order to account for deviations caused by distortion and other geometric influences, the orientation can deviate up to 5 %. The next image illustrates those connected cells where the color defines a certain group.
![Connected patch labels][connected_patch_labels]
Sometimes even neighboring cells are colored differently. This happens if the orientation of those patches differ too much (> 5%).
### Selecting groups
Next up is the selection of the groups which most probably contain a barcode. Because more than one barcode can be present in an image at the same time, groups are first sorted and then filtered by their count of patches.
![Remaining patch labels][remaining_patch_labels]
Only the biggest groups remain which are then passed on to the creation of a bounding-box.
### Create bounding box
Finally all the information necessary for outlining the location of the barcode is available. In this last step, a minimum bounding box is created
which spans over all the patches in one group. First, the average angle of the containing patches is calculated which is then used to rotate the cells
by exactly this angle. After that a bounding-box is computed by simply finding the outermost corners of all the patches.
![Rotated cells with box][transformed_patches]
Finally, the bounding-box is rotated in the inverse direction to transform it back to its origin.
![Bounding box][bounding_box_skeleton]
As illustrated in the image above, the bounding-box is placed exactly where the barcode pattern is printed. Having a box like this helps a lot during the
actual decoding process because knowing the orientation and the rough area limits the scanning efforts.
## Barcode Decoder
[teaser_image]: http://barcode.oberhofer.us/doc/img/teaser.png
[binary_image]: http://barcode.oberhofer.us/doc/img/binary.png
[skeleton_image]: http://barcode.oberhofer.us/doc/img/skeleton.png
[otsu_wiki]: http://en.wikipedia.org/wiki/Otsu%27s_method
[douglas_05]: http://www.icics.org/2005/download/P0840.pdf
[skeleton_wiki]: http://en.wikipedia.org/wiki/Morphological_skeleton
[labeling_wiki]: http://en.wikipedia.org/wiki/Connected-component_labeling
[labeling_codeproject]: http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
[labeling_paper]: http://www.iis.sinica.edu.tw/papers/fchang/1362-F.pdf
[component_labeling_image]: http://barcode.oberhofer.us/doc/img/component-labeling.png
[component_labeling_lines_image]: http://barcode.oberhofer.us/doc/img/component-labeling-line.png
[component_labeling_text_image]: http://barcode.oberhofer.us/doc/img/component-labeling-text.png
[math_theta]: http://www.sciweavers.org/tex2img.php?eq=%5CTheta%20%3D%20%5Cfrac%7B1%7D%7B2%7D%20%5Carctan%20%5Cleft%28%20%5Cfrac%7B2%5Cmu%27_%7B11%7D%7D%7B%5Cmu%27_%7B20%7D%20-%20%5Cmu%27_%7B02%7D%7D%20%5Cright%29&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0
[math_mu]: http://www.sciweavers.org/tex2img.php?eq=%5Cmu%27_%7B11%7D%20%3D%20M_%7B11%7D%2FM_%7B00%7D%20-%20%5Cbar%7Bx%7D%5Cbar%7By%7D%20%5C%5C%0A%5Cmu%27_%7B02%7D%20%3D%20M_%7B02%7D%2FM_%7B00%7D%20-%20%5Cbar%7By%7D%5E2%20%5C%5C%0A%5Cmu%27_%7B20%7D%20%3D%20M_%7B20%7D%2FM_%7B00%7D%20-%20%5Cbar%7Bx%7D%5E2&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0
[math_x_y_bar]: http://www.sciweavers.org/tex2img.php?eq=%5Cbar%7Bx%7D%20%3D%20M_%7B10%7D%2FM_%7B00%7D%20%5C%5C%0A%5Cbar%7By%7D%20%3D%20M_%7B01%7D%2FM_%7B00%7D&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0
[math_m]: http://www.sciweavers.org/tex2img.php?eq=M_%7B00%7D%20%3D%20%5Csum_x%20%5Csum_y%20I%28x%2Cy%29%20%5C%5C%0AM_%7B10%7D%20%3D%20%5Csum_x%20%5Csum_y%20x%20I%28x%2Cy%29%20%5C%5C%0AM_%7B01%7D%20%3D%20%5Csum_x%20%5Csum_y%20y%20I%28x%2Cy%29%20%5C%5C%0AM_%7B11%7D%20%3D%20%5Csum_x%20%5Csum_y%20xy%20I%28x%2Cy%29%20%5C%5C%0AM_%7B20%7D%20%3D%20%5Csum_x%20%5Csum_y%20x%5E2%20I%28x%2Cy%29%20%5C%5C%0AM_%7B02%7D%20%3D%20%5Csum_x%20%5Csum_y%20y%5E2%20I%28x%2Cy%29&bc=Transparent&fc=Black&im=png&fs=12&ff=arev&edit=0
[central_image_moments_wiki]: http://en.wikipedia.org/wiki/Image_moment#Central_moments
[found_patches_image]: http://barcode.oberhofer.us/doc/img/patches_found.png
[connected_patch_labels]: http://barcode.oberhofer.us/doc/img/connected-patch-labels.png
[remaining_patch_labels]: http://barcode.oberhofer.us/doc/img/remaining-patch-labels.png
[transformed_patches]: http://barcode.oberhofer.us/doc/img/bb-rotated.png
[bounding_box_skeleton]: http://barcode.oberhofer.us/doc/img/bb-binary.png

@ -0,0 +1,21 @@
{
"name": "quagga",
"version": "0.1.0",
"description": "This README would normally document whatever steps are necessary to get your application up and running.",
"main": "js/main.js",
"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-nodeunit": "~0.4.1",
"grunt-contrib-uglify": "~0.5.0",
"grunt-requirejs": "^0.4.2"
},
"directories": {
"doc": "doc"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

@ -0,0 +1,76 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(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;
}
};
});

@ -0,0 +1,247 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(Bresenham, ImageDebug, Code128Reader, EANReader) {
"use strict";
var readers = {
code_128_reader: Code128Reader,
ean_reader: EANReader
};
var BarcodeDecoder = {
create : function(config, inputImageWrapper) {
var _canvas = {
ctx : {
frequency : null,
pattern : null,
overlay : null
},
dom : {
frequency : null,
pattern : null,
overlay : null
}
},
_barcodeReaders = [],
_barcodeReader = null;
initCanvas();
initReaders();
initConfig();
function initCanvas() {
var $debug = document.querySelector("#debug.detection");
_canvas.dom.frequency = document.querySelector("canvas.frequency");
if (!_canvas.dom.frequency) {
_canvas.dom.frequency = document.createElement("canvas");
_canvas.dom.frequency.className = "frequency";
if($debug) {
$debug.appendChild(_canvas.dom.frequency);
}
}
_canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d");
_canvas.dom.pattern = document.querySelector("canvas.patternBuffer");
if (!_canvas.dom.pattern) {
_canvas.dom.pattern = document.createElement("canvas");
_canvas.dom.pattern.className = "patternBuffer";
if($debug) {
$debug.appendChild(_canvas.dom.pattern);
}
}
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
}
}
function initReaders() {
var i;
for ( i = 0; i < config.readers.length; i++) {
console.log(config.readers[i]);
_barcodeReaders.push(new readers[config.readers[i]]());
}
}
function initConfig() {
var i,
vis = [{
node : _canvas.dom.frequency,
prop : config.showFrequency
}, {
node : _canvas.dom.pattern,
prop : config.showPattern
}];
for (i = 0; i < vis.length; i++) {
if (vis[i].prop === true) {
vis[i].node.style.display = "block";
} else {
vis[i].node.style.display = "none";
}
}
}
/**
* extend the line on both ends
* @param {Array} line
* @param {Number} angle
*/
function getExtendedLine(line, angle, ext) {
var extension = {
y : ext * Math.sin(angle),
x : ext * Math.cos(angle)
};
line[0].y -= extension.y;
line[0].x -= extension.x;
line[1].y += extension.y;
line[1].x += extension.x;
// check if inside image
if (!inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0)) {
return null;
}
return line;
}
function getLine(box) {
return [{
x : (box[1][0] - box[0][0]) / 2 + box[0][0],
y : (box[1][1] - box[0][1]) / 2 + box[0][1]
}, {
x : (box[3][0] - box[2][0]) / 2 + box[2][0],
y : (box[3][1] - box[2][1]) / 2 + box[2][1]
}];
}
function tryDecode(line) {
var result = null,
i,
barcodeLine = Bresenham.getBarcodeLine(inputImageWrapper, line[0], line[1]);
if (config.showFrequency) {
ImageDebug.drawPath(line, {x: 'x', y: 'y'}, _canvas.ctx.overlay, {color: 'red', lineWidth: 3});
Bresenham.debug.printFrequency(barcodeLine.line, _canvas.dom.frequency);
}
Bresenham.toBinaryLine(barcodeLine);
if (config.showPattern) {
Bresenham.debug.printPattern(barcodeLine.line, _canvas.dom.pattern);
}
for ( i = 0; i < _barcodeReaders.length && result === null; i++) {
result = _barcodeReaders[i].decodePattern(barcodeLine.line);
if (result !== null) {
_barcodeReader = _barcodeReaders[i];
}
}
if(result === null){
return null;
}
return {
codeResult: result,
barcodeLine: barcodeLine
};
}
/**
* This method slices the given area apart and tries to detect a barcode-pattern
* for each slice. It returns the decoded barcode, or null if nothing was found
* @param {Array} box
* @param {Array} line
* @param {Number} lineAngle
*/
function tryDecodeBruteForce(box, line, lineAngle) {
var sideLength = Math.sqrt(Math.pow(box[1][0] - box[0][0], 2) + Math.pow((box[1][1] - box[0][1]), 2)),
i,
slices = 16,
result = null,
dir,
extension,
xdir = Math.sin(lineAngle),
ydir = Math.cos(lineAngle);
for ( i = 1; i < slices && result === null; i++) {
// move line perpendicular to angle
dir = sideLength / slices * i * (i % 2 === 0 ? -1 : 1);
extension = {
y : dir * xdir,
x : dir * ydir
};
line[0].y += extension.x;
line[0].x -= extension.y;
line[1].y += extension.x;
line[1].x -= extension.y;
result = tryDecode(line);
}
return result;
}
/**
* With the help of the configured readers (Code128 or EAN) this function tries to detect a
* valid barcode pattern within the given area.
* @param {Object} box The area to search in
* @returns {Object} the result {codeResult, line, angle, pattern, threshold}
*/
function decodeFromBoundingBox(box) {
var line,
lineAngle,
ctx = _canvas.ctx.overlay,
result;
if (config.drawBoundingBox && ctx) {
ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "blue", lineWidth: 2});
}
line = getLine(box);
lineAngle = Math.atan2(line[1].y - line[0].y, line[1].x - line[0].x);
line = getExtendedLine(line, lineAngle, 10);
if(line === null){
return null;
}
result = tryDecode(line);
if(result === null) {
result = tryDecodeBruteForce(box, line, lineAngle);
}
if(result === null) {
return null;
}
if (result && config.drawScanline && ctx) {
ImageDebug.drawPath(line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
}
return {
codeResult : result.codeResult,
line : line,
angle : lineAngle,
pattern : result.barcodeLine.line,
threshold : result.barcodeLine.threshold
};
}
return {
decodeFromBoundingBox : function(box) {
return decodeFromBoundingBox(box);
},
decodeFromBoundingBoxes : function(boxes) {
var i, result;
for ( i = 0; i < boxes.length; i++) {
result = decodeFromBoundingBox(boxes[i]);
if (result && result.codeResult) {
return result;
}
}
}
};
}
};
return (BarcodeDecoder);
});

@ -0,0 +1,515 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, mat2, vec2 */
define(["image_wrapper", "cv_utils", "rasterizer", "tracer", "skeletonizer", "array_helper", "image_debug"],
function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, ImageDebug) {
"use strict";
var _config,
_currentImageWrapper,
_skelImageWrapper,
_subImageWrapper,
_labelImageWrapper,
_patchGrid,
_patchLabelGrid,
_imageToPatchGrid,
_binaryImageWrapper,
_halfSample = true,
_patchSize,
_canvasContainer = {
ctx : {
binary : null
},
dom : {
binary : null
}
},
_numPatches = {x: 0, y: 0},
_inputImageWrapper,
_skeletonizer;
function initBuffers() {
var skeletonImageData;
if (_halfSample) {
_currentImageWrapper = new ImageWrapper({
x : _inputImageWrapper.size.x / 2 | 0,
y : _inputImageWrapper.size.y / 2 | 0
});
} else {
_currentImageWrapper = _inputImageWrapper;
}
_patchSize = {
x : 16 * ( _halfSample ? 1 : 2),
y : 16 * ( _halfSample ? 1 : 2)
};
_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);
skeletonImageData = new ArrayBuffer(_patchSize.x * _patchSize.y * 16);
_subImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y));
_skelImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), undefined, true);
_skeletonizer = skeletonizer(window, {
size : _patchSize.x
}, skeletonImageData);
_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);
}
function initCanvas() {
_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 bounding box which encloses all the given patches
* @returns {Array} The minimal bounding box
*/
function boxFromPatches(patches) {
var overAvg, i, j, patch, transMat, minx = _binaryImageWrapper.size.x, miny = _binaryImageWrapper.size.y, maxx = -_binaryImageWrapper.size.x, maxy = -_binaryImageWrapper.size.y, box, scale;
// draw all patches which are to be taken into consideration
overAvg = 0;
for ( i = 0; i < patches.length; i++) {
patch = patches[i];
overAvg += patch.rad;
if (_config.showPatches) {
ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, {color: "red"});
}
}
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 (_config.boxFromPatches.showTransformed) {
ImageDebug.drawPath(patch.box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#99ff00', lineWidth: 2});
}
}
// find bounding box
for ( i = 0; i < patches.length; i++) {
patch = patches[i];
for ( j = 0; j < 4; j++) {
if (patch.box[j][0] < minx) {
minx = patch.box[j][0];
}
if (patch.box[j][0] > maxx) {
maxx = patch.box[j][0];
}
if (patch.box[j][1] < miny) {
miny = patch.box[j][1];
}
if (patch.box[j][1] > maxy) {
maxy = patch.box[j][1];
}
}
}
box = [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]];
if (_config.boxFromPatches.showTransformedBox) {
ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2});
}
scale = _halfSample ? 2 : 1;
// reverse rotation;
transMat = mat2.inverse(transMat);
for ( j = 0; j < 4; j++) {
mat2.xVec2(transMat, box[j]);
}
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);
}
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,
moments,
patchesFound = [],
rasterizer,
rasterResult,
patch;
for ( i = 0; i < _numPatches.x; i++) {
for ( j = 0; j < _numPatches.y; j++) {
x = _subImageWrapper.size.x * i;
y = _subImageWrapper.size.y * j;
// seperate parts
skeletonize(x, y);
// Rasterize, find individual bars
_skelImageWrapper.zeroBorder();
ArrayHelper.init(_labelImageWrapper.data, 0);
rasterizer = Rasterizer.create(_skelImageWrapper, _labelImageWrapper);
rasterResult = rasterizer.rasterize(0);
if (_config.showLabels) {
_labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), {x : x, y : y});
}
// calculate moments from the skeletonized patch
moments = _labelImageWrapper.moments(rasterResult.count);
// extract eligible patches
patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y));
}
}
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});
}
}
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]++;
}
}
labelHist = labelHist.map(function(val, idx) {
return {
val : val,
label : idx + 1
};
});
labelHist.sort(function(a, b) {
return b.val - a.val;
});
// extract top areas with at least 6 patches present
topLabels = labelHist.filter(function(el) {
return el.val >= 5;
});
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);
}
}
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});
}
}
}
}
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);
}
}
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));
}
}
/**
* 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 = [];
if (moments.length >= 2) {
// only collect moments which's area covers at least 6 pixels.
for ( k = 0; k < moments.length; k++) {
if (moments[k].m00 > 6) {
eligibleMoments.push(moments[k]);
}
}
// if at least 2 moments are found which have 6pixels 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;
}
// 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];
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;
}
function trace(currentIdx) {
var x, y, currentPatch, patch, idx, dir, current = {
x : currentIdx % _patchLabelGrid.size.x,
y : (currentIdx / _patchLabelGrid.size.x) | 0
}, similarity;
if (currentIdx < _patchLabelGrid.data.length) {
currentPatch = _imageToPatchGrid.data[currentIdx];
// assign label
_patchLabelGrid.data[currentIdx] = label;
for ( dir = 0; dir < Tracer.searchDirections.length; dir++) {
y = current.y + Tracer.searchDirections[dir][0];
x = current.x + Tracer.searchDirections[dir][1];
idx = y * _patchLabelGrid.size.x + x;
// continue if patch empty
if (_patchGrid.data[idx] === 0) {
_patchLabelGrid.data[idx] = Number.MAX_VALUE;
continue;
}
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);
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(config, data) {
_config = config;
_inputImageWrapper = data.inputImageWrapper;
initBuffers();
initCanvas();
},
locate : function() {
var patchesFound,
topLabels = [],
boxes = [];
if (_halfSample) {
CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
}
binarizeImage();
patchesFound = findPatches();
// return unless 5% or more patches are found
if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
return;
}
// rasterrize area by comparing angular similarity;
var maxLabel = rasterizeAngularSimilarity(patchesFound);
if (maxLabel <= 1) {
return null;
}
// search for area with the most patches (biggest connected area)
topLabels = findBiggestConnectedAreas(maxLabel);
if (topLabels.length === 0) {
return null;
}
boxes = findBoxes(topLabels, maxLabel);
return boxes;
}
};
});

@ -0,0 +1,169 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(
function() {
"use strict";
function BarcodeReader() {
this._row = [];
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;
for (i = 0; i < counter.length; i++) {
error += Math.abs(code[i] - counter[i]);
}
return error;
};
BarcodeReader.prototype._nextSet = function(line) {
var i;
for (i = 0; i < line.length; i++) {
if (line[i]) {
return i;
}
}
return line.length;
};
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);
for (i = 0; i < counter.length; i++) {
norm = counter[i] === 1 ? counter[i] : counter[i] / ratio;
normalized.push(norm);
}
return normalized;
};
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;
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);
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;
}
}
}
// 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;
}
return result;
};
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 (BarcodeReader);
}
);

@ -0,0 +1,201 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(function() {
"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;
function read(a, b) {
val = imageData[b * width + a];
sum += val;
min = val < min ? val : min;
max = val > max ? val : max;
line.push(val);
}
if (steep) {
tmp = x0;
x0 = y0;
y0 = tmp;
tmp = x1;
x1 = y1;
y1 = tmp;
}
if (x0 > x1) {
tmp = x0;
x0 = x1;
x1 = tmp;
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 {
line : line,
min : min,
max : max
};
};
/**
* 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,
center = min + (max - min) / 2,
extrema = [],
currentDir,
dir,
threshold = (max - min) / 8,
rThreshold = -threshold,
i,
j;
// 1. find extrema
currentDir = line[0] > center ? Slope.DIR.DOWN : Slope.DIR.UP;
extrema.push({
pos : 0,
val : line[0]
});
for ( i = 0; i < line.length - 1; i++) {
slope = (line[i + 1] - line[i]);
if (slope < rThreshold) {
dir = Slope.DIR.UP;
} else if (slope > threshold) {
dir = Slope.DIR.DOWN;
} else {
dir = currentDir;
}
if (currentDir !== dir) {
extrema.push({
pos : i,
val : line[i]
});
currentDir = dir;
}
}
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;
}
// 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) / 2) | 0;
} else {
threshold = (extrema[i + 1].val + (extrema[i].val - extrema[i + 1].val) / 2) | 0;
}
for ( j = extrema[i].pos; j < extrema[i + 1].pos; j++) {
line[j] = line[j] > threshold ? 0 : 1;
}
}
return {
line : line,
threshold : threshold
};
};
/**
* 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]);
}
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);
}
}
}
};
return (Bresenham);
});

@ -0,0 +1,94 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, MediaStreamTrack */
define(function() {
"use strict";
/**
* Wraps browser-specific getUserMedia
* @param {Object} constraints
* @param {Object} success Callback
* @param {Object} failure Callback
*/
function getUserMedia(constraints, success, failure) {
navigator.getUserMedia(constraints, function(stream) {
var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream;
success.apply(null, [videoSrc]);
}, failure);
}
/**
* Tries to attach the camer-stream to a given video-element
* and calls the callback function when the content is ready
* @param {Object} constraints
* @param {Object} video
* @param {Object} callback
*/
function initCamera(constraints, video, callback) {
getUserMedia(constraints, function(src) {
video.src = src;
video.addEventListener('loadeddata', function() {
var attempts = 10;
function checkVideo() {
if (attempts > 0) {
if (video.videoWidth > 0 && video.videoHeight > 0) {
console.log(video.videoWidth + "px x " + video.videoHeight + "px");
callback();
} else {
window.setTimeout(checkVideo, 500);
}
} else {
callback('Unable to play video stream. Is webcam working?');
}
attempts--;
}
checkVideo();
}, false);
video.play();
}, function(e) {
console.log(e);
});
}
/**
* Requests the back-facing camera of the user. The callback is called
* whenever the stream is ready to be consumed, or if an error occures.
* @param {Object} video
* @param {Object} callback
*/
function request(video, callback) {
if ( typeof MediaStreamTrack.getSources !== 'undefined') {
MediaStreamTrack.getSources(function(sourceInfos) {
var videoSourceId;
for (var i = 0; i != sourceInfos.length; ++i) {
var sourceInfo = sourceInfos[i];
if (sourceInfo.kind == "video" && sourceInfo.facing == "environment") {
videoSourceId = sourceInfo.id;
}
}
var constraints = {
audio : false,
video : {
optional : [{
sourceId : videoSourceId
}]
}
};
initCamera(constraints, video, callback);
});
} else {
initCamera({
video : true,
audio : false
}, video, callback);
}
}
return {
request : function(video, callback) {
request(video, callback);
}
};
});

@ -0,0 +1,71 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2 */
define(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 = {};
function init() {
add(point);
updateCenter();
}
function add(point) {
pointMap[point.id] = point;
points.push(point);
}
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)]);
}
init();
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
};
}
};
return (Cluster);
});

@ -0,0 +1,448 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(
[
"./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]
]}
};
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);
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;
};
Code128Reader.prototype._findEnd = function() {
var counter = [0, 0, 0, 0, 0, 0, 0],
i,
self = this,
offset = self._nextSet(self._row),
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
},
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, 13);
error = self._matchPattern(normalized, self.CODE_PATTERN[self.STOP_CODE]);
if (error < 3) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
for ( j = 0; j < 5; j++) {
counter[j] = counter[j + 2];
}
counter[5] = 0;
counter[6] = 0;
counterPos--;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
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);
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 < 3) {
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;
}
}
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;
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;
}
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 (code === null) {
return null;
}
// find end bar
code.end = self._nextUnset(self._row, code.end);
if (code.end === self._row.length) {
return null;
}
// checksum
// Does not work correctly yet!!! startcode - endcode?
checksum -= multiplier * rawResult[rawResult.length - 1];
if (checksum % 103 != rawResult[rawResult.length - 1]) {
return null;
}
// remove last code from result (checksum)
result.splice(result.length - 1, 1);
return {
code : result.join(""),
start : startInfo.start,
end : code.end,
codeset : codeset,
startInfo : startInfo,
decodedCodes : decodedCodes,
endInfo : code
};
};
return (Code128Reader);
}
);

@ -0,0 +1,43 @@
/**
* The basic configuration
*/
define(function(){
var config = {
inputStream: { name: "Live",
type: "LiveStream"
},
tracking: false,
debug: false,
controls: false,
locate: true,
visual: {
show: true
},
decoder:{
drawBoundingBox: true,
showFrequency: false,
drawScanline: true,
showPattern: false,
readers: [
'code_128_reader'
]
},
locator: {
showCanvas: false,
showPatches: false,
showFoundPatches: false,
showSkeleton: false,
showLabels: false,
showPatchLabels: false,
showRemainingPatchLabels: false,
boxFromPatches: {
showTransformed: false,
showTransformedBox: false,
showBB: false
}
}
};
return config;
});

@ -0,0 +1,544 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2, vec3 */
define(['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;
};
/**
* 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;
// 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++;
}
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++;
}
}
};
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;
// sum up first row
for (var i = 0; i < width; i++) {
sum += imageData[i];
integralImageData[i] = sum;
}
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];
}
}
};
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 ? 0 : 1;
}
};
CVUtils.computeHistogram = function(imageWrapper) {
var imageData = imageWrapper.data, length = imageData.length, i, hist = new Int32Array(256);
// init histogram
for ( i = 0; i < 256; i++) {
hist[i] = 0;
}
while (length--) {
hist[imageData[length]]++;
}
return hist;
};
CVUtils.otsuThreshold = function(imageWrapper, targetWrapper) {
var hist, threshold;
function px(init, end) {
var sum = 0, i;
for ( i = init; i <= end; i++) {
sum += hist[i];
}
return sum;
}
function mx(init, end) {
var i, sum = 0;
for ( i = init; i <= end; i++) {
sum += i * hist[i];
}
return sum;
}
function determineThreshold() {
var vet = [0], p1, p2, p12, k, m1, m2, m12;
hist = CVUtils.computeHistogram(imageWrapper);
for ( k = 1; k < 255; k++) {
p1 = px(0, k);
p2 = px(k + 1, 255);
p12 = p1 * p2;
if (p12 === 0) {
p12 = 1;
}
m1 = mx(0, k) * p2;
m2 = mx(k + 1, 255) * p1;
m12 = m1 - m2;
vet[k] = m12 * m12 / p12;
}
return ArrayHelper.maxIndex(vet);
}
threshold = determineThreshold();
CVUtils.thresholdImage(imageWrapper, threshold, targetWrapper);
return threshold;
};
// local thresholding
CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapper) {
CVUtils.computeIntegralImage(imageWrapper, integralWrapper);
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);
// 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;
}
}
// 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;
}
}
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;
}
}
};
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 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 clusters;
};
CVUtils.Tracer = {
trace : function(points, vec) {
var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0;
function trace(idx, forward) {
var from, to, toIdx, predictedPos, thresholdX = 1, thresholdY = Math.abs(vec[1] / 10), found = false;
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;
}
}
// 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]
};
}
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]);
}
}
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];
}
};
CVUtils.bitwiseOr = 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];
}
};
CVUtils.countNonZero = function(imageWrapper) {
var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0;
while (length--) {
sum += data[length];
}
return sum;
};
CVUtils.topGeneric = function(list, top, scoreFunc) {
var i, minIdx = 0, min = 0, queue = [], score, hit, pos;
for ( i = 0; i < top; i++) {
queue[i] = {
score : 0,
item : null
};
}
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;
}
}
}
}
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) {
var l = imageData.length / 4;
var i = 0;
for ( i = 0; i < l; i++) {
//outArray[i] = (0.299*imageData[i*4+0] + 0.587*imageData[i*4+1] + 0.114*imageData[i*4+2]);
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;
};
/**
* @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;
}
};
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];
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;
};
return (CVUtils);
});

@ -0,0 +1,254 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(
[
"./barcode_reader"
],
function(BarcodeReader) {
"use strict";
function EANReader() {
BarcodeReader.call(this);
}
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]}
};
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);
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;
return bestMatch;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
throw BarcodeReader.CodeNotFoundException;
};
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;
}
if (tryHarder === undefined) {
tryHarder = true;
}
if ( epsilon === undefined) {
epsilon = 2;
}
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);
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 {
throw BarcodeReader.PatternNotFoundException;
}
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
throw BarcodeReader.PatternNotFoundException;
};
EANReader.prototype._decode = function() {
var startInfo,
self = this,
code = null,
result = [],
i,
codeFrequency = 0x0,
decodedCodes = [];
try {
startInfo = self._findPattern(self.START_PATTERN);
code = {
code : startInfo.code,
start : startInfo.start,
end : startInfo.end
};
decodedCodes.push(code);
for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end);
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);
}
for ( i = 0; i < self.CODE_FREQUENCY.length; i++) {
if (codeFrequency === self.CODE_FREQUENCY[i]) {
result.unshift(i);
break;
}
}
code = self._findPattern(self.MIDDLE_PATTERN, code.end, true);
if (code === null) {
return null;
}
decodedCodes.push(code);
for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end, self.CODE_G_START);
decodedCodes.push(code);
result.push(code.code);
}
code = self._findPattern(self.STOP_PATTERN, code.end);
decodedCodes.push(code);
// Checksum
if (!self._checksum(result)) {
return null;
}
} catch (exc) {
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);
}
);

@ -0,0 +1,57 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(function() {
"use strict";
var _events = function() {
var events = {};
function getEvent(eventName) {
if (!events[eventName]) {
events[eventName] = {
subscribers : []
};
}
return events[eventName];
}
function publishSubscription(subscription, data) {
if (subscription.async) {
setTimeout(function() {
subscription.callback.call(null, data);
}, 4);
} else {
subscription.callback.call(null, data);
}
}
return {
subscribe : function(event, callback, async) {
var subscription;
if ( typeof callback === "function") {
subscription = {
callback : callback,
async : async
};
} else {
subscription = callback;
if (!subscription.callback) {
throw "Callback was not specified on options";
}
}
getEvent(event).subscribers.push(subscription);
},
publish : function(eventName, data) {
var subscribers = getEvent(eventName).subscribers, i;
for ( i = 0; i < subscribers.length; i++) {
publishSubscription(subscribers[i], data);
}
}
};
}();
return _events;
});

@ -0,0 +1,92 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(["cv_utils"], function(CVUtils) {
"use strict";
var FrameGrabber = {};
FrameGrabber.create = function(inputStream, canvas) {
var _that = {},
_streamConfig = inputStream.getConfig(),
_video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()),
_size =_streamConfig.size ? CVUtils.imageRef(_streamConfig.size, _streamConfig.size) : _video_size,
_sx = 0,
_sy = 0,
_dx = 0,
_dy = 0,
_sWidth,
_dWidth,
_sHeight,
_dHeight,
_canvas = null,
_ctx = null,
_data = null;
// Check if size is given
if (_streamConfig.size) {
if (_video_size.x/_video_size.y > 1) {
_size.x = _streamConfig.size;
_size.y = (_video_size.y/_video_size.x)*_streamConfig.size;
} else {
_size.y = _streamConfig.size;
_size.x = (_video_size.x/_video_size.y)*_streamConfig.size;
}
}
_sWidth = _video_size.x;
_dWidth = _size.x;
_sHeight = _video_size.y;
_dHeight = _size.y;
_canvas = canvas ? canvas : document.createElement("canvas");
_canvas.width = _size.x;
_canvas.height = _size.y;
_ctx = _canvas.getContext("2d");
_data = new Uint8Array(_size.x * _size.y);
/**
* Uses the given array as frame-buffer
*/
_that.attachData = function(data) {
_data = data;
};
/**
* Returns the used frame-buffer
*/
_that.getData = function() {
return _data;
};
/**
* Fetches a frame from the input-stream and puts into the frame-buffer.
* The image-data is converted to gray-scale and then half-sampled if configured.
*/
_that.grab = function() {
var doHalfSample = _streamConfig.halfSample,
frame = inputStream.getFrame(),
ctxData;
if (frame) {
_ctx.drawImage(frame, _sx, _sy, _sWidth, _sHeight, _dx, _dy, _dWidth, _dHeight);
ctxData = _ctx.getImageData(0, 0, _size.x, _size.y).data;
if(doHalfSample){
CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data);
} else {
CVUtils.computeGray(ctxData, _data);
}
return true;
} else {
return false;
}
};
_that.getSize = function() {
return _size;
};
return _that;
};
return (FrameGrabber);
});

@ -0,0 +1,429 @@
/*
<augmentedJS: A javascript library for natural feature tracking>
Copyright (C) 2011
- Christoph Oberhofer (ar.oberhofer@gmail.com)
- Jens Grubert (jg@jensgrubert.de)
- Gerhard Reitmayr (reitmayr@icg.tugraz.at)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* 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[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 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;
};
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];
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;
};
var vec4={};
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;
};
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;
};
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;
};
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;
};
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];
} else {
if(glMatrixArrayType === Array)
dest = new glMatrixArrayType([0,0,0,0]);
else
dest = new glMatrixArrayType(4);
}
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;
return dest;
};
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;
};
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(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;
};
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;
};
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;
};
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
*/
vec2.unproject = function(vec){
return vec3.create([vec[0], vec[1], 1]);
};
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
*/
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;
};
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;
};
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];
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;
};
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;
};

@ -0,0 +1,40 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define([], 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);
}
};
});

@ -0,0 +1,29 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(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);
},
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();
}
};
});

@ -0,0 +1,63 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(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 (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";
}
}
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 (notloadedImgs.length === 0) {
console.log("Images loaded");
callback.apply(null, [htmlImagesArray]);
}
};
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);
};
}
return (ImageLoader);
});

@ -0,0 +1,424 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2, mat2 */
define([
"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);
}
}
} 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));
};
/**
* 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;
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;
}
};
/**
* 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;
}
};
/**
* 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
*/
ImageWrapper.prototype.subImage = function(from, size) {
return new SubImage(from, size, this);
};
/**
* 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];
}
}
};
ImageWrapper.prototype.copyTo = function(imageWrapper) {
var length = this.data.length, srcData = this.data, dstData = imageWrapper.data;
while (length--) {
dstData[length] = srcData[length];
}
};
/**
* 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]];
};
/**
* 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;
}
};
/**
* Inverts a binary image in place
*/
ImageWrapper.prototype.invert = function() {
var data = this.data, length = data.length;
while (length--) {
data[length] = data[length] ? 0 : 1;
}
};
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;
}
}
};
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;
}
for ( i = 0; i < labelcount; i++) {
labelsum[i] = {
m00 : 0,
m01 : 0,
m10 : 0,
m11 : 0,
m02 : 0,
m20 : 0,
theta : 0,
rad : 0
};
}
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;
}
}
}
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;
}
label.rad = tmp > PI ? tmp - PI : tmp;
label.vec = vec2.create([Math.cos(tmp), Math.sin(tmp)]);
result.push(label);
}
}
return result;
};
/**
* 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);
};
/**
* 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);
};
return (ImageWrapper);
});

@ -0,0 +1,236 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(["image_loader"], function(ImageLoader) {
"use strict";
var InputStream = {};
InputStream.createVideoStream = function(video) {
var that = {},
_config = null,
_eventNames = ['canrecord', 'ended'],
_eventHandlers = {};
that.getRealWidth = function() {
return video.videoWidth;
};
that.getRealHeight = function() {
return video.videoHeight;
};
that.getWidth = function() {
return _config.halfSample ? video.videoWidth / 2 : video.videoWidth;
};
that.getHeight = function() {
return _config.halfSample ? video.videoHeight / 2 : video.videoHeight;
};
that.setInputStream = function(config) {
_config = config;
video.src = config.src;
};
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);
}
};
that.trigger = function(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.getFrame = function() {
return video;
};
return that;
};
InputStream.createLiveStream = function(video) {
video.setAttribute("autoplay", true);
var that = InputStream.createVideoStream(video);
that.ended = function() {
return false;
};
that.getWidth = function() {
return this.getConfig().halfSample ? video.videoWidth / 2 : video.videoWidth;
};
that.getHeight = function() {
return this.getConfig().halfSample ? video.videoHeight / 2 : video.videoHeight;
};
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,
_eventNames = ['canrecord', 'ended'],
_eventHandlers = {};
function loadImages() {
loaded = false;
ImageLoader.load(baseUrl, function(imgs) {
imgArray = imgs;
width = imgs[0].width;
height = imgs[0].height;
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 _config.size ? width/height > 1 ? _config.size : (width/height) * _config.size : width;
};
that.getHeight = function() {
return _config.size ? width/height > 1 ? (height/width) * _config.size : _config.size : 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.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;
};
return that;
};
return (InputStream);
});

@ -0,0 +1,223 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2 */
define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access"],
function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess) {
"use strict";
var _inputStream,
_framegrabber,
_stopped,
_canvasContainer = {
ctx : {
image : null,
overlay : null
},
dom : {
image : null,
overlay : null
}
},
_inputImageWrapper,
_boxSize,
_decoder,
_initialized = false;
function initialize(config) {
_config = HtmlUtils.mergeObjects(_config, config);
initInputStream();
}
function initConfig() {
var vis = [{
node : document.querySelector("div[data-controls]"),
prop : _config.controls
}, {
node : _canvasContainer.dom.overlay,
prop : _config.visual.show
}];
for (var i = 0; i < vis.length; i++) {
if (vis[i].node) {
if (vis[i].prop === true) {
vis[i].node.style.display = "block";
} else {
vis[i].node.style.display = "none";
}
}
}
}
function initInputStream() {
var video;
if (_config.inputStream.type == "VideoStream") {
video = document.createElement("video");
_inputStream = InputStream.createVideoStream(video);
} else if (_config.inputStream.type == "ImageStream") {
_inputStream = InputStream.createImageStream();
} else if (_config.inputStream.type == "LiveStream") {
video = document.createElement("video");
var $viewport = document.querySelector("#interactive.viewport");
if($viewport) {
$viewport.appendChild(video);
}
_inputStream = InputStream.createLiveStream(video);
CameraAccess.request(video, function(err) {
if (!err) {
_inputStream.trigger("canrecord");
} else {
console.log(err);
}
});
}
_inputStream.setAttribute("preload", "auto");
_inputStream.setAttribute("autoplay", true);
_inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord);
}
function canRecord() {
initBuffers();
initCanvas();
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
_framegrabber.attachData(_inputImageWrapper.data);
initConfig();
_inputStream.play();
_initialized = true;
if (_config.readyFunc) {
_config.readyFunc.apply();
}
}
function initCanvas() {
var $viewport = document.querySelector("#interactive.viewport");
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
if (!_canvasContainer.dom.image) {
_canvasContainer.dom.image = document.createElement("canvas");
_canvasContainer.dom.image.className = "imgBuffer";
if($viewport && _config.inputStream.type == "ImageStream") {
$viewport.appendChild(_canvasContainer.dom.image);
}
}
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.width = _inputImageWrapper.size.x;
_canvasContainer.dom.image.height = _inputImageWrapper.size.y;
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) {
_canvasContainer.dom.overlay = document.createElement("canvas");
_canvasContainer.dom.overlay.className = "drawingBuffer";
if($viewport) {
$viewport.appendChild(_canvasContainer.dom.overlay);
}
var clearFix = document.createElement("br");
clearFix.setAttribute("clear", "all");
if($viewport) {
$viewport.appendChild(clearFix);
}
}
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputImageWrapper.size.x;
_canvasContainer.dom.overlay.height = _inputImageWrapper.size.y;
}
function initBuffers() {
_inputImageWrapper = new ImageWrapper({
x : _inputStream.getWidth(),
y : _inputStream.getHeight()
});
console.log(_inputStream.getWidth());
console.log(_inputStream.getHeight());
_boxSize = [
vec2.create([20, _inputStream.getHeight() / 2 - 100]),
vec2.create([20, _inputStream.getHeight() / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 - 100])
];
BarcodeLocator.init(_config.locator, {
inputImageWrapper : _inputImageWrapper
});
}
function getBoundingBoxes() {
var boxes;
if (_config.locate) {
boxes = BarcodeLocator.locate();
} else {
boxes = [_boxSize];
}
return boxes;
}
function update() {
var result,
boxes;
if (_framegrabber.grab()) {
_canvasContainer.ctx.overlay.clearRect(0, 0, _inputImageWrapper.size.x, _inputImageWrapper.size.y);
boxes = getBoundingBoxes();
if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes);
if (result && result.codeResult) {
Events.publish("detected", result.codeResult.code);
}
}
}
}
function start() {
_stopped = false;
( function frame() {
if (!_stopped) {
if (_config.inputStream.type == "LiveStream") {
window.requestAnimFrame(frame);
}
update();
}
}());
}
return {
init : function(config, callback) {
initialize(config, callback);
},
start : function() {
console.log("Start!");
start();
},
stop : function() {
_stopped = true;
},
onDetected : function(callback) {
Events.subscribe("detected", callback, true);
},
isInitialized : function() {
return _initialized;
},
canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) {
config.inputStream = {
type : "ImageStream",
src : config.src,
sequence : false,
size: 800
};
config.readyFunc = function() {
Events.subscribe("detected", function(result) {
_stopped = true;
resultCallback.call(null, result);
}, true);
start();
};
initialize(config);
},
Reader: {
EANReader : EANReader,
Code128Reader : Code128Reader
}
};
});

@ -0,0 +1,198 @@
/* 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(Tracer) {
"use strict";
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);
return {
rasterize : function(depthlabel) {
var color,
bc,
lc,
labelindex,
cx,
cy,
colorMap = [],
vertex,
p,
cc,
sc,
pos,
connectedCount = 0,
i;
for ( i = 0; i < 400; i++) {
colorMap[i] = 0;
}
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
};
},
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();
}
}
}
};
}
};
return (Rasterizer);
});

@ -0,0 +1,202 @@
/* jshint undef: true, unused: true, browser:true, devel: true, -W041: false */
/* global define */
define(function() {
"use strict";
function Skeletonizer(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 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
};
}
return Skeletonizer;
});

@ -0,0 +1,97 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(["typedefs"], function() {
"use strict";
/**
* 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;
this.from = from;
this.size = size;
}
/**
* 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);
};
/**
* 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];
};
/**
* 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;
};
/**
* 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);
});

@ -0,0 +1,108 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
/**
* http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
*/
define(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;
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;
}
function vertex2D(x, y, dir) {
return {
dir : dir,
x : x,
y : y,
next : null,
prev : null
};
}
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;
}
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 (Tracer);
});

@ -0,0 +1,21 @@
/*
* typedefs.js
* Normalizes browser-specific prefixes
*/
glMatrixArrayType = Float32Array;
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;

410
src/vendor/almond.js vendored

@ -0,0 +1,410 @@
/**
* almond 0.2.6 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/almond for details
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */
var requirejs, require, define;
(function (undef) {
var main, req, makeMap, handlers,
defined = {},
waiting = {},
config = {},
defining = {},
hasOwn = Object.prototype.hasOwnProperty,
aps = [].slice;
function hasProp(obj, prop) {
return hasOwn.call(obj, prop);
}
/**
* Given a relative module name, like ./something, normalize it to
* a real name that can be mapped to a path.
* @param {String} name the relative name
* @param {String} baseName a real name that the name arg is relative
* to.
* @returns {String} normalized name
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap,
foundI, foundStarMap, starI, i, j, part,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
if (name && name.charAt(0) === ".") {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
//Convert baseName to array, and lop off the last part,
//so that . matches that "directory" and not name of the baseName's
//module. For instance, baseName of "one/two/three", maps to
//"one/two/three.js", but we want the directory, "one/two" for
//this normalization.
baseParts = baseParts.slice(0, baseParts.length - 1);
name = baseParts.concat(name.split("/"));
//start trimDots
for (i = 0; i < name.length; i += 1) {
part = name[i];
if (part === ".") {
name.splice(i, 1);
i -= 1;
} else if (part === "..") {
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
}
//end trimDots
name = name.join("/");
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
}
//Apply map config if available.
if ((baseParts || starMap) && map) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join("/");
if (baseParts) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = map[baseParts.slice(0, j).join('/')];
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = mapValue[nameSegment];
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && starMap[nameSegment]) {
foundStarMap = starMap[nameSegment];
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
return name;
}
function makeRequire(relName, forceSync) {
return function () {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
};
}
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(depName) {
return function (value) {
defined[depName] = value;
};
}
function callDep(name) {
if (hasProp(waiting, name)) {
var args = waiting[name];
delete waiting[name];
defining[name] = true;
main.apply(undef, args);
}
if (!hasProp(defined, name) && !hasProp(defining, name)) {
throw new Error('No ' + name);
}
return defined[name];
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relName) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName));
} else {
name = normalize(name, relName);
}
} else {
name = normalize(name, relName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
if (prefix) {
plugin = callDep(prefix);
}
}
//Using ridiculous property names for space reasons
return {
f: prefix ? prefix + '!' + name : name, //fullName
n: name,
pr: prefix,
p: plugin
};
};
function makeConfig(name) {
return function () {
return (config && config.config && config.config[name]) || {};
};
}
handlers = {
require: function (name) {
return makeRequire(name);
},
exports: function (name) {
var e = defined[name];
if (typeof e !== 'undefined') {
return e;
} else {
return (defined[name] = {});
}
},
module: function (name) {
return {
id: name,
uri: '',
exports: defined[name],
config: makeConfig(name)
};
}
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i,
args = [],
usingExports;
//Use name if no relName
relName = relName || name;
//Call the callback to define the module, if necessary.
if (typeof callback === 'function') {
//Pull out the defined dependencies and pass the ordered
//values to the callback.
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName);
depName = map.f;
//Fast path CommonJS standard dependencies.
if (depName === "require") {
args[i] = handlers.require(name);
} else if (depName === "exports") {
//CommonJS module spec 1.1
args[i] = handlers.exports(name);
usingExports = true;
} else if (depName === "module") {
//CommonJS module spec 1.1
cjsModule = args[i] = handlers.module(name);
} else if (hasProp(defined, depName) ||
hasProp(waiting, depName) ||
hasProp(defining, depName)) {
args[i] = callDep(depName);
} else if (map.p) {
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
args[i] = defined[depName];
} else {
throw new Error(name + ' missing ' + depName);
}
}
ret = callback.apply(defined[name], args);
if (name) {
//If setting exports via "module" is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
if (cjsModule && cjsModule.exports !== undef &&
cjsModule.exports !== defined[name]) {
defined[name] = cjsModule.exports;
} else if (ret !== undef || !usingExports) {
//Use the return value from the function.
defined[name] = ret;
}
}
} else if (name) {
//May just be an object definition for the module. Only
//worry about defining if have a module name.
defined[name] = callback;
}
};
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
if (typeof deps === "string") {
if (handlers[deps]) {
//callback in this case is really relName
return handlers[deps](callback);
}
//Just return the module wanted. In this scenario, the
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
if (callback.splice) {
//callback is an array, which means it is a dependency list.
//Adjust args if there are dependencies
deps = callback;
callback = relName;
relName = null;
} else {
deps = undef;
}
}
//Support require(['a'])
callback = callback || function () {};
//If relName is a function, it is an errback handler,
//so remove it.
if (typeof relName === 'function') {
relName = forceSync;
forceSync = alt;
}
//Simulate async callback;
if (forceSync) {
main(undef, deps, callback, relName);
} else {
//Using a non-zero value because of concern for what old browsers
//do, and latest browsers "upgrade" to 4 if lower value is used:
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
//If want a value immediately, use require('id') instead -- something
//that works in almond on the global level, but not guaranteed and
//unlikely to work in other AMD implementations.
setTimeout(function () {
main(undef, deps, callback, relName);
}, 4);
}
return req;
};
/**
* Just drops the config on the floor, but returns req in case
* the config return value is used.
*/
req.config = function (cfg) {
config = cfg;
if (config.deps) {
req(config.deps, config.callback);
}
return req;
};
/**
* Expose module registry for debugging and tooling
*/
requirejs._defined = defined;
define = function (name, deps, callback) {
//This module may not have dependencies
if (!deps.splice) {
//deps is not an array, so probably means
//an object literal or factory function for
//the value. Adjust args.
callback = deps;
deps = [];
}
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
waiting[name] = [name, deps, callback];
}
};
define.amd = {
jQuery: true
};
}());

@ -0,0 +1,121 @@
/*
* http://www.quirksmode.org/js/detect.html
*/
var BrowserDetect = {
init: function () {
this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
this.version = this.searchVersion(navigator.userAgent)
|| this.searchVersion(navigator.appVersion)
|| "an unknown version";
this.OS = this.searchString(this.dataOS) || "an unknown OS";
},
searchString: function (data) {
for (var i=0;i<data.length;i++) {
var dataString = data[i].string;
var dataProp = data[i].prop;
this.versionSearchString = data[i].versionSearch || data[i].identity;
if (dataString) {
if (dataString.indexOf(data[i].subString) != -1)
return data[i].identity;
}
else if (dataProp)
return data[i].identity;
}
},
searchVersion: function (dataString) {
var index = dataString.indexOf(this.versionSearchString);
if (index == -1) return;
return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
},
dataBrowser: [
{
string: navigator.userAgent,
subString: "Chrome",
identity: "Chrome"
},
{ string: navigator.userAgent,
subString: "OmniWeb",
versionSearch: "OmniWeb/",
identity: "OmniWeb"
},
{
string: navigator.vendor,
subString: "Apple",
identity: "Safari",
versionSearch: "Version"
},
{
prop: window.opera,
identity: "Opera",
versionSearch: "Version"
},
{
string: navigator.vendor,
subString: "iCab",
identity: "iCab"
},
{
string: navigator.vendor,
subString: "KDE",
identity: "Konqueror"
},
{
string: navigator.userAgent,
subString: "Firefox",
identity: "Firefox"
},
{
string: navigator.vendor,
subString: "Camino",
identity: "Camino"
},
{ // for newer Netscapes (6+)
string: navigator.userAgent,
subString: "Netscape",
identity: "Netscape"
},
{
string: navigator.userAgent,
subString: "MSIE",
identity: "Explorer",
versionSearch: "MSIE"
},
{
string: navigator.userAgent,
subString: "Gecko",
identity: "Mozilla",
versionSearch: "rv"
},
{ // for older Netscapes (4-)
string: navigator.userAgent,
subString: "Mozilla",
identity: "Netscape",
versionSearch: "Mozilla"
}
],
dataOS : [
{
string: navigator.platform,
subString: "Win",
identity: "Windows"
},
{
string: navigator.platform,
subString: "Mac",
identity: "Mac"
},
{
string: navigator.userAgent,
subString: "iPhone",
identity: "iPhone/iPod"
},
{
string: navigator.platform,
subString: "Linux",
identity: "Linux"
}
]
};
BrowserDetect.init();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

27811
src/vendor/r.js vendored

File diff suppressed because one or more lines are too long

2053
src/vendor/require.js vendored

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