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');
+    });
+});