HarmonyOS Scan Kit 图像识码与码图生成全攻略:从图片解析到二维码生成

前言

除了实时相机扫码,HarmonyOS Scan Kit 还提供了两大重要能力:图像识码(从本地图片或图像数据中识别码图)和码图生成(将文本或字节数组转换为码图)。这两项能力在内容加工、离线扫码、信息分享等场景中发挥着不可替代的作用。

本文将全面讲解图像识码(本地图片识别 + 图像数据识别)和码图生成(文本生成 + 字节数组生成)的完整开发流程,包含详细的 API 说明、参数配置和实战代码。

核心价值:图像识码让你从相册或相机流中提取码图信息;码图生成让你将任意文本或二进制数据编码为标准的 QR Code 或条形码,两者结合可覆盖完整的内容加工场景。

一、图像识码概述

1.1 什么是图像识码

图像识码是 Scan Kit 提供的离线扫码能力,支持对图库中的码图或图像数据进行扫描识别。它包含两种输入方式:

  • 本地图片识别decode 接口):通过图片路径(URI)输入,识别图片中的码图
  • 图像数据识别decodeImage 接口):通过字节数组(NV21 格式)输入,识别图像数据中的码图

1.2 图像识码的应用场景

场景输入方式典型应用
相册图片识别本地图片 URI识别微信/QQ 收到的二维码截图
相机预览流识别NV21 字节数组扫码 + 识物综合场景
截图识别本地图片 URI识别网页截图中的二维码
文档扫描本地图片 URI识别 PDF 文档中的条形码

1.3 两种识别方式对比

对比维度本地图片识别(decode)图像数据识别(decodeImage)
输入方式InputImage(URI)ByteImage(字节数组)
适用场景相册图片、截图相机预览流
数据来源PhotoViewPickerCamera Kit ImageReceiver
格式要求无特殊限制仅支持 NV21 格式
多码识别支持支持

二、本地图片识别(decode)

2.1 接口说明

本地图片识别通过 detectBarcode.decode 接口实现:

接口签名描述
decode(inputImage: InputImage, options?: ScanOptions): Promise<Array<ScanResult>>Promise 异步回调
decode(inputImage: InputImage, options: ScanOptions, callback: AsyncCallback<Array<ScanResult>>): voidCallback 异步回调
decode(inputImage: InputImage, callback: AsyncCallback<Array<ScanResult>>): voidCallback 异步回调(无参数)

2.2 核心数据结构

// 待识别的图片信息
interface InputImage {
  uri: string;  // 图片路径,例如 file://media/Photo/x/xxx.jpg
}

// 识码结果
interface DetectResult {
  scanResult: scanBarcode.ScanResult[];  // 识码结果数组
}

// 字节图像数据
interface ByteImage {
  byteBuffer: ArrayBuffer;    // 图像数据字节数组
  width: number;              // 图像宽度
  height: number;             // 图像高度
  format: ImageFormat;        // 图像格式(NV21)
}

// 图像格式枚举
enum ImageFormat {
  NV21 = 0
}

2.3 完整开发示例

以下是一个完整的本地图片识别实现:

import { scanCore, scanBarcode, detectBarcode } from '@kit.ScanKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[ImageDecode]';

@Entry
@Component
struct ImageDecodePage {
  @State decodeResults: string[] = [];
  @State selectedImageUri: string = '';

