Skip to content

Commit

Permalink
separate most Quagga module code outside of the static interface to a…
Browse files Browse the repository at this point in the history
… class

- after this commit, there should be no functional change, but the bulk
  of the code outside the static interface has been moved to a class,
  instead of global variables. An instance of that class is created at the
  top as a global.
  • Loading branch information
ericblade committed Apr 16, 2020
1 parent 73f9762 commit b9cade6
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 240 deletions.
1 change: 1 addition & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ProdConfig from './config.prod';

declare var ENV: QuaggaBuildEnvironment;

// @ts-ignore // TODO: this produces a bizarre typescript error
const QuaggaConfig: QuaggaJSConfigObject = ENV.development
? DevConfig
: ENV.node
Expand Down
4 changes: 2 additions & 2 deletions src/input/camera_access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function deprecatedConstraints(videoConstraints: MediaTrackConstraintsWithDeprec
return normalized;
}

export function pickConstraints(videoConstraints: MediaTrackConstraintsWithDeprecated): Promise<MediaStreamConstraints> {
export function pickConstraints(videoConstraints: MediaTrackConstraintsWithDeprecated = {}): Promise<MediaStreamConstraints> {
const video = deprecatedConstraints(videoConstraints);

if (video && video.deviceId && video.facingMode) {
Expand All @@ -95,7 +95,7 @@ function getActiveTrack(): MediaStreamTrack | null {
* Used for accessing information about the active stream track and available video devices.
*/
const QuaggaJSCameraAccess = {
request: function(video: HTMLVideoElement, videoConstraints: MediaTrackConstraintsWithDeprecated): Promise<any> {
request: function(video: HTMLVideoElement, videoConstraints?: MediaTrackConstraintsWithDeprecated): Promise<any> {
return pickConstraints(videoConstraints)
.then((newConstraints) => initCamera(video, newConstraints))
.catch(err => {
Expand Down
240 changes: 9 additions & 231 deletions src/quagga.js
Original file line number Diff line number Diff line change
@@ -1,240 +1,18 @@
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
import ImageWrapper from './common/image_wrapper';
import BarcodeLocator from './locator/barcode_locator';
import BarcodeDecoder from './decoder/barcode_decoder';
import BarcodeReader from './reader/barcode_reader';
import Events from './common/events';
import CameraAccess from './input/camera_access';
import ImageDebug from './common/image_debug';
import ResultCollector from './analytics/result_collector';
import Config from './config/config';
import BrowserInputStream, { NodeInputStream } from './input/input_stream';
import BrowserFrameGrabber, { NodeFrameGrabber } from './input/frame_grabber';
import { merge } from 'lodash';
import { clone } from 'gl-vec2';
import { QuaggaContext } from './QuaggaContext';

import setupInputStream from './quagga/setupInputStream.ts';
import _getViewPort from './quagga/getViewPort.ts';
import _initBuffers from './quagga/initBuffers.ts';
import _initCanvas from './quagga/initCanvas';
import { moveBox, moveLine } from './quagga/transform';
import * as QWorkers from './quagga/qworker.ts';

const vec2 = { clone };

const InputStream = typeof window === 'undefined' ? NodeInputStream : BrowserInputStream;
const FrameGrabber = typeof window === 'undefined' ? NodeFrameGrabber : BrowserFrameGrabber;

// export BarcodeReader and other utilities for external plugins
export { BarcodeReader, BarcodeDecoder, ImageWrapper, ImageDebug, ResultCollector, CameraAccess };

const _context = new QuaggaContext();

function initBuffers(imageWrapper) {
const { inputImageWrapper, boxSize } = _initBuffers(_context.inputStream, imageWrapper, _context.config.locator);
_context.inputImageWrapper = inputImageWrapper;
_context.boxSize = boxSize;
}

function initializeData(imageWrapper) {
initBuffers(imageWrapper);
_context.decoder = BarcodeDecoder.create(_context.config.decoder, _context.inputImageWrapper);
}

function getViewPort() {
const { target } = _context.config.inputStream;
return _getViewPort(target);
}

function ready(cb) {
_context.inputStream.play();
cb();
}

function initCanvas() {
_context.canvasContainer = _initCanvas(_context);
}

function canRecord(cb) {
BarcodeLocator.checkImageConstraints(_context.inputStream, _context.config.locator);
initCanvas(_context.config);
_context.framegrabber = FrameGrabber.create(_context.inputStream, _context.canvasContainer.dom.image);

QWorkers.adjustWorkerPool(_context.config.numOfWorkers, _context.config, _context.inputStream, function () {
if (_context.config.numOfWorkers === 0) {
initializeData();
}
ready(cb);
});
}

function initInputStream(cb) {
const { type: inputType, constraints } = _context.config.inputStream;
const { video, inputStream } = setupInputStream(inputType, getViewPort(), InputStream);

if (inputType === 'LiveStream') {
CameraAccess.request(video, constraints)
.then(() => inputStream.trigger('canrecord'))
.catch((err) => cb(err));
}

inputStream.setAttribute('preload', 'auto');
inputStream.setInputStream(_context.config.inputStream);
inputStream.addEventListener('canrecord', canRecord.bind(undefined, cb));

_context.inputStream = inputStream;
}

function getBoundingBoxes() {
if (_context.config.locate) {
return BarcodeLocator.locate();
} else {
return [[
vec2.clone(_context.boxSize[0]),
vec2.clone(_context.boxSize[1]),
vec2.clone(_context.boxSize[2]),
vec2.clone(_context.boxSize[3])]];
}
}

function transformResult(result) {
const topRight = _context.inputStream.getTopRight();
const xOffset = topRight.x;
const yOffset = topRight.y;

if (xOffset === 0 && yOffset === 0) {
return;
}

if (result.barcodes) {
result.barcodes.forEach((barcode) => transformResult(barcode));
}

if (result.line && result.line.length === 2) {
moveLine(result.line, xOffset, yOffset);
}

if (result.box) {
moveBox(result.box, xOffset, yOffset);
}

if (result.boxes && result.boxes.length > 0) {
for (let i = 0; i < result.boxes.length; i++) {
moveBox(result.boxes[i], xOffset, yOffset);
}
}
}

function addResult(result, imageData) {
if (!imageData || !_context.resultCollector) {
return;
}

if (result.barcodes) {
result.barcodes.filter(barcode => barcode.codeResult)
.forEach(barcode => addResult(barcode, imageData));
} else if (result.codeResult) {
_context.resultCollector.addResult(imageData, _context.inputStream.getCanvasSize(), result.codeResult);
}
}

function hasCodeResult(result) {
return result && (result.barcodes ?
result.barcodes.some(barcode => barcode.codeResult) :
result.codeResult);
}

function publishResult(result, imageData) {
let resultToPublish = result;

if (result && _context.onUIThread) {
transformResult(result);
addResult(result, imageData);
resultToPublish = result.barcodes || result;
}

Events.publish('processed', resultToPublish);
if (hasCodeResult(result)) {
Events.publish('detected', resultToPublish);
}
}

function locateAndDecode() {
const boxes = getBoundingBoxes();
if (boxes) {
const decodeResult = _context.decoder.decodeFromBoundingBoxes(boxes) || {};
decodeResult.boxes = boxes;
publishResult(decodeResult, _context.inputImageWrapper.data);
} else {
const imageResult = _context.decoder.decodeFromImage(_context.inputImageWrapper);
if (imageResult) {
publishResult(imageResult, _context.inputImageWrapper.data);
} else {
publishResult({ codeResult: { code: null } });
}
}
}

function update() {
if (_context.onUIThread) {
const workersUpdated = QWorkers.updateWorkers(_context.framegrabber);
if (!workersUpdated) {
_context.framegrabber.attachData(_context.inputImageWrapper.data);
if (_context.framegrabber.grab()) {
if (!workersUpdated) {
locateAndDecode();
}
}
}
} else {
_context.framegrabber.attachData(_context.inputImageWrapper.data);
_context. _framegrabber.grab();
locateAndDecode();
}
}

function startContinuousUpdate() {
var next = null,
delay = 1000 / (_context.config.frequency || 60);

_context.stopped = false;
(function frame(timestamp) {
next = next || timestamp;
if (!_context.stopped) {
if (timestamp >= next) {
next += delay;
update();
}
window.requestAnimFrame(frame);
}
}(performance.now()));
}

function start() {
if (_context.onUIThread && _context.config.inputStream.type === 'LiveStream') {
startContinuousUpdate();
} else {
update();
}
}

function setReaders(readers) {
if (_context.decoder) {
_context.decoder.setReaders(readers);
}
QWorkers.setReaders(readers);
}
import Quagga from './quagga/quagga';

function registerReader(name, reader) {
// load it to the module
BarcodeDecoder.registerReader(name, reader);
// then make sure any running instances of decoder and workers know about it
if (_context.decoder) {
_context.decoder.registerReader(name, reader);
}
QWorkers.registerReader(name, reader);
}
const instance = new Quagga();
const _context = instance.context;

export default {
init: function (config, cb, imageWrapper) {
Expand All @@ -245,16 +23,16 @@ export default {
}
if (imageWrapper) {
_context.onUIThread = false;
initializeData(imageWrapper);
instance.initializeData(imageWrapper);
if (cb) {
cb();
}
} else {
initInputStream(cb);
instance.initInputStream(cb);
}
},
start: function () {
start();
instance.start();
},
stop: function () {
_context.stopped = true;
Expand All @@ -280,10 +58,10 @@ export default {
Events.unsubscribe('processed', callback);
},
setReaders: function (readers) {
setReaders(readers);
instance.setReaders(readers);
},
registerReader: function (name, reader) {
registerReader(name, reader);
instance.registerReader(name, reader);
},
registerResultCollector: function (resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
Expand Down Expand Up @@ -341,7 +119,7 @@ export default {
}
resolve(result);
}, true);
start();
instance.start();
});
} catch (err) {
this.inDecodeSingle = false;
Expand Down
2 changes: 1 addition & 1 deletion src/quagga/getViewPort.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default function getViewPort(target?: HTMLElement | string) {
export default function getViewPort(target?: Element | string): Element | null {
if (typeof document === 'undefined') {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion src/quagga/initBuffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ declare var ENV: QuaggaBuildEnvironment;

// TODO: need typescript def for inputstream
// TODO: need typescript def for BarcodeLocator
export default function initBuffers(inputStream: any, imageWrapper: ImageWrapper, locator: any) {
export default function initBuffers(inputStream: any, imageWrapper: ImageWrapper | undefined, locator: any) {
const inputImageWrapper = imageWrapper ? imageWrapper : new ImageWrapper({
x: inputStream.getWidth(),
y: inputStream.getHeight(),
Expand Down
Loading

0 comments on commit b9cade6

Please sign in to comment.