29 从 Buffer 创建图像源

引言

传统图片处理往往先写文件再读文件,多一次磁盘 IO 就多一份延迟与风险。在 HarmonyOS 上,image.createImageSource(buffer) 允许我们直接从内存字节创建图像源,实现"拍照 → 取 JPEG 字节 → 解码 → 识别"的无文件 IO 链路。本工程正是这条链路的完整范例:相机回调拿到 ArrayBuffer,不经落盘直接解码成 PixelMap 交给 OCR(源码参考:entry/src/main/ets/common/utils/Camera.ets)。本文讲清 byteBuffer 从哪来、如何走通"Buffer → ImageSource → PixelMap"的解码链路,以及 Buffer 解码的数据完整性与格式注意事项。

正文知识点

1. byteBuffer 的来源:相机 photoAvailable 回调

工程中,拍照结果通过 PhotoOutput 的 photoAvailable 事件异步投递:

// 源码参考:entry/src/main/ets/common/utils/Camera.ets
this.photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
  let imageObj = photo.main;
  imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => {
    if (errCode || component === undefined) {
      return;
    }
    let buffer: ArrayBuffer;
    buffer = component.byteBuffer;   // ← JPEG 原始字节,就是我们解码的输入
    this.result = await this.recognizeImage(buffer);
  })
})

  • photo.main:Photo 的主图像对象;
  • getComponent(ComponentType.JPEG):异步取回 JPEG 组件;
  • component.byteBuffer:JPEG 编码的原始字节(ArrayBuffer)。

2. 无文件 IO 的解码链路

拿到 buffer 后,工程 recognizeImage 中仅两行即完成解码(源码参考:entry/src/main/ets/common/utils/Camera.ets):

let imageResource = image.createImageSource(buffer);
let pixelMapInstance = await imageResource.createPixelMap();

链路可以画成:

camera.Photo → getComponent(JPEG) → component.byteBuffer (ArrayBuffer)
    → image.createImageSource(buffer)   // 同步,创建解码源,不落盘
    → imageResource.createPixelMap()    // 异步,解码出像素图
    → textRecognition.recognizeText({ pixelMap })  // 消费像素

全程无文件 IO、无路径处理、无沙箱权限问题,天然适合"即拍即识别"这类对延迟敏感的场景。这也是 createImageSource 三种入参(path / fd / buffer)中 buffer 形式的最大价值:数据还在内存,解码立刻开始

3. Buffer 解码注意事项

  • 数据完整性:传入的 ArrayBuffer 必须是完整的单帧图像编码数据(JPEG 文件头 SOI 到文件尾 EOI)。截断、拼接、多帧混合都会导致解码失败或花屏。相机 getComponent(JPEG) 返回的字节通常直接可用,但如果做了自定义处理(如拼帧、加包头),要确保还原成标准格式再解码。
  • 数据生命周期createImageSource(buffer) 内部会引用该 buffer 做解析,若后续业务要修改/复用同一 ArrayBuffer(如连续两帧),建议先 new Uint8Array(original) 拷贝一份再传入,避免解码源与写入方互相干扰。
  • 格式识别createImageSource 会解析头部字节自动识别 JPEG/PNG/WEBP 等格式,无需显式指定;传入非图像数据时抛异常(如 62980096 参数错误 / 解码失败类错误码),必须 try-catch。
  • 容量提示:工程 CommonConstants 中 ARRAYBUFFER_SIZE = 4069IMAGE_RECEIVER_WIDTH = 640IMAGE_RECEIVER_HEIGHT = 480IMAGE_RECEIVER_CAPACITY = 8 是一组"接收器规格"参考值:若通过 image.ImageReceiver 接收相机输出,缓冲区容量(如 8 帧)不足会导致丢帧,尺寸偏小则图像过糊,可据此估算 buffer 峰值内存(640×480×4 ≈ 1.2 MB/帧)。
  • 释放:识别完成后 pixelMapInstance.release()imageResource.release()(工程已有),buffer 本身由 GC 回收,无需手动 release。

代码示例

一个"从 Buffer 解码并识别 + 异常兜底"的完整示例(可直接替换工程 recognizeImage 的解码段):

// 基于 entry/src/main/ets/common/utils/Camera.ets 的完整示例
import { image } from '@kit.ImageKit';
import { textRecognition } from '@kit.CoreVisionKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function recognizeFromJpegBuffer(buffer: ArrayBuffer): Promise<string> {
  // 1. 安全拷贝,避免外部复用 buffer 影响解码
  const safeBuffer: ArrayBuffer = buffer.slice(0);
  // 2. 从内存创建图像源(无文件 IO)
  const imageResource: image.ImageSource = image.createImageSource(safeBuffer);
  let pixelMap: image.PixelMap | undefined = undefined;
  try {
    // 3. 异步解码出像素图
    pixelMap = await imageResource.createPixelMap();
    const info: image.ImageInfo = pixelMap.getImageInfoSync();
    console.info(`decode from buffer, size=${info.size.width}x${info.size.height}`);

    // 4. 交给 OCR
    const visionInfo: textRecognition.VisionInfo = { pixelMap: pixelMap };
    const config: textRecognition.TextRecognitionConfiguration = {
      isDirectionDetectionSupported: true
    };
    const result = await textRecognition.recognizeText(visionInfo, config);
    return result.value;
  } catch (error) {
    const err = error as BusinessError;
    console.error(`decode/recognize failed. code=${err.code}, message=${err.message}`);
    return '识别失败';
  } finally {
    // 5. 无论成败都释放原生资源
    pixelMap?.release();
    imageResource.release();
  }
}

运行效果与注意事项

  • 正确运行时,log 会打印 decode from buffer, size=…x…,随后 OCR 返回识别文本,整个过程不产生任何临时图片文件,卸载应用也无需清理残留图片。
  • 若相机输出分辨率很大(如 4000×3000),createPixelMap 默认按原尺寸解码,内存峰值约 4000×3000×4 ≈ 45 MB;内存紧张时可传 DecodingOptions.desiredSize 降采样,或用 ImageReceiver 直接限定输出 640×480(对应工程 IMAGE_RECEIVER_WIDTH/HEIGHT)。
  • 连续拍照场景下,photoAvailable 回调并发到来,注意不要在上一帧识别完成前复用/覆盖 buffer;必要时加队列串行处理。
  • 遇到解码报错先自查三件事:buffer 是否为空或截断、是否为合法图像格式、是否在 release() 之后仍在使用。这三条覆盖了绝大多数 Buffer 解码失败。

总结

"从 Buffer 创建图像源"是 ImageKit 面向内存数据的高效解码入口:相机回调的 component.byteBuffer 提供 JPEG 字节,image.createImageSource(buffer) + createPixelMap() 在内存中完成解码,OCR 直接消费像素图。掌握数据完整性、生命周期与降采样三项要点,即可把这条无文件 IO 链路跑稳、跑省,这正是"拍照识别文字"工程性能的关键所在。

Logo

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

更多推荐