  build() {
    Column() {
      Text('本地图片识码')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 识码结果
      if (this.decodeResults.length > 0) {
        List() {
          ForEach(this.decodeResults, (result: string, index: number) => {
            ListItem() {
              Text(`结果 ${index + 1}: ${result}`)
                .fontSize(14)
                .padding(10)
                .width('100%')
            }
          })
        }
        .width('90%')
        .height('40%')
        .margin({ bottom: 20 })
        .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
        .borderRadius(8)
      }

      // 选择图片按钮
      Button('选择图片并识别')
        .width('80%')
        .onClick(() => {
          this.selectAndDecodeImage();
        })

      Text('支持识别图片中的二维码和条形码')
        .fontSize(12)
        .fontColor($r('sys.color.ohos_id_color_text_secondary'))
        .margin({ top: 12 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 选择图片并进行识码
   */
  private async selectAndDecodeImage(): Promise<void> {
    try {
      // 1. 使用 PhotoViewPicker 选择图片
      const uri = await this.selectPhoto();
      if (!uri) {
        return;
      }

      this.selectedImageUri = uri;
      hilog.info(0x0001, TAG, `Selected image: ${uri}`);

      // 2. 调用 decode 接口识码
      await this.decodeImage(uri);
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `Failed to decode image: ${error.message}`);
      this.decodeResults = [`识别失败: ${error.message}`];
    }
  }

  /**
   * 使用 PhotoViewPicker 选择图片
   */
  private async selectPhoto(): Promise<string | undefined> {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    photoSelectOptions.maxSelectNumber = 1;
    photoSelectOptions.isPhotoTakingSupported = false;
    photoSelectOptions.isEditSupported = false;

    const photoPicker = new photoAccessHelper.PhotoViewPicker();

    try {
      const data: photoAccessHelper.PhotoSelectResult =
        await photoPicker.select(photoSelectOptions);

      if (!data || !data.photoUris || data.photoUris.length === 0) {
        hilog.warn(0x0001, TAG, 'No photo selected');
        return undefined;
      }

      return data.photoUris[0];
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `Failed to select photo: ${error.message}`);
      return undefined;
    }
  }

  /**
   * 对图片进行识码
   */
  private async decodeImage(uri: string): Promise<void> {
    // 构建 InputImage 对象
    const inputImage: detectBarcode.InputImage = {
      uri: uri
    };

    // 配置识码参数
    const options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true
    };

    try {
      const results: scanBarcode.ScanResult[] =
        await detectBarcode.decode(inputImage, options);

      hilog.info(0x0001, TAG,
        `Decode success, found ${results.length} codes`);

      // 格式化结果
      this.decodeResults = results.map((result, index) => {
        return `${index + 1}: ${result.originalValue} (类型: ${result.scanType})`;
      });

      if (this.decodeResults.length === 0) {
        this.decodeResults = ['未识别到码图'];
      }
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `Decode failed: ${error.message}`);
      throw error;
    }
  }
}

2.4 封装通用图像识码服务

import { scanCore, scanBarcode, detectBarcode } from '@kit.ScanKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[ImageDecodeService]';

/**
 * 图像识码结果
 */
export interface ImageDecodeResult {
  success: boolean;
  message: string;
  codes: Array<{
    originalValue: string;
    scanType: string;
  }>;
}

/**
 * 通用图像识码服务
 */
export class ImageDecodeService {
  /**
   * 从相册选择图片并识码
   */
  static async decodeFromPhoto(): Promise<ImageDecodeResult> {
    try {
      // 1. 选择图片
      const photoUri = await this.selectPhoto();
      if (!photoUri) {
        return {
          success: false,
          message: '未选择图片',
          codes: []
        };
      }

      // 2. 识码
      return await this.decodeFromUri(photoUri);
    } catch (err) {
      const error = err as BusinessError;
      return {
        success: false,
        message: `识码异常: ${error.message}`,
        codes: []
      };
    }
  }

  /**
   * 从指定 URI 识码
   */
  static async decodeFromUri(uri: string): Promise<ImageDecodeResult> {
    const inputImage: detectBarcode.InputImage = { uri: uri };

    const options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true
    };

    try {
      const results = await detectBarcode.decode(inputImage, options);

      return {
        success: true,
        message: `识别到 ${results.length} 个码图`,
        codes: results.map(r => ({
          originalValue: r.originalValue,
          scanType: r.scanType.toString()
        }))
      };
    } catch (err) {
      const error = err as BusinessError;
      return {
        success: false,
        message: `识码失败: ${error.message}`,
        codes: []
      };
    }
  }

  /**
   * 选择照片
   */
  private static async selectPhoto(): Promise<string | undefined> {
    const options = new photoAccessHelper.PhotoSelectOptions();
    options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    options.maxSelectNumber = 1;

    const picker = new photoAccessHelper.PhotoViewPicker();
    const result = await picker.select(options);

    if (result?.photoUris?.length) {
      return result.photoUris[0];
    }
    return undefined;
  }
}

三、图像数据识别(decodeImage)

3.1 场景说明

图像数据识别适用于从相机预览流中实时识别码图,需要配合 Camera Kit 使用。典型应用场景包括:

  • 扫码 + 识物综合场景
  • 需要自定义相机流处理的场景
  • 需要同时处理码图和图像内容的场景

3.2 开发步骤

以下是与 Camera Kit 配合使用的图像数据识码流程:

import { detectBarcode, scanBarcode, scanCore } from '@kit.ScanKit';
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[ImageDataDecode]';

