Skip to content
Snippets Groups Projects
Commit e3650b71 authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

qr-code-scanner: Export an API for scanning single images

In greenlight we copied this code for scanning images, either from
real image files, or from PDF files converted to images via pdf.js.

To avoid duplication clean up the internal API and expose it.

Also write some unit tests for the scan engine while at it.
parent 0987064d
No related branches found
No related tags found
No related merge requests found
Pipeline #127389 failed
export class ScanResult {
constructor() {
this.data = null;
this.cornerPoints = null;
}
}
export class QrCodeScannerEngine {
constructor() {
this._engine = null;
this._canvas = document.createElement('canvas');
this._scanner = null;
}
/**
* Scan am image like thing for a QR code. Returns null if none is found.
* The region to scan in can be restricted via "options".
*
* @param {*} image
* @param {?object} options
* @param {number} options.x
* @param {number} options.y
* @param {number} options.width
* @param {number} options.height
* @returns {?ScanResult}
*/
async scanImage(image, options = null) {
if (this._scanner === null) {
this._scanner = (await import('qr-scanner')).default;
}
if (this._engine === null) {
this._engine = await this._scanner.createQrEngine();
}
try {
let tmp = await this._scanner.scanImage(image, {
scanRegion: options ?? null,
qrEngine: this._engine,
canvas: this._canvas,
});
let res = new ScanResult();
res.data = tmp.data;
res.cornerPoints = tmp.cornerPoints;
return res;
} catch (e) {
return null;
}
}
}
import {QrCodeScanner} from './qr-code-scanner.js';
export {QrCodeScanner};
export {QrCodeScanner} from './qr-code-scanner.js';
export {QrCodeScannerEngine, ScanResult} from './engine.js';
......@@ -7,6 +7,7 @@ import {Icon, MiniSpinner} from '@dbp-toolkit/common';
import {classMap} from 'lit/directives/class-map.js';
import {Mutex} from 'async-mutex';
import {getIconSVGURL} from '@dbp-toolkit/common';
import {QrCodeScannerEngine, ScanResult} from './engine.js';
/**
* Returns the ID for the most important device
......@@ -116,34 +117,6 @@ async function createVideoElement(deviceId) {
return null;
}
class QRScanner {
constructor() {
this._engine = null;
this._canvas = document.createElement('canvas');
this._scanner = null;
}
async scan(canvas, x, y, width, height) {
if (this._scanner === null) {
this._scanner = (await import('qr-scanner')).default;
}
if (this._engine === null) {
this._engine = await this._scanner.createQrEngine();
}
try {
return {
data: await this._scanner.scanImage(canvas, {
scanRegion: {x: x, y: y, width: width, height: height},
qrEngine: this._engine,
canvas: this._canvas,
}),
};
} catch (e) {
return null;
}
}
}
export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
constructor() {
super();
......@@ -265,10 +238,11 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
}
this._askPermission = false;
/** @type {?ScanResult} */
let lastCode = null;
let lastSentData = null;
let detector = new QRScanner();
let detector = new QrCodeScannerEngine();
let detectorRunning = false;
const tick = () => {
......@@ -300,7 +274,12 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
if (!detectorRunning) {
detectorRunning = true;
detector
.scan(canvasElement, maskStartX, maskStartY, maskWidth, maskHeight)
.scanImage(canvasElement, {
x: maskStartX,
y: maskStartY,
width: maskWidth,
height: maskHeight,
})
.then((code) => {
detectorRunning = false;
// if we got restarted then the video element is new, so stop then.
......@@ -308,7 +287,7 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
lastCode = code;
if (code) {
let currentData = code.data.data;
let currentData = code.data;
if (lastSentData !== currentData) {
this._outputData = currentData;
this.dispatchEvent(
......@@ -327,7 +306,7 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
});
}
let matched = lastCode ? lastCode.data.data.match(this.matchRegex) !== null : false;
let matched = lastCode ? lastCode.data.match(this.matchRegex) !== null : false;
//draw mask
canvas.beginPath();
......
import {assert} from '@esm-bundle/chai';
import '../src/dbp-qr-code-scanner';
import {QrCodeScannerEngine} from '../src';
suite('dbp-qr-code-scanner basics', () => {
let node;
......@@ -19,3 +20,30 @@ suite('dbp-qr-code-scanner basics', () => {
assert.isNotNull(node.shadowRoot);
});
});
suite('scan image', () => {
test('should detect', async () => {
let engine = new QrCodeScannerEngine();
let image = new Image();
image.setAttribute(
'src',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmAQMAAACS83vtAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9bi1IqDu0gopChdbIgKuKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE0clJ0UVK/F9SaBHjwXE/3t173L0D/M0qU82ecUDVLCOTSgq5/KrQ+4ogIghhBHGJmfqcKKbhOb7u4ePrXYJneZ/7c/QrBZMBPoF4lumGRbxBPL1p6Zz3iaOsLCnE58RjBl2Q+JHrsstvnEsO+3lm1Mhm5omjxEKpi+UuZmVDJZ4ijimqRvn+nMsK5y3OarXO2vfkLwwXtJVlrtMcRgqLWIIIATLqqKAKCwlaNVJMZGg/6eEfcvwiuWRyVcDIsYAaVEiOH/wPfndrFicn3KRwEgi+2PZHHOjdBVoN2/4+tu3WCRB4Bq60jr/WBGY+SW90tNgRMLANXFx3NHkPuNwBBp90yZAcKUDTXywC72f0TXkgcguE1tze2vs4fQCy1FX6Bjg4BEZLlL3u8e6+7t7+PdPu7wdo/XKjkhoyogAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+YFEwogFupCMRsAAAAGUExURQAAAP///6XZn90AAAABYktHRAH/Ai3eAAAAuUlEQVQI12P4DwR/GDDJD1KfFWwYvt/ctn4Pw5fYnRpAMrz1BZCM6wKS3y97vN/D8EHUa4ENw//PrPZ/GH7e7FwKJA19l9ow/Lv4extQzdPvjEBzBH+31jB80jyVu4fh//09JjDyn1Ef5x+Gz7caltswfM2xOruH4Y8Qd8Ueho8rnHhrGP6yWjPWAE0+uNOG4YMM+wqgaTe3WtYwfInoZQaS0b1BQDKcZRLQhZf99gJtkSieuIcBh18ArRODGZrlUXYAAAAASUVORK5CYII='
);
let res;
res = await engine.scanImage(image);
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
// the second time the same
res = await engine.scanImage(image);
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
// if we don't scan the whole thing then it fails
res = await engine.scanImage(image, {x: 0, y: 0, width: 5, height: 5});
assert.isNull(res);
// if we pass the right size, then everything is OK again
res = await engine.scanImage(image, {x: 0, y: 0, width: image.width, height: image.height});
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment