You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.9 KiB
114 lines
3.9 KiB
// spec: https://wicg.github.io/shape-detection-api/#barcode-detection-api
|
|
|
|
import { BarcodeDetectorOptions, BarcodeFormat, DetectedBarcode } from "./basic-types"
|
|
import { imageDataFrom } from "./image-data"
|
|
import * as ZXing from "@zxing/library"
|
|
|
|
const mapFormat = new Map<BarcodeFormat, ZXing.BarcodeFormat>([
|
|
|
|
[ "aztec", ZXing.BarcodeFormat.AZTEC ],
|
|
// [ "codabar", ZXing.BarcodeFormat.CODABAR ],
|
|
[ "code_39", ZXing.BarcodeFormat.CODE_39 ],
|
|
// [ "code_93", ZXing.BarcodeFormat.CODE_93 ],
|
|
[ "code_128", ZXing.BarcodeFormat.CODE_128 ],
|
|
[ "data_matrix", ZXing.BarcodeFormat.DATA_MATRIX ],
|
|
[ "ean_8", ZXing.BarcodeFormat.EAN_8 ],
|
|
[ "ean_13", ZXing.BarcodeFormat.EAN_13 ],
|
|
[ "itf", ZXing.BarcodeFormat.ITF ],
|
|
[ "pdf417", ZXing.BarcodeFormat.PDF_417 ],
|
|
[ "qr_code", ZXing.BarcodeFormat.QR_CODE ],
|
|
[ "upc_a", ZXing.BarcodeFormat.UPC_A ],
|
|
[ "upc_e", ZXing.BarcodeFormat.UPC_E ]
|
|
|
|
])
|
|
|
|
const mapFormatInv = new Map<ZXing.BarcodeFormat, BarcodeFormat>(
|
|
Array.from(mapFormat).map(([key, val]) => [val, key])
|
|
)
|
|
|
|
const allSupportedFormats : BarcodeFormat[] = Array.from(mapFormat.keys())
|
|
|
|
const mapResult = (result : ZXing.Result) : DetectedBarcode => {
|
|
const format = mapFormatInv.get(result.getBarcodeFormat())
|
|
const rawValue = result.getText()
|
|
|
|
const cornerPoints = Object.freeze(
|
|
result
|
|
.getResultPoints()
|
|
.map(point => ({ x: point.getX(), y: point.getY() }))
|
|
)
|
|
|
|
const x_min = Math.min(...cornerPoints.map(point => point.x))
|
|
const x_max = Math.max(...cornerPoints.map(point => point.x))
|
|
const y_min = Math.min(...cornerPoints.map(point => point.y))
|
|
const y_max = Math.max(...cornerPoints.map(point => point.y))
|
|
|
|
const boundingBox = DOMRectReadOnly.fromRect({
|
|
x: x_min,
|
|
y: y_min,
|
|
width: x_max - x_min,
|
|
height: y_max - y_min
|
|
})
|
|
|
|
return { format, rawValue, cornerPoints, boundingBox }
|
|
}
|
|
|
|
export default class BarcodeDetector {
|
|
|
|
reader : ZXing.MultiFormatReader
|
|
|
|
constructor (barcodeDetectorOptions? : BarcodeDetectorOptions) {
|
|
// SPEC: A series of BarcodeFormats to search for in the subsequent detect() calls. If not present then the UA SHOULD
|
|
// search for all supported formats.
|
|
const formats = barcodeDetectorOptions?.formats ?? allSupportedFormats
|
|
|
|
// SPEC: If barcodeDetectorOptions.formats is present and empty, then throw a new TypeError.
|
|
if (formats.length === 0) {
|
|
throw new TypeError("") // TODO pick message
|
|
}
|
|
|
|
// SPEC: If barcodeDetectorOptions.formats is present and contains unknown, then throw a new TypeError.
|
|
if (formats.includes("unknown")) {
|
|
throw new TypeError("") // TODO pick message
|
|
}
|
|
|
|
const hints = new Map([
|
|
[ ZXing.DecodeHintType.POSSIBLE_FORMATS, formats.map(format => mapFormat.get(format)) ]
|
|
])
|
|
|
|
this.reader = new ZXing.MultiFormatReader()
|
|
this.reader.setHints(hints)
|
|
}
|
|
|
|
static async getSupportedFormats() : Promise<BarcodeFormat[]> {
|
|
return allSupportedFormats
|
|
}
|
|
|
|
// INVESTIGATE:
|
|
// * native api on Mac Chrome gives "Source not supported" for Blob
|
|
// * only two corner points for code_39
|
|
async detect(image : ImageBitmapSource) : Promise<DetectedBarcode[]> {
|
|
// TODO: [SPEC]
|
|
// Note that if the ImageBitmapSource is an object with either a horizontal dimension or a vertical dimension equal
|
|
// to zero, then the Promise will be simply resolved with an empty sequence of detected objects.
|
|
|
|
const imageData = await imageDataFrom(image)
|
|
const canvas = document.createElement("canvas");
|
|
const canvasCtx = canvas.getContext("2d");
|
|
canvas.width = imageData.width;
|
|
canvas.height = imageData.height;
|
|
canvasCtx.putImageData(imageData, 0, 0);
|
|
|
|
try {
|
|
const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(canvas);
|
|
const binaryBitmap = new ZXing.BinaryBitmap(new ZXing.HybridBinarizer(luminanceSource));
|
|
const result = this.reader.decode(binaryBitmap);
|
|
|
|
return [ mapResult(result) ]
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
return []
|
|
}
|
|
}
|
|
}
|