/**
 * 图像数据识码服务
 */
export class ImageDataDecodeService {
  /**
   * 从 ImageReceiver 获取的图像数据中识码
   */
  static async decodeFromImageBuffer(
    imgComponent: image.Component,
    width: number,
    height: number
  ): Promise<scanBarcode.ScanResult[]> {
    const stride: number = imgComponent.rowStride;
    let imgByteBuffer: ArrayBuffer = imgComponent.byteBuffer;

    // 图像数据的宽 width 与行距 stride 不一致时,需要处理
    if (stride !== width) {
      const dstBufferSize: number = width * height * 1.5;
      const dstArr = new Uint8Array(dstBufferSize);

      for (let j = 0; j < height * 1.5; j++) {
        const srcBuf = new Uint8Array(imgByteBuffer, j * stride, width);
        dstArr.set(srcBuf, j * width);
      }

      imgByteBuffer = dstArr.buffer as ArrayBuffer;
    }

    // 构建 ByteImage 对象
    const byteImg: detectBarcode.ByteImage = {
      byteBuffer: imgByteBuffer,
      width: width,
      height: height,
      format: detectBarcode.ImageFormat.NV21
    };

    // 配置识码参数
    const options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.QR_CODE],
      enableMultiMode: false
    };

    try {
      const detectResult = await detectBarcode.decodeImage(byteImg, options);
      hilog.info(0x0001, TAG,
        `DecodeImage success, found ${detectResult.scanResult.length} codes`);

      return detectResult.scanResult;
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `DecodeImage failed: ${error.message}`);
      throw error;
    }
  }
}

四、码图生成概述

4.1 什么是码图生成

码图生成是 Scan Kit 提供的码图创建能力,支持通过文本或字节数组生成码图。目前已支持 13 种码制式(QR Code、Data Matrix、PDF417、Aztec 及 9 种条形码),暂不支持 MULTIFUNCTIONAL CODE 生成。

4.2 两种生成方式对比

对比维度文本生成码图字节数组生成码图
输入类型stringArrayBuffer
支持码制式13 种(全部)仅 QR Code
适用场景网址、ID、文本加密数据、二进制协议
数据限制按码类型限制长度按纠错等级限制字节长度
尺寸要求根据码类型不同width 必须等于 height

4.3 纠错等级说明

QR Code 生成支持四种纠错等级:

纠错等级常量值纠错率适用场景
LEVEL_L07%数据量大、码图清晰场景
LEVEL_M115%一般场景
LEVEL_Q225%需要一定容错能力
LEVEL_H330%高度容错、码图可能受损

五、文本生成码图

5.1 接口说明

文本生成码图通过 generateBarcode.createBarcode 接口实现:

在这里插入图片描述

使用 Scan Kit 的 generateBarcode 模块生成的 QR Code 二维码效果,支持自定义尺寸、纠错等级和颜色

// 文本生成码图
createBarcode(
  content: string,
  options: CreateOptions
): Promise<image.PixelMap>

5.2 CreateOptions 参数说明

参数名类型必填说明
scanTypescanCore.ScanType码图类型
widthnumber码图宽度,px,取值范围 [200, 4096]
heightnumber码图高度,px,取值范围 [200, 4096]
marginnumber最小边距,默认 1,取值范围 [1, 10]
levelErrorCorrectionLevel纠错水平,默认 LEVEL_H,仅 QR Code 有效
backgroundColornumber背景颜色,HEX 格式,默认 0xFFFFFF(白色)
pixelMapColornumber码图颜色,HEX 格式,默认 0x000000(黑色)

5.3 生成 QR Code 示例

import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[QRGenerator]';

@Entry
@Component
struct QRCodeGeneratorPage {
  @State pixelMap: image.PixelMap | undefined = undefined;
  @State inputText: string = 'https://www.example.com';

