diff --git a/example/file_input.js b/example/file_input.js index 0a638fc..709fcba 100644 --- a/example/file_input.js +++ b/example/file_input.js @@ -53,34 +53,47 @@ $(function() { $(".controls .reader-config-group").off("change", "input, select"); $(".controls button").off("click"); }, + detect: function() { + var scanner = this.scanner; + scanner.detect() + .then(function(result) { + console.log(result); + addToResults(scanner, result); + return result; + }) + .catch(function(result) { + console.log('Not found', result); + return result; + }) + .then(function(result) { + drawResult(scanner, result); + this.attachListeners(); + }.bind(this)); + }, decode: function(file) { this.detachListeners(); var size = this.state.inputStream.size; console.log("decode..."); - Quagga.fromImage(file, { - constraints: {width: size, height: size}, - locator: this.state.locator, - decoder: this.state.decoder, - }) - .then(function(scanner) { - scanner.detect() - .then(function(result) { - console.log(result); - addToResults(scanner, result); - return result; + if (!this.scanner) { + Quagga.fromImage({ + constraints: {src: file, width: size, height: size}, + locator: this.state.locator, + decoder: this.state.decoder, }) - .catch(function(result) { - console.log('Not found', result); - return result; - }) - .then(function(result) { - drawResult(scanner, result); - this.attachListeners(); + .then(function(scanner) { + this.scanner = scanner; + this.detect(); }.bind(this)); - }.bind(this)); + } else { + this.scanner.applyConfig({ + constraints: {src: file, width: size, height: size}, + locator: this.state.locator, + decoder: this.state.decoder, + }) + .then(this.detect.bind(this)); + } - // Quagga.fromCamera(constraints) // Quagga.fromSource(); // Quagga.fromPixelCapture(); }, diff --git a/example/live_w_locator.js b/example/live_w_locator.js index 06683ce..ba90e84 100644 --- a/example/live_w_locator.js +++ b/example/live_w_locator.js @@ -101,7 +101,8 @@ $(function() { console.log(JSON.stringify(this.state)); - this.scanner.applyConfig({ + this.scanner + .applyConfig({ constraints: this.state.inputStream.constraints, locator: this.state.locator, decoder: this.state.decoder, diff --git a/src/input/CameraSource.js b/src/input/CameraSource.js index 7661771..03e5969 100644 --- a/src/input/CameraSource.js +++ b/src/input/CameraSource.js @@ -3,7 +3,7 @@ import {determineOrientation} from '../common/device'; import CameraAccess from './camera_access'; import {getViewport} from '../common/utils'; import {generateSourceInterface} from './SourceInterface'; -import {Scope} from './input/SourceScope'; +import {Scope} from './SourceScope'; const ConstraintPresets = [ { diff --git a/src/input/ImageSource.js b/src/input/ImageSource.js index d24feb1..839a8bd 100644 --- a/src/input/ImageSource.js +++ b/src/input/ImageSource.js @@ -1,82 +1,102 @@ import {findTagsInObjectURL} from './exif_helper'; -import {generateSourceInterface} from './SourceInterface'; +import {Source} from './SourceInterface'; -export function fromImage(input, constraints = {width: 800, height: 800, channels: 3}) { - var $image = null; - var src = null; - if (typeof input === 'string') { - // data or url, or queryString - $image = new Image(); - src = input; - } else if (input instanceof HTMLImageElement) { - $image = input; - } else if (input instanceof File) { - $image = new Image(); - src = URL.createObjectURL(input); - } else { - return Promise.reject("fromImage needs a src, HTMLImageElement or File"); +class ImageSource extends Source { + constructor() { + super("IMAGE"); + this._$image = null; + this._src = null; + this.tags = null; + this.colorChannels = 3; } - return new Promise(function(resolve, reject) { - if (src || !$image.complete) { - console.log('Adding eventlistener'); - $image.addEventListener('load', function() { - resolve(); - }, false); - $image.addEventListener('error', function(e) { - reject(e); - }, false); - if (src) { - console.log(`Setting src = ${src}`); - $image.src = src; + + applyConstraints(newConstraints) { + this.constraints = newConstraints; + this.colorChannels = this.constraints.channels || this.colorChannels; + return this._applyInput(this.constraints.src) + ._loadResource() + .then(() => findTagsInObjectURL(this._src, ['orientation'])) + .then((tags) => {this.tags = tags;}) + .then(this._determineDimensions.bind(this)) + .then(() => this); + } + + _loadResource() { + return new Promise((resolve, reject) => { + if (this._src || !this._$image.complete) { + this._$image.addEventListener('load', resolve, false); + this._$image.addEventListener('error', reject, false); + if (this._src) { + console.log(`Setting src = ${this._src}`); + this._$image.src = this._src; + } + } else { + return resolve(); } + }); + } + + _applyInput(input) { + if (typeof input === 'string') { + // data or url, or queryString + this._$image = new Image(); + this._src = input; + } else if (input instanceof HTMLImageElement) { + this._$image = input; + } else if (input instanceof File) { + this._$image = new Image(); + this._src = URL.createObjectURL(input); } else { - return resolve(); + throw new Error("fromImage needs a src, HTMLImageElement or File"); } - }) - .then(() => findTagsInObjectURL(src, ['orientation'])) - .then((tags) => { - let width = $image.naturalWidth; - let height = $image.naturalHeight; - if (tags && tags.orientation) { - switch (tags.orientation) { + return this; + } + + _determineDimensions() { + let width = this._$image.naturalWidth; + let height = this._$image.naturalHeight; + let desiredWidth = this.constraints.width; + if (this.tags && this.tags.orientation) { + switch (this.tags.orientation) { case 6: case 8: - width = $image.naturalHeight; - height = $image.naturalWidth; + width = this._$image.naturalHeight; + height = this._$image.naturalWidth; } } + const imageAR = width / height; - const calculatedWidth = imageAR > 1 ? constraints.width : Math.floor((imageAR) * constraints.width); - const calculatedHeight = imageAR > 1 ? Math.floor((1 / imageAR) * constraints.width) : constraints.width; - const colorChannels = constraints.channels || 3; + const calculatedWidth = imageAR > 1 ? desiredWidth : Math.floor((imageAR) * desiredWidth); + const calculatedHeight = imageAR > 1 ? Math.floor((1 / imageAR) * desiredWidth) : desiredWidth; - return Object.assign(generateSourceInterface(), { - type: "IMAGE", - colorChannels, - tags, - getDimensions() { - return { - viewport: { - width: $image.naturalWidth, // AR - height: $image.naturalHeight, // AR - x: 0, // AR - y: 0, // AR - }, - canvas: { - width: calculatedWidth, // AR - height: calculatedHeight, // AR - }, - }; - }, - getDrawable: function() { - return $image; - }, - getLabel: function() { - return $image.src; + this._dimensions = { + viewport: { + width, // AR + height, // AR + x: 0, // AR + y: 0, // AR }, - getConstraints: function() { - return constraints; + canvas: { + width: calculatedWidth, // AR + height: calculatedHeight, // AR }, - }); - }); + }; + } + + getDimensions() { + return this._dimensions; + } + + getDrawable() { + return this._$image; + } + + getLabel() { + return this._$image.src; + } +} + +export function fromImage(constraints = {width: 800, height: 800, channels: 3}) { + const imageSource = new ImageSource(); + return imageSource.applyConstraints(constraints); } diff --git a/src/input/PixelCapture.js b/src/input/PixelCapture.js index 570f298..e0afe7d 100644 --- a/src/input/PixelCapture.js +++ b/src/input/PixelCapture.js @@ -98,6 +98,7 @@ export function fromSource(source, {target = "#interactive.viewport"} = {}) { return { grabFrameData: function grabFrameData({clipping} = {}) { + const frame = source.getDrawable(); const {viewport, canvas: canvasSize} = source.getDimensions(); const sx = viewport.x; const sy = viewport.y; @@ -121,12 +122,12 @@ export function fromSource(source, {target = "#interactive.viewport"} = {}) { return sleep(100).then(grabFrameData); } - if (!(drawable instanceof HTMLCanvasElement)) { + if (!(frame instanceof HTMLCanvasElement)) { drawImage( canvasSize, ctx, source, - drawable, + frame, sx, sy, sWidth, diff --git a/src/input/SourceInterface.js b/src/input/SourceInterface.js index bc6ff7b..b7c5359 100644 --- a/src/input/SourceInterface.js +++ b/src/input/SourceInterface.js @@ -1,24 +1,22 @@ -const viewport = { - x: 0, - y: 0, - width: 0, - height: 0, -}; +class Source { + constructor(type) { + this.type = type; + } + getDimensions() {} + getConstraints() {} + getDrawable() {} + applyConstraints() {} + getLabel() {} + stop() {} + getScope() {} +} -const canvas = { - height: 0, - width: 0, -}; +export {Source}; export function generateSourceInterface() { return { type: "INTERFACE", - getDimensions() { - return { - viewport, - canvas, - }; - }, + getDimensions() {}, getConstraints() {}, getDrawable() {}, applyConstraints() {}, @@ -26,4 +24,4 @@ export function generateSourceInterface() { stop() {}, getScope() {}, }; -} +}; diff --git a/src/quagga.js b/src/quagga.js index e401f65..5d6b710 100644 --- a/src/quagga.js +++ b/src/quagga.js @@ -43,17 +43,11 @@ function fromConfig(pixelCapturer, config) { scanner.start(); return Promise.resolve(true); } - pendingStart = new Promise((resolve, reject) => { - scanner.init(currentConfig, (error) => { - if (error) { - console.log(error); - reject(error); - } - initialized = true; - scanner.start(); - resolve(); - pendingStart = null; - }); + pendingStart = scanner.init(currentConfig) + .then(() => { + initialized = true; + scanner.start(); + pendingStart = null; }); return pendingStart; }, @@ -88,14 +82,14 @@ function fromConfig(pixelCapturer, config) { }) }; } else { - return new Promise((resolve, reject) => { - scanner.decodeSingle(currentConfig, (result) => { - if (result && result.codeResult && result.codeResult.code) { - return resolve(result); - } - return reject(result); - }); - }); + let pendingDecodeSingle = Promise.resolve(); + if (!initialized) { + pendingDecodeSingle = pendingDecodeSingle + .then(scanner.init.bind(scanner, currentConfig)) + .then(() => {initialized = true;}); + } + return pendingDecodeSingle + .then(scanner.decodeSingle.bind(scanner)); } }, registerResultCollector(resultCollector) { @@ -106,6 +100,7 @@ function fromConfig(pixelCapturer, config) { }, applyConfig(newConfig) { const normalizedConfig = merge({}, Config, currentConfig, newConfig); + const wasRunning = scanner.isRunning(); let promise = Promise.resolve(); if (hasConfigChanged(currentConfig, normalizedConfig, "constraints")) { console.log("constraints changed!", currentConfig.constraints, normalizedConfig.constraints); @@ -117,6 +112,9 @@ function fromConfig(pixelCapturer, config) { if (hasConfigChanged(currentConfig, normalizedConfig)) { console.log("config changed!"); promise = promise.then(() => scanner.applyConfig(normalizedConfig)); + if (wasRunning) { + promise = promise.then(scanner.start.bind(scanner)); + } } currentConfig = normalizedConfig; return promise; @@ -134,10 +132,13 @@ function fromSource(config, source) { function createApi() { return { - fromImage(image, options) { + fromImage(options) { const config = merge({}, Config, options); return Source - .fromImage(image, config.constraints) + .fromImage(config.constraints, { + target: config.target, + scope: Source.Scope.INTERNAL, + }) .then(fromSource.bind(null, config)); }, fromCamera(options) { diff --git a/src/scanner.js b/src/scanner.js index c3d3a9a..669c9d2 100644 --- a/src/scanner.js +++ b/src/scanner.js @@ -346,7 +346,7 @@ function createScanner(pixelCapturer) { x: e.data.size.x, y: e.data.size.y }, new Uint8Array(e.data.imageData)); - scanner.init(config, ready, imageWrapper); + scanner.init(config, imageWrapper).then(ready); scanner.subscribe("processed", onProcessed); } else if (e.data.cmd === 'process') { imageWrapper.data = new Uint8Array(e.data.imageData); @@ -417,15 +417,17 @@ function createScanner(pixelCapturer) { } return { - init: function(config, cb, imageWrapper) { + init: function(config, imageWrapper) { + _stopped = true; _config = merge({}, Config, config); if (imageWrapper) { _onUIThread = false; initBuffers(imageWrapper); - return cb(); + return Promise.resolve(); } else { - return setup(_config).then(cb); + adjustWorkerPool(0); + return setup(_config); } }, start: function() { @@ -443,10 +445,7 @@ function createScanner(pixelCapturer) { } }, applyConfig(newConfig) { - _stopped = true; - adjustWorkerPool(0); - _config = merge({}, Config, _config, newConfig); - return setup(_config).then(start); + return this.init(newConfig); }, pause: function() { _stopped = true; @@ -462,11 +461,14 @@ function createScanner(pixelCapturer) { _resultCollector = resultCollector; } }, - decodeSingle: function(config, resultCallback) { - this.init(config, () => { + decodeSingle() { + return new Promise((resolve, reject) => { _events.once("processed", (result) => { this.stop(); - resultCallback.call(null, result); + if (result && result.codeResult && result.codeResult.code) { + return resolve(result); + } + return reject(result); }, true); start(); });