How to Read Binary Data from a Barcode
A barcode or bar code is a method of representing data in a visual, machine-readable form. There are all kinds of barcodes and mainly, they can be divided into 1D barcodes and 2D barcodes.
We can get binary data from barcodes and there are different ways to interpret the data. For example, in EAN-13, code 1110010 in the right hand represents the digit 0. As for QR code, which is two-dimensional and can store more data, has several modes to represent data:
Input mode | Mode indicator | Max. characters | Possible characters, default encoding |
---|---|---|---|
Numeric only | 1 | 7,089 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
Alphanumeric | 2 | 4,296 | 0–9, A–Z (upper-case only), space, $, %, *, +, -, ., /, : |
Binary/byte | 4 | 2,953 | ISO/IEC 8859-1 |
Kanji/kana | 8 | 1,817 | Shift JIS X 0208 |
Structured Append | 3 | unlimited | Not specific |
PS: structured append is a mode in which the data is divided into several barcodes.
In this article, we are going to create a JavaScript library for reading the binary data of a barcode, with a special focus on QR code as this format has multiple modes.
Dynamsoft Barcode Reader is used for reading the barcodes.
New Project
Create a new project with Vite and the typescript template:
npm create vite@latest BarcodeDataReader -- --template vanilla-ts
Write Definitions
Define several interfaces and an enum for reading the binary data of barcodes.
-
Define a
BarcodeDetail
interface:export interface BarcodeDetail { mode?:number; //The data encoding mode model?:number; //Number of models errorCorrectionLevel?:number; columns?:number; //The column count rows?:number; //The row count page?:number; //Position of the particular code in structured append codes totalPage?:number; //Total number of structured append codes parityData?:number; //A value obtained by XORing byte by byte the ASCII/JIS values of all the original input data before division into symbol blocks. version?:number; //The version of the code. }
-
Define a
Barcode
interface:export interface Barcode { bytes:Uint8Array; details:BarcodeDetail; barcodeType?:string; }
The binary data is stored as
Uint8Array
. -
Define a
ReadingResult
interface:export interface ReadingResult { text?:string; img?:HTMLImageElement; blob?:Blob; }
We can get the result as plain text, an HTML image element or blob.
The type is decided by the
DataType
passed to the binary data reader.export enum DataType { text = 0, image = 1, unknown = 2 }
Create a Class to Read Binary Data
-
Create a new
BarcodeDataReader.ts
file with the following template:export class BarcodeDataReader{ async read(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{ if (barcodes.length == 0) { throw new Error("No barcodes given"); } let results:ReadingResult[] = []; return results; } }
-
Read the binary data based on different modes.
async read(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{ if (barcodes.length == 0) { throw new Error("No barcodes given"); } let results:ReadingResult[] = []; const mode = barcodes[0].details.mode; if (mode == 1) { results = this.readNumericBarcodes(barcodes); }else if (mode == 2) { results = this.readAlphaNumericBarcodes(barcodes); }else if (mode == 4) { results = await this.readByteEncodingBarcodes(barcodes,dataType); }else if (mode == 8) { results = this.readKanjiBarcodes(barcodes); }else if (mode == 3) { results = await this.readStructuredAppendBarcodes(barcodes,dataType); }else { results = await this.readByteEncodingBarcodes(barcodes,DataType.text); } return results; }
-
Implement reading the data in numeric, alphanumeric and Kanji modes. The three modes’ data is plain text and they can share a similar logic.
private readAlphaNumericBarcodes(barcodes:Barcode[]):ReadingResult[]{ return this.decodeText(barcodes,"ASCII"); } private readNumericBarcodes(barcodes:Barcode[]):ReadingResult[]{ return this.decodeText(barcodes,"ASCII"); } private readKanjiBarcodes(barcodes:Barcode[]):ReadingResult[]{ return this.decodeText(barcodes,"SHIFT-JIS"); } private decodeText(barcodes:Barcode[],encoding:string){ let results:ReadingResult[] = []; for (let index = 0; index < barcodes.length; index++) { const barcode = barcodes[index]; const decoder = new TextDecoder(encoding); const text = decoder.decode(barcode.bytes); let result = { text:text } results.push(result); } return results; }
-
Implement reading the data in byte mode.
As the data type is unknown in byte mode, we have to get the reading results based on the user-specified data type.
If the data type is text, we have to detect the encoding. The
chardet
library is used here for detection.private async readByteEncodingBarcodes(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{ let results:ReadingResult[] = []; for (let index = 0; index < barcodes.length; index++) { const barcode = barcodes[index]; let result:ReadingResult = await this.getResultBasedOnDataType(barcode.bytes,dataType); results.push(result); } return results; } async getResultBasedOnDataType(data:Uint8Array,dataType:DataType) { let result:ReadingResult; if (dataType == DataType.text) { const charset = chardet.analyse(data); const decoder = new TextDecoder(charset[0].name); const text = decoder.decode(data); result = { text:text } }else if (dataType == DataType.image) { const img = await this.getImageFromUint8Array(data); result = { img:img } }else{ result = { blob:this.getBlobFromUint8Array(data) } } return result; } getBlobFromUint8Array(data:Uint8Array) { return new Blob([data]); } getImageFromUint8Array(data:Uint8Array):Promise<HTMLImageElement>{ return new Promise<HTMLImageElement>((resolve, reject) => { const img = document.createElement("img"); const blob = this.getBlobFromUint8Array(data); img.onload = function(){ resolve(img); } img.onerror = function(error) { console.error(error); reject(error); } img.src = URL.createObjectURL(blob); console.log(img.src) }) }
-
Implement reading the data in structured append mode.
We have to sort the barcodes based on their page numbers, merge the bytes into one and then get the result.
private async readStructuredAppendBarcodes(barcodes:Barcode[],dataType:DataType):Promise<ReadingResult[]>{ let results:ReadingResult[] = []; barcodes.sort((a, b) => (a.details.page ?? 0) - (b.details.page ?? 0)) let concatedData:Uint8Array = new Uint8Array(); for (let index = 0; index < barcodes.length; index++) { const barcode = barcodes[index]; let merged = new Uint8Array(barcode.bytes.length + concatedData.length); merged.set(concatedData); merged.set(barcode.bytes, concatedData.length); concatedData = merged; } let result = await this.getResultBasedOnDataType(concatedData,dataType); results.push(result); return results; }
Read QR Codes to Have a Test
We can then update index.html
to use Dynamsoft Barcode Reader to read QR codes and this library to read the binary data.
Here is the basic code:
let router = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
//read barcodes from an image
let result = await router.capture(document.getElementById("image"),"ReadBarcodes_Balance");
let dataType = document.getElementById("dataTypeSelect").selectedIndex;
let barcodes = [];
for (let index = 0; index < result.items.length; index++) {
const item = result.items[index];
if (item.type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE) {
let data = new Uint8Array(item.bytes.length);
data.set(item.bytes);
let barcode = {
bytes:data,
details:item.details
}
barcodes.push(barcode);
}
}
let results = await dataReader.read(barcodes,dataType)
console.log(results);
You can visit this online demo to have a try.
If you don’t have QR codes at hand, you can use the QR codes and the QR code generator in this article.
Here is a screenshot of a test of reading two QR codes which have an image encoded:
Package as a Library
We can publish it as a library onto NPM for ease of use.
-
Install
devDependencies
:npm install -D @types/node vite-plugin-dts
-
Create a new
vite.config.ts
file:// vite.config.ts import { resolve } from 'path'; import { defineConfig } from 'vite'; import dts from 'vite-plugin-dts'; // https://vitejs.dev/guide/build.html#library-mode export default defineConfig({ build: { lib: { entry: resolve(__dirname, 'src/index.ts'), name: 'barcode-data-reader', fileName: 'barcode-data-reader', }, }, plugins: [dts()], });
-
Add the entry points of our package to
package.json
.{ "main": "./dist/barcode-data-reader.umd.cjs", "module": "./dist/barcode-data-reader.js", "types": "./dist/index.d.ts", "exports": { "import": { "types": "./dist/index.d.ts", "default": "./dist/barcode-data-reader.js" }, "require": { "types": "./dist/index.d.ts", "default": "./dist/barcode-data-reader.umd.cjs" } }, "files": [ "dist/*.css", "dist/*.js", "dist/*.cjs", "dist/*.d.ts" ] }
Run npm run build
. Then, we can have the packaged files in the dist
.
Source Code
Get the source code of the library to have a try: