diff --git a/example/node-test.js b/example/node-test.js new file mode 100644 index 0000000..b080c88 --- /dev/null +++ b/example/node-test.js @@ -0,0 +1,15 @@ +var Quagga = require('../lib/quagga'); + +Quagga.decodeSingle({ + src: "../test/fixtures/code_128/image-001.jpg", + numOfWorkers: 0, + inputStream: { + size: 640 + } +}, function(result) { + if(result.codeResult) { + console.log("result", result.codeResult.code); + } else { + console.log("not detected"); + } +}); \ No newline at end of file diff --git a/lib/frame_grabber.js b/lib/frame_grabber.js new file mode 100644 index 0000000..e621f6d --- /dev/null +++ b/lib/frame_grabber.js @@ -0,0 +1,75 @@ +/* jshint undef: true, unused: true, browser:true, devel: true */ +/* global define */ + +define(["cv_utils"], function(CVUtils) { + "use strict"; + + var FrameGrabber = {}; + + FrameGrabber.create = function(inputStream) { + var _that = {}, + _streamConfig = inputStream.getConfig(), + _video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()), + _size =_streamConfig.size ? CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()) : _video_size, + _sx = 0, + _sy = 0, + _dx = 0, + _dy = 0, + _sWidth, + _dWidth, + _sHeight, + _dHeight, + _canvas = null, + _ctx = null, + _data = null; + + _sWidth = _video_size.x; + _dWidth = _size.x; + _sHeight = _video_size.y; + _dHeight = _size.y; + + _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(); + + if (frame) { + if(doHalfSample){ + CVUtils.grayAndHalfSampleFromCanvasData(frame.data, _size, _data); + } else { + CVUtils.computeGray(frame.data, _data); + } + return true; + } else { + return false; + } + }; + + _that.getSize = function() { + return _size; + }; + + return _that; + }; + + return (FrameGrabber); +}); diff --git a/lib/input_stream.js b/lib/input_stream.js new file mode 100644 index 0000000..3fc3105 --- /dev/null +++ b/lib/input_stream.js @@ -0,0 +1,138 @@ +/* jshint undef: true, unused: true, browser:true, devel: true */ +/* global define */ + +define(function() { + "use strict"; + + var GetPixels = require("get-pixels"); + + var InputStream = {}; + + InputStream.createImageStream = function() { + var that = {}; + var _config = null; + + var width = 0, + height = 0, + frameIdx = 0, + paused = true, + loaded = false, + frame = null, + baseUrl, + ended = false, + size, + calculatedWidth, + calculatedHeight, + _eventNames = ['canrecord', 'ended'], + _eventHandlers = {}; + + function loadImages() { + loaded = false; + GetPixels(baseUrl, function(err, pixels) { + if (err) { + console.log(err); + exit(1); + } + loaded = true; + console.log(pixels.shape); + frame = pixels; + width = pixels.shape[0]; + height = pixels.shape[1]; + calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width; + calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height; + + + setTimeout(function() { + publishEvent("canrecord", []); + }, 0); + }); + } + + function publishEvent(eventName, args) { + var j, + handlers = _eventHandlers[eventName]; + + if (handlers && handlers.length > 0) { + for ( j = 0; j < handlers.length; j++) { + handlers[j].apply(that, args); + } + } + } + + + that.trigger = publishEvent; + + that.getWidth = function() { + return calculatedWidth; + }; + + that.getHeight = function() { + return calculatedHeight; + }; + + that.setWidth = function(width) { + calculatedWidth = width; + }; + + that.setHeight = function(height) { + calculatedHeight = height; + }; + + that.getRealWidth = function() { + return width; + }; + + that.getRealHeight = function() { + return height; + }; + + that.setInputStream = function(stream) { + _config = stream; + baseUrl = _config.src; + size = 1; + 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() { + if (!loaded){ + return null; + } + return frame; + }; + + return that; + }; + + return (InputStream); +}); diff --git a/lib/quagga.js b/lib/quagga.js new file mode 100644 index 0000000..7845bb7 --- /dev/null +++ b/lib/quagga.js @@ -0,0 +1,12 @@ +var requirejs = require('requirejs'); + +requirejs.config({ + "baseUrl" : "../src", + "paths" : { + "typedefs" : "typedefs", + "input_stream": "../lib/input_stream", + "frame_grabber": "../lib/frame_grabber" + } +}); + +module.exports = requirejs('quagga'); \ No newline at end of file diff --git a/package.json b/package.json index 7b3359e..7d01fc1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "quagga", "version": "0.6.6", "description": "An advanced barcode-scanner written in JavaScript", - "main": "dist/quagga.js", + "main": "lib/quagga.js", + "browser": "dist/quagga.js", "devDependencies": { "async": "^0.9.0", "grunt": "~0.4.5", @@ -46,5 +47,9 @@ "imageprocessing" ], "author": "Christoph Oberhofer ", - "license": "MIT" + "license": "MIT", + "dependencies": { + "get-pixels": "^3.2.2", + "gl-matrix": "^2.1.0" + } } diff --git a/src/barcode_locator.js b/src/barcode_locator.js index 8d7b2bf..ded574f 100644 --- a/src/barcode_locator.js +++ b/src/barcode_locator.js @@ -1,8 +1,8 @@ /* jshint undef: true, unused: true, browser:true, devel: true */ /* global define, mat2, vec2 */ -define("barcode_locator", ["image_wrapper", "cv_utils", "rasterizer", "tracer", "skeletonizer", "array_helper", "image_debug"], -function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, ImageDebug) { +define("barcode_locator", ["image_wrapper", "cv_utils", "rasterizer", "tracer", "skeletonizer", "array_helper", "image_debug", "gl-matrix"], +function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, ImageDebug, glMatrix) { var _config, _currentImageWrapper, @@ -25,6 +25,8 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I _numPatches = {x: 0, y: 0}, _inputImageWrapper, _skeletonizer, + vec2 = glMatrix.vec2, + mat2 = glMatrix.mat2, self = this; function initBuffers() { @@ -100,15 +102,14 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I 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)]); + transMat = mat2.clone([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]); + vec2.transformMat2(patch.box[j], patch.box[j], transMat); } if (_config.boxFromPatches.showTransformed) { @@ -143,9 +144,9 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I scale = _config.halfSample ? 2 : 1; // reverse rotation; - transMat = mat2.inverse(transMat); + transMat = mat2.invert(transMat, transMat); for ( j = 0; j < 4; j++) { - mat2.xVec2(transMat, box[j]); + vec2.transformMat2(box[j], box[j], transMat); } if (_config.boxFromPatches.showBB) { @@ -153,7 +154,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I } for ( j = 0; j < 4; j++) { - vec2.scale(box[j], scale); + vec2.scale(box[j], box[j], scale); } return box; @@ -377,10 +378,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I 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])], + box : [vec2.clone([x, y]), vec2.clone([x + _subImageWrapper.size.x, y]), vec2.clone([x + _subImageWrapper.size.x, y + _subImageWrapper.size.y]), vec2.clone([x, y + _subImageWrapper.size.y])], moments : matchingMoments, rad : avg, - vec : vec2.create([Math.cos(avg), Math.sin(avg)]) + vec : vec2.clone([Math.cos(avg), Math.sin(avg)]) }; patchesFound.push(patch); } diff --git a/src/cluster.js b/src/cluster.js index f44ab46..650a2c8 100644 --- a/src/cluster.js +++ b/src/cluster.js @@ -1,9 +1,10 @@ /* jshint undef: true, unused: true, browser:true, devel: true */ -/* global define, vec2 */ +/* global define */ -define(function() { +define(["gl-matrix"], function(glMatrix) { "use strict"; - + + var vec2 = glMatrix.vec2; /** * Creates a cluster for grouping similar orientations of datapoints */ @@ -11,7 +12,7 @@ define(function() { create : function(point, threshold) { var points = [], center = { rad : 0, - vec : vec2.create([0, 0]) + vec : vec2.clone([0, 0]) }, pointMap = {}; function init() { @@ -30,7 +31,7 @@ define(function() { sum += points[i].rad; } center.rad = sum / points.length; - center.vec = vec2.create([Math.cos(center.rad), Math.sin(center.rad)]); + center.vec = vec2.clone([Math.cos(center.rad), Math.sin(center.rad)]); } init(); diff --git a/src/cv_utils.js b/src/cv_utils.js index 12ff3d0..39a37fa 100644 --- a/src/cv_utils.js +++ b/src/cv_utils.js @@ -1,7 +1,7 @@ /* jshint undef: true, unused: true, browser:true, devel: true */ -/* global define, vec2, vec3 */ +/* global define */ -define(['cluster', 'glMatrixAddon', "array_helper"], function(Cluster2, glMatrixAddon, ArrayHelper) { +define(['cluster', "array_helper", "gl-matrix"], function(Cluster2, ArrayHelper, glMatrix) { "use strict"; /* @@ -14,7 +14,9 @@ define(['cluster', 'glMatrixAddon', "array_helper"], function(Cluster2, glMatrix * @class Represents a collection of useful CV algorithms/functions */ - var CVUtils = {}; + var CVUtils = {}, + vec2 = glMatrix.vec2, + vec3 = glMatrix.vec3; /** * @param x x-coordinate @@ -26,10 +28,10 @@ define(['cluster', 'glMatrixAddon', "array_helper"], function(Cluster2, glMatrix x : x, y : y, toVec2 : function() { - return vec2.create([this.x, this.y]); + return vec2.clone([this.x, this.y]); }, toVec3 : function() { - return vec3.create([this.x, this.y, 1]); + return vec3.clone([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); diff --git a/src/glMatrixAddon.js b/src/glMatrixAddon.js index 798c4aa..3c1ddad 100644 --- a/src/glMatrixAddon.js +++ b/src/glMatrixAddon.js @@ -224,7 +224,7 @@ mat2.inverse = function(mat){ }; var vec2 = {}; -vec2.create = function(vec){ +vec2.clone = function(vec){ var dest; if(vec) { @@ -324,7 +324,7 @@ vec2.length = function(vec){ }; vec2.perspectiveProject = function(vec){ - var result = vec2.create(vec); + var result = vec2.clone(vec); return vec2.scale(result, 1/vec[2]); }; @@ -333,7 +333,7 @@ vec2.perspectiveProject = function(vec){ * @returns vec2 projected vector */ vec3.project = function(vec){ - return vec2.scale(vec2.create(vec), 1/vec[2]); + return vec2.scale(vec2.clone(vec), 1/vec[2]); }; var vec6 = {}; diff --git a/src/image_wrapper.js b/src/image_wrapper.js index 5e21795..b520fa7 100644 --- a/src/image_wrapper.js +++ b/src/image_wrapper.js @@ -1,14 +1,17 @@ /* jshint undef: true, unused: true, browser:true, devel: true */ -/* global define, vec2, mat2 */ +/* global define */ define([ "subImage", "cv_utils", - "array_helper" + "array_helper", + "gl-matrix" ], - function(SubImage, CVUtils, ArrayHelper) { + function(SubImage, CVUtils, ArrayHelper, glMatrix) { 'use strict'; + var vec2 = glMatrix.vec2, + mat2 = glMatrix.mat2; /** * Represents a basic image combining the data and size. @@ -62,11 +65,11 @@ define([ */ 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 across = vec2.clone([M[0], M[2]]); + var down = vec2.clone([M[1], M[3]]); var defaultValue = 0; - var p0 = vec2.subtract(inOrig, mat2.xVec2(M, outOrig, vec2.create()), vec2.create()); + var p0 = vec2.subtract(inOrig, mat2.xVec2(M, outOrig, vec2.clone()), vec2.clone()); var min_x = p0[0], min_y = p0[1]; var max_x = min_x, max_y = min_y; @@ -94,7 +97,7 @@ define([ else max_y += h * down[1]; - var carrigeReturn = vec2.subtract(down, vec2.scale(across, w, vec2.create()), vec2.create()); + var carrigeReturn = vec2.subtract(down, vec2.scale(across, w, vec2.clone()), vec2.clone()); if (min_x >= 0 && min_y >= 0 && max_x < iw - 1 && max_y < ih - 1) { p = p0; @@ -346,7 +349,7 @@ define([ label.theta += 180; } label.rad = tmp > PI ? tmp - PI : tmp; - label.vec = vec2.create([Math.cos(tmp), Math.sin(tmp)]); + label.vec = vec2.clone([Math.cos(tmp), Math.sin(tmp)]); result.push(label); } } diff --git a/src/quagga.js b/src/quagga.js index b7ee257..45a141f 100644 --- a/src/quagga.js +++ b/src/quagga.js @@ -1,5 +1,5 @@ /* jshint undef: true, unused: true, browser:true, devel: true, evil: true */ -/* global define, vec2 */ +/* global define */ define([ @@ -15,7 +15,8 @@ define([ "events", "camera_access", "image_debug", - "cv_utils"], + "cv_utils", + "gl-matrix"], function(Code128Reader, EANReader, InputStream, @@ -28,7 +29,8 @@ function(Code128Reader, Events, CameraAccess, ImageDebug, - CVUtils) { + CVUtils, + glMatrix) { "use strict"; var _inputStream, @@ -48,7 +50,8 @@ function(Code128Reader, _boxSize, _decoder, _workerPool = [], - _onUIThread = true; + _onUIThread = true, + vec2 = glMatrix.vec2; function initializeData(imageWrapper) { initBuffers(imageWrapper); @@ -56,20 +59,22 @@ function(Code128Reader, } 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"; + if (typeof document !== "undefined") { + 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"; + } } } } @@ -161,35 +166,37 @@ function(Code128Reader, } 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 = _inputStream.getWidth(); - _canvasContainer.dom.image.height = _inputStream.getHeight(); - - _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); + if (typeof document !== "undefined") { + 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); + } } - var clearFix = document.createElement("br"); - clearFix.setAttribute("clear", "all"); - if($viewport) { - $viewport.appendChild(clearFix); + _canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d"); + _canvasContainer.dom.image.width = _inputStream.getWidth(); + _canvasContainer.dom.image.height = _inputStream.getHeight(); + + _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 = _inputStream.getWidth(); + _canvasContainer.dom.overlay.height = _inputStream.getHeight(); } - _canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d"); - _canvasContainer.dom.overlay.width = _inputStream.getWidth(); - _canvasContainer.dom.overlay.height = _inputStream.getHeight(); } function initBuffers(imageWrapper) { @@ -204,10 +211,10 @@ function(Code128Reader, console.log(_inputImageWrapper.size); _boxSize = [ - vec2.create([20, _inputImageWrapper.size.y / 2 - 100]), - vec2.create([20, _inputImageWrapper.size.y / 2 + 100]), - vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]), - vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100]) + vec2.clone([20, _inputImageWrapper.size.y / 2 - 100]), + vec2.clone([20, _inputImageWrapper.size.y / 2 + 100]), + vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]), + vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100]) ]; BarcodeLocator.init(_inputImageWrapper, _config.locator); } diff --git a/src/skeletonizer.js b/src/skeletonizer.js index 166f305..41af7f4 100644 --- a/src/skeletonizer.js +++ b/src/skeletonizer.js @@ -4,6 +4,16 @@ define(function() { "use strict"; + Math.imul = Math.imul || function(a, b) { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + // the shift by 0 fixes the sign on the high part + // the final |0 converts the unsigned value into a signed value + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); + }; + /* @preserve ASM BEGIN */ function Skeletonizer(stdlib, foreign, buffer) { "use asm";