如何读取条码中的二进制数据

条码是一种以机器可读的可视形式表示数据的方法。条码种类繁多,主要分为一维码和二维码。

我们可以从条码获取二进制数据,并通过不同方法去读码。例如,在EAN-13中,位于其右边的代码1110010代表数字0。而QR码,它是二维的,可以存储更多的数据,有几种表示数据的模式:

输入模式 模式标识 最大字符数 允许的字符,默认编码
仅数字 1 7,089 0、1、2、3、4、5、6、7、8、9
字母与数字 2 4,296 0-9、A-Z(仅限大写)、空格、$、%、*、+、-、..、/、:
二进制/字节 4 2,953 ISO/IEC 8859-1
日语汉字/假名 8 1,817 Shift JIS X 0208
结构化追加 3 无限 没有限定

PS:结构化追加(Structured Append)是一种将数据分到多个条码的模式。

在本文中,我们将创建一个用于读取条码二进制数据的JavaScript库,并重点处理QR码,因为这种码型有多种模式。

Dynamsoft Barcode Reader会被用于读取条码。

新建项目

使用Vite创建一个新的TypeScript项目:

bash 复制代码
npm create vite@latest BarcodeDataReader -- --template vanilla-ts

编写定义

为读取条码的二进制数据定义几个接口和一个枚举。

  • 定义BarcodeDetail接口:

    ts 复制代码
    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.
    }
  • 定义Barcode接口:

    ts 复制代码
    export interface Barcode {
      bytes:Uint8Array;
      details:BarcodeDetail;
      barcodeType?:string;
    }

    二进制数据存储为Uint8Array

  • 定义ReadingResult接口:

    ts 复制代码
    export interface ReadingResult {
      text?:string;
      img?:HTMLImageElement;
      blob?:Blob;
    }

    我们可以得到纯文本、HTML图像元素或blob这三种类型的结果。

    类型需要在DataType中指定。

    ts 复制代码
    export enum DataType {
      text = 0,
      image = 1,
      unknown = 2
    }

创建一个读取二进制数据的类

  1. 使用以下模板创建一个新的BarcodeDataReader.ts文件:

    ts 复制代码
    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;
      }
    }
  2. 根据不同模式读取二进制数据。

    ts 复制代码
    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;
    }
  3. 实现对数字、字母与数字和日文汉字模式的读取。这三种模式的数据都是纯文本,它们可以共享类似的逻辑。

    ts 复制代码
    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;
    }
  4. 以字节模式读取数据。

    由于字节模式下的数据类型未知,我们需要根据用户指定的数据类型获取读取结果。

    如果数据类型是文本,我们需要检测编码。这里使用chardet库进行检测。

    ts 复制代码
    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)
      })
    }
  5. 以结构化追加模式读取数据。

    需要根据页码对条码进行排序,将多个码的数据合并成一个Unit8Array,然后得到结果。

    ts 复制代码
    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;
    }

读取测试

然后,我们可以更新index.html,使用Dynamsoft Barcode Reader读取QR码,并使用我们编写的库读取二进制数据。

以下是基本代码:

js 复制代码
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);

可以访问在线演示进行试用。

如果手头没有二维码,可以使用此文中的二维码和二维码生成器。

下面是读取两个二维码的测试截图,一张图片被编码在这两个二维码中:

打包为库

为了便于使用,我们可以将其作为库发布到NPM上。

  1. 安装devDependencies

    bash 复制代码
    npm install -D @types/node vite-plugin-dts
  2. 创建一个新的vite.config.ts文件:

    ts 复制代码
    // 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()],
    });
  3. 将我们的包的入口点添加到package.json

    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"
      ]
    }

运行npm run build。然后,我们可以在dist文件夹中看到打包好的文件。

源代码

欢迎下载源代码并尝试使用:

github.com/tony-xlh/ba...

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax