diff --git a/packages/qr-code-scanner/src/engine.js b/packages/qr-code-scanner/src/engine.js new file mode 100644 index 0000000000000000000000000000000000000000..ff4dbce221744f976147c56b13ba8ffa7ab808e3 --- /dev/null +++ b/packages/qr-code-scanner/src/engine.js @@ -0,0 +1,48 @@ +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; + } + } +} diff --git a/packages/qr-code-scanner/src/index.js b/packages/qr-code-scanner/src/index.js index 4e722c43212845e5ab9870d822d08eb423ebdc56..887a4cc3d9c4f8edcce84173086dcd0d87f4c090 100644 --- a/packages/qr-code-scanner/src/index.js +++ b/packages/qr-code-scanner/src/index.js @@ -1,3 +1,2 @@ -import {QrCodeScanner} from './qr-code-scanner.js'; - -export {QrCodeScanner}; +export {QrCodeScanner} from './qr-code-scanner.js'; +export {QrCodeScannerEngine, ScanResult} from './engine.js'; diff --git a/packages/qr-code-scanner/src/qr-code-scanner.js b/packages/qr-code-scanner/src/qr-code-scanner.js index be3080b54551bd326f50df3c0096cf5f97fbf3a9..e1ffcd0771e4ce55193729f041cd5f53631f01db 100644 --- a/packages/qr-code-scanner/src/qr-code-scanner.js +++ b/packages/qr-code-scanner/src/qr-code-scanner.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(); diff --git a/packages/qr-code-scanner/test/unit.js b/packages/qr-code-scanner/test/unit.js index 2c1b29876ba615e2fff80fb9e26aae6cf75d52c7..2d3bb7966c83d10168abd2dcbefdc924a3153670 100644 --- a/packages/qr-code-scanner/test/unit.js +++ b/packages/qr-code-scanner/test/unit.js @@ -1,6 +1,7 @@ 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'); + }); +});