  build() {
    Column() {
      Text('QR Code 生成器')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 输入框
      TextInput({ text: this.inputText })
        .width('80%')
        .placeholder('请输入要生成的内容')
        .onChange((value: string) => {
          this.inputText = value;
        })
        .margin({ bottom: 20 })

      // 生成的二维码
      if (this.pixelMap) {
        Column() {
          Image(this.pixelMap)
            .width(300)
            .height(300)
            .objectFit(ImageFit.Contain)
            .backgroundColor(Color.White)
            .borderRadius(8)

          Text('生成成功!')
            .fontSize(14)
            .fontColor('#4CAF50')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Center)
        .margin({ bottom: 20 })
      }

      // 生成按钮
      Button('生成 QR Code')
        .width('80%')
        .onClick(() => {
          this.generateQRCode();
        })

      Button('清空')
        .width('80%')
        .margin({ top: 10 })
        .onClick(() => {
          this.pixelMap = undefined;
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 生成 QR Code
   */
  private generateQRCode(): void {
    if (!this.inputText) {
      AlertDialog.show({
        title: '提示',
        message: '请输入要生成的内容',
        confirm: { value: '确定', action: () => { } }
      });
      return;
    }

    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.QR_CODE,
      width: 400,
      height: 400,
      margin: 2,
      level: generateBarcode.ErrorCorrectionLevel.LEVEL_H,
      backgroundColor: 0xFFFFFF,
      pixelMapColor: 0x000000
    };

    try {
      generateBarcode.createBarcode(this.inputText, options)
        .then((pixelMap: image.PixelMap) => {
          this.pixelMap = pixelMap;
          hilog.info(0x0001, TAG, 'QR code generated successfully');
        })
        .catch((err: BusinessError) => {
          hilog.error(0x0001, TAG,
            `Failed to generate QR code. Code: ${err.code}, message: ${err.message}`);

          AlertDialog.show({
            title: '生成失败',
            message: `错误码: ${err.code}\n${err.message}`,
            confirm: { value: '确定', action: () => { } }
          });
        });
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `Failed to createBarcode: ${error.message}`);
    }
  }
}

5.4 生成 EAN-13 商品条形码

在这里插入图片描述

使用 Scan Kit 生成的 EAN-13 标准商品条形码,支持零售场景的 POS 扫码结算

import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[BarcodeGen]';

@Entry
@Component
struct BarcodeGeneratorPage {
  @State pixelMap: image.PixelMap | undefined = undefined;

  build() {
    Column() {
      Text('商品条形码生成器')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      if (this.pixelMap) {
        Column() {
          Image(this.pixelMap)
            .width(300)
            .height(150)
            .objectFit(ImageFit.Contain)
            .backgroundColor(Color.White)
            .borderRadius(8)

          Text('EAN-13 条形码')
            .fontSize(14)
            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Center)
        .margin({ bottom: 20 })
      }

      Button('生成 EAN-13 条形码')
        .width('80%')
        .onClick(() => {
          this.generateEAN13();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 生成 EAN-13 商品条形码
   * EAN-13 规范:12 位数字,首位非 0
   */
  private generateEAN13(): void {
    // EAN-13 编码内容(12 位数字)
    const content: string = '694251983457';

    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.EAN_13,
      // 条形码建议宽高比 2:1
      width: 400,
      height: 200,
      margin: 2
    };

    try {
      generateBarcode.createBarcode(content, options)
        .then((pixelMap: image.PixelMap) => {
          this.pixelMap = pixelMap;
          hilog.info(0x0001, TAG, 'EAN-13 barcode generated');
        })
        .catch((err: BusinessError) => {
          hilog.error(0x0001, TAG,
            `Failed to generate EAN-13: ${err.message}`);
        });
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `createBarcode error: ${error.message}`);
    }
  }
}

六、字节数组生成码图

6.1 接口说明

字节数组生成码图适用于交通卡、支付令牌等需要二进制编码的场景:

// 字节数组生成码图(仅支持 QR Code)
createBarcode(
  content: ArrayBuffer,
  options: CreateOptions
): Promise<image.PixelMap>

6.2 交通卡二维码生成示例

import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
import { buffer } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[TransportCardQR]';

@Entry
@Component
struct TransportCardQRPage {
  @State pixelMap: image.PixelMap | undefined = undefined;

  build() {
    Column() {
      Text('交通卡二维码生成')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      if (this.pixelMap) {
        Column() {
          Image(this.pixelMap)
            .width(300)
            .height(300)
            .objectFit(ImageFit.Contain)
            .backgroundColor(Color.White)
            .borderRadius(8)

          Text('交通卡 QR Code')
            .fontSize(14)
            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Center)
        .margin({ bottom: 20 })
      }

      Button('生成交通卡二维码')
        .width('80%')
        .onClick(() => {
          this.generateTransportQR();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 生成交通卡二维码
   */
  private generateTransportQR(): void {
    // 1. 准备十六进制数据(交通卡加密数据)
    const hexData: string =
      '0177C10DD10F7768600000000000000000000000000000000000000000000000';

    // 2. 将十六进制字符串转换为 ArrayBuffer
    const contentBuffer: ArrayBuffer = buffer.from(hexData, 'hex').buffer;

    // 3. 配置参数(字节数组生成仅支持 QR Code)
    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.QR_CODE,
      // 字节数组生成必须 width === height
      width: 400,
      height: 400,
      margin: 2,
      // 交通卡推荐 25% 纠错率
      level: generateBarcode.ErrorCorrectionLevel.LEVEL_Q,
      backgroundColor: 0xFFFFFF,
      pixelMapColor: 0x000000
    };

    try {
      generateBarcode.createBarcode(contentBuffer, options)
        .then((pixelMap: image.PixelMap) => {
          this.pixelMap = pixelMap;
          hilog.info(0x0001, TAG, 'Transport QR code generated');
        })
        .catch((err: BusinessError) => {
          hilog.error(0x0001, TAG,
            `Failed to generate transport QR: ${err.message}`);
        });
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(0x0001, TAG,
        `createBarcode error: ${error.message}`);
    }
  }
}

6.3 Base64 数据生成二维码

处理从后端接口返回的 Base64 编码数据:

import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
import { buffer, util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[Base64QR]';

export class Base64QRGenerator {
  /**
   * 从 Base64 字符串生成二维码
   * @param base64Content Base64 编码的内容
   * @param size 码图尺寸
   */
  static async generateFromBase64(
    base64Content: string,
    size: number = 400
  ): Promise<image.PixelMap> {
    // 1. Base64 解码
    const base64Helper = new util.Base64Helper();
    const decodedArray = base64Helper.decodeSync(
      base64Content,
      util.Type.MIME
    );

    // 2. ISO-8859-1 编码(Latin1)
    const str = buffer.from(decodedArray.buffer).toString();
    const contentBuffer: ArrayBuffer = buffer.from(str, 'latin1').buffer;

    hilog.info(0x0001, TAG,
      `Buffer length: ${contentBuffer.byteLength}`);

    // 3. 配置生成参数
    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.QR_CODE,
      width: size,
      height: size,
      margin: 2,
      level: generateBarcode.ErrorCorrectionLevel.LEVEL_Q,
      backgroundColor: 0xFFFFFF,
      pixelMapColor: 0x000000
    };

    // 4. 生成码图
    return generateBarcode.createBarcode(contentBuffer, options);
  }
}

七、生成带 Logo 的二维码

7.1 使用 Canvas 叠加 Logo

通过 Canvas 组件将二维码与 Logo 图片叠加:

import { image } from '@kit.ImageKit';
import { generateBarcode, scanCore } from '@kit.ScanKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[LogoQR]';

@Entry
@Component
struct LogoQRCodePage {
  @State pixelMap: image.PixelMap | undefined = undefined;
  private settings: RenderingContextSettings =
    new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D =
    new CanvasRenderingContext2D(this.settings);
  private logoImg: ImageBitmap =
    new ImageBitmap('common/logo.png');
  private qrCodeSize: number = 300;

  build() {
    Column() {
      Text('带 Logo 的二维码')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // Canvas 渲染二维码 + Logo
      Canvas(this.context)
        .width(this.qrCodeSize)
        .height(this.qrCodeSize)
        .backgroundColor(Color.White)
        .onReady(() => {
          this.createLogoQRCode();
        })
        .borderRadius(8)
        .margin({ bottom: 20 })

      Text('点击按钮重新生成')
        .fontSize(12)
        .fontColor($r('sys.color.ohos_id_color_text_secondary'))
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 创建带 Logo 的二维码
   */
  private createLogoQRCode(): void {
    this.pixelMap = undefined;
    const content: string = 'https://www.example.com/scan';

    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.QR_CODE,
      height: this.qrCodeSize,
      width: this.qrCodeSize
    };

    generateBarcode.createBarcode(content, options)
      .then((pixelMap: image.PixelMap) => {
        this.pixelMap = pixelMap;

        // 绘制二维码
        this.context.drawImage(
          this.pixelMap,
          0, 0, this.qrCodeSize, this.qrCodeSize,
          0, 0, this.qrCodeSize, this.qrCodeSize
        );

        // 在二维码中心叠加 Logo
        const logoSize: number = 80;
        const logoX: number = (this.qrCodeSize - logoSize) / 2;
        const logoY: number = (this.qrCodeSize - logoSize) / 2;

        this.context.drawImage(
          this.logoImg,
          0, 0, logoSize, logoSize,
          logoX, logoY, logoSize, logoSize
        );

        hilog.info(0x0001, TAG, 'Logo QR code created');
      })
      .catch((err: BusinessError) => {
        hilog.error(0x0001, TAG,
          `Failed to create logo QR: ${err.message}`);
      });
  }
}

八、码图生成参数规范

8.1 尺寸建议

码类型宽高关系建议尺寸说明
QR Codewidth = height400 x 400正方形二维码
Data Matrixwidth = height400 x 400正方形码图
Aztecwidth = height400 x 400正方形码图
EAN-13width : height = 2:1400 x 200宽条形码
EAN-8width : height = 2:1400 x 200宽条形码
Code 128width : height = 2:1400 x 200宽条形码
PDF417width : height = 2:1400 x 200宽条形码

8.2 颜色建议

  • 建议使用默认颜色:黑色码图、白色背景
  • 码图颜色和背景对比度较小会影响识别率
  • 自定义颜色时确保对比度 > 70%

8.3 常见错误

错误码原因解决方案
202参数非法检查 width/height 是否在 [200, 4096] 范围内
202字节数组生成尺寸不匹配确保 width === height
内容超长码类型不支持当前长度使用 QR Code 或分段生成

九、综合应用场景

9.1 生成 + 识别闭环

实现码图生成后立即识别的完整闭环:

import { scanCore, generateBarcode, detectBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[QRVerify]';

@Entry
@Component
struct QRVerifyPage {
  @State pixelMap: image.PixelMap | undefined = undefined;
  @State verifyResult: string = '';

  build() {
    Column() {
      Text('码图生成与验证')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      if (this.pixelMap) {
        Image(this.pixelMap)
          .width(300)
          .height(300)
          .backgroundColor(Color.White)
          .margin({ bottom: 20 })
      }

      if (this.verifyResult) {
        Text(`验证结果: ${this.verifyResult}`)
          .fontSize(14)
          .fontColor('#4CAF50')
          .margin({ bottom: 20 })
      }

      Button('生成并验证')
        .onClick(() => {
          this.generateAndVerify();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  private async generateAndVerify(): Promise<void> {
    const content: string = 'VerifyTest_' + Date.now();

    const options: generateBarcode.CreateOptions = {
      scanType: scanCore.ScanType.QR_CODE,
      width: 400,
      height: 400,
      level: generateBarcode.ErrorCorrectionLevel.LEVEL_H
    };

    try {
      // 生成码图
      const pixelMap = await generateBarcode.createBarcode(content, options);
      this.pixelMap = pixelMap;

      // 验证:将 PixelMap 转为可识别的格式
      // 实际项目中需要保存到文件再通过 decode 识别
      this.verifyResult = `生成成功!原始内容: ${content}`;
    } catch (err) {
      const error = err as BusinessError;
      this.verifyResult = `生成失败: ${error.message}`;
    }
  }
}

十、最佳实践总结

10.1 码图生成建议

  1. QR Code 使用默认尺寸:400x400 px 是推荐的平衡点
  2. 条形码遵循 2:1 比例:确保条形码宽度足够扫描
  3. 字节数组生成注意尺寸:width 必须等于 height
  4. 选择合适的纠错等级:LEVEL_H(30%)适合可能受损的场景
  5. Base64 数据注意编码:使用 Latin1 编码而非直接 TextEncoder

10.2 图像识码建议

  1. 优先使用本地图片识别decode 接口简单易用
  2. 图像数据识别注意格式:仅支持 NV21 格式
  3. 处理 stride 问题:图像数据的宽与行距不一致时需要转换
  4. 多码识别:设置 enableMultiMode: true

总结

本文全面讲解了 HarmonyOS Scan Kit 的图像识码和码图生成能力。图像识码支持从本地图片和图像数据中提取码图信息,码图生成支持通过文本和字节数组创建 13 种码制式的码图。

这两项能力是 Scan Kit 的内容加工层,与实时扫码能力互补,形成了完整的码图处理闭环:

  • 生成:将信息编码为码图 → 分发:码图被用户使用
  • 扫描:用户实时扫描码图 → 识码:从图片中识别码图

在最后一篇文章中,我们将讲解Scan Kit 高级应用场景与最佳实践,覆盖综合实战案例和性能优化。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