Skip to content

Commit

Permalink
Use zbar-wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
adzialocha committed Apr 16, 2024
1 parent 1e797a0 commit 01023d9
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 52 deletions.
10 changes: 5 additions & 5 deletions bin/build-frontend-ts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ function config(options) {
define: {
'process.env.NODE_ENV': isProduction ? '"production"' : '"development"',
},
// We want the inlined version of the `zbar-wasm` package as the .wasm paths
// are unfortunately hardcoded in this package. We can force esbuild to load
// the inlined version through setting a condition which will make esbuild
// prefer an package export named after it
conditions: ['zbar-inlined'],
plugins,
external: ["*.jpg", "*.png", "*.webp", "*.svg"]
}
Expand Down Expand Up @@ -157,11 +162,6 @@ async function bundle(options) {
'node_modules/@deltachat/message_parser_wasm/message_parser_wasm_bg.wasm',
'html-dist/message_parser_wasm_bg.wasm',
)

await copyFile(
'node_modules/zxing-wasm/dist/full/zxing_full.wasm',
'html-dist/zxing_full.wasm',
)
}

/**
Expand Down
22 changes: 7 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@emoji-mart/data": "1.1.2",
"@emoji-mart/react": "1.1.1",
"@mapbox/geojson-extent": "^1.0.0",
"@undecaf/zbar-wasm": "^0.10.1",
"application-config": "^1.0.1",
"classnames": "^2.3.2",
"debounce": "^1.2.0",
Expand All @@ -108,8 +109,7 @@
"stackframe": "^1.2.1",
"use-debounce": "^3.3.0",
"webxdc-types": "^1.0.1",
"ws": "7.5.9",
"zxing-wasm": "^1.2.7"
"ws": "7.5.9"
},
"devDependencies": {
"@electron/notarize": "^2.1.0",
Expand Down
108 changes: 80 additions & 28 deletions src/renderer/components/QrReader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import React, {
useState,
} from 'react'
import {
readBarcodesFromImageData,
readBarcodesFromImageFile,
setZXingModuleOverrides,
} from 'zxing-wasm'
ZBarConfigType,
ZBarScanner,
ZBarSymbolType,
getDefaultScanner,
scanImageData,
} from '@undecaf/zbar-wasm'
import classNames from 'classnames'
import { Spinner } from '@blueprintjs/core'

Expand All @@ -30,18 +32,47 @@ type Props = {
onScan: (data: string) => void
}

type ImageDimensions = {
width: number
height: number
}

const SCAN_QR_INTERVAL_MS = 50

setZXingModuleOverrides({
// Tell ZXing where the wasm file is located after it got copied there during
// build process
locateFile: (path, prefix) => {
if (path.endsWith('.wasm')) {
return `./${path}`
}
return prefix + path
},
})
async function getImageDataFromFile(file: File): Promise<ImageData> {
const image = new Image()
const reader = new FileReader()

return new Promise(resolve => {
// Extract image data from base64 encoded blob
image.addEventListener('load', () => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = image.width
canvas.height = image.height

if (!context) {
return
}

context.drawImage(image, 0, 0)

const imageData = context.getImageData(0, 0, image.width, image.height)
resolve(imageData)
})

// Load image from file as base64 encoded data string
reader.addEventListener(
'load',
() => {
image.src = reader.result as string
},
false
)

reader.readAsDataURL(file)
})
}

export default function QrReader({ onError, onScan }: Props) {
const tx = useTranslationFunction()
Expand All @@ -54,14 +85,41 @@ export default function QrReader({ onError, onScan }: Props) {

const [ready, setReady] = useState(false)
const [error, setError] = useState(false)
const [scanner, setScanner] = useState<ZBarScanner | undefined>(undefined)
const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([])
const [facingMode, setFacingMode] = useState<FacingMode>('user')
const [deviceId, setDeviceId] = useState<string | undefined>(undefined)
const [dimensions, setDimensions] = useState({
const [dimensions, setDimensions] = useState<ImageDimensions>({
width: 640,
height: 480,
})

useEffect(() => {
const createScanner = async () => {
const qrCodeScanner = await getDefaultScanner()

// First disable all code types
Object.keys(ZBarSymbolType).forEach(key => {
qrCodeScanner.setConfig(
key as unknown as ZBarSymbolType,
ZBarConfigType.ZBAR_CFG_ENABLE,
0
)
})

// .. and enable only QrCode scanning
qrCodeScanner.setConfig(
ZBarSymbolType.ZBAR_QRCODE,
ZBarConfigType.ZBAR_CFG_ENABLE,
1
)

setScanner(qrCodeScanner)
}

createScanner()
}, [])

const handleError = useCallback(
(error: any) => {
if (typeof error === 'string') {
Expand Down Expand Up @@ -103,22 +161,18 @@ export default function QrReader({ onError, onScan }: Props) {
if (!event.target.files || event.target.files.length === 0) {
return
}

const file = event.target.files[0]
const arrayBuffer = await file.arrayBuffer()
const blob = new Blob([new Uint8Array(arrayBuffer)], { type: file.type })

try {
const results = await readBarcodesFromImageFile(blob, {
formats: ['QRCode'],
})
const imageData = await getImageDataFromFile(file)
const results = await scanImageData(imageData, scanner)

if (unmounted) {
return
}

results.forEach(result => {
onScan(result.text)
onScan(result.decode())
})
} catch (error: any) {
handleError(error)
Expand All @@ -128,7 +182,7 @@ export default function QrReader({ onError, onScan }: Props) {
unmounted = true
}
},
[handleError, onScan]
[handleError, onScan, scanner]
)

const handleSelectDevice = useCallback(
Expand Down Expand Up @@ -307,16 +361,14 @@ export default function QrReader({ onError, onScan }: Props) {

const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
try {
const results = await readBarcodesFromImageData(imageData, {
formats: ['QRCode'],
})
const results = await scanImageData(imageData, scanner)

if (unmounted) {
return
}

results.forEach(result => {
onScan(result.text)
onScan(result.decode())
})
} catch (error: any) {
handleError(error)
Expand All @@ -328,7 +380,7 @@ export default function QrReader({ onError, onScan }: Props) {
unmounted = true
window.clearInterval(interval)
}
}, [handleError, onScan])
}, [handleError, onScan, scanner])

return (
<div className={styles.qrReader}>
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import initMessageParserWasm from '@deltachat/message_parser_wasm'
import initWasm from '@deltachat/message_parser_wasm'

import App from './App'
import initSystemIntegration from './system-integration'
Expand All @@ -14,7 +14,7 @@ async function main() {
runtime.initialize()
printProcessLogLevelInfo()

await initMessageParserWasm('./message_parser_wasm_bg.wasm')
await initWasm('./message_parser_wasm_bg.wasm')

initSystemIntegration()

Expand Down

0 comments on commit 01023d9

Please sign in to comment.