目录

在元服务中如何通过像素Buffer创建PixelMap

问题现象

在元服务开发中,开发者需要通过像素Buffer创建PixelMap。但在实际开发中会遇到以下阻碍:

  1. 元服务中暂时无法使用静态方法image.createPixelMap接口。
  2. 元服务中可以使用实例类ImageSource中的createPixelMap方法,但在使用image.createImageSource创建ImageSource实例时,因该组件仅支持解码已编码的图片格式(如JPG、PNG),无法识别不含文件头的纯像素Buffer数据,导致创建失败。

背景知识

解决方案

在元服务中,可以采用构建空壳PixelMap并注入数据的方案实现通过像素Buffer创建PixelMap。具体实现步骤如下:

  1. 构建合法的ImageSource对象:由于ImageSource必须基于合法的图片文件格式创建,开发者需准备一个极小的合法图片数据,本文解决方案会采用一个1x1像素的透明PNG图片的Base64字符串。将其转换为ArrayBuffer后,调用image.createImageSource接口生成一个合法的ImageSource实例。此步骤旨在满足image.createImageSource的参数规范。
  2. 配置解码参数以分配内存:调用createPixelMap实例方法时,需配置DecodingOptions参数:
    • 开启编辑模式:设置editable为true,确保生成的PixelMap允许后续写入数据。
    • 强制指定尺寸:设置desiredSize为目标图片的实际宽和高。系统会忽略第1步中源图(1x1像素)的原始尺寸,按照此参数在内存中开辟相应大小的显存空间。
    • 指定像素格式:设置desiredPixelFormat(如RGBA_8888),确保内存布局与Raw Buffer一致。
  3. 注入原始像素数据:获得上述配置生成的PixelMap对象(此时它是一个具有目标尺寸的“空壳”)后,调用pixelMap.writeBufferToPixels接口,将业务中准备好的Raw Buffer数据写入。

    传入的Buffer字节长度必须严格等于“宽×高×单像素字节数”,否则会导致写入失败。

完整的示例代码如下:

import { image } from '@kit.ImageKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';

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

  /**
   * 核心方法
   */
  async createPixelMapSafe(rawBuffer: ArrayBuffer, width: number, height: number): Promise<image.PixelMap | null> {
    try {
      // 1. 准备 Base64 图 (1x1 PNG)
      const dummyBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAANSURBVBhXY/j/fw8DAwAA/wD/b6y2ZgAAAABJRU5ErkJggg==';
      const base64Str = dummyBase64.split(',')[1];
      const base64Helper = new util.Base64Helper();
      const dummyBuffer = base64Helper.decodeSync(base64Str).buffer as ArrayBuffer;

      // 2. 创建 ImageSource
      const imageSource = image.createImageSource(dummyBuffer);

      // 3. 移花接木:强制指定尺寸 + 开启编辑模式
      const decodingOpts: image.DecodingOptions = {
        editable: true,
        desiredSize: { width: width, height: height },
        desiredPixelFormat: image.PixelMapFormat.RGBA_8888
      };

      // 4. 生成 PixelMap (空壳图片)
      const pixelMap = await imageSource.createPixelMap(decodingOpts);

      // 5. 注入真实的 Buffer 数据
      // 这里的 rawBuffer 大小必须 = width * height * 4
      await pixelMap.writeBufferToPixels(rawBuffer);

      console.info('PixelMap 创建成功');
      return pixelMap;

    } catch (error) {
      const err = error as BusinessError;
      console.error(`Error: ${err.message}`);
      return null;
    }
  }

  // --- 测试逻辑 ---
  async generateImage() {
    const w = 200, h = 200;
    const buf = new ArrayBuffer(w * h * 4);
    const view = new DataView(buf);

    // 随便填点颜色 (红蓝渐变)
    for (let i = 0; i < buf.byteLength; i += 4) {
      view.setUint8(i, (i / 4) % 255);
      view.setUint8(i + 1, 0);
      view.setUint8(i + 2, 255);
      view.setUint8(i + 3, 255);
    }

    const pm = await this.createPixelMapSafe(buf, w, h);
    if (pm) {
      this.pixelMap = pm;
    }
  }

  build() {
    Column({ space: 20 }) {
      if (this.pixelMap) {
        Image(this.pixelMap).width(200).height(200).border({width:1})
      }
      Button('生成').onClick(() => this.generateImage())
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

常见FAQ

Q:为什么不能直接把像素Buffer传给image.createImageSource?

A:createImageSource接口仅支持解码已编码的图片文件数据(如PNG、JPG格式),这些数据包含特定的文件头(Header)。而像素Buffer(Raw Buffer)是纯粹的颜色数值集合,没有文件头,因此解析器无法识别,导致创建失败。

Q:代码中为什么要先创建一个1x1的Base64图片?

A:使用极小的1x1合法图片可以成功创建一个ImageSource实例,随后利用解码参数将其扩大为所需的任意尺寸,从而达到创建目标PixelMap的目的。

Q:在使用writeBufferToPixels时报错是什么原因?

A:通常是因为像素Buffer的字节长度计算错误。请确保rawBuffer.byteLength严格等于width * height * 像素字节数。例如,RGBA_8888格式下,每个像素占4字节,总长度必须完全匹配,多一个或少一个字节都会导致写入失败。