引言在 HarmonyOS 应用开发中图片处理是一个非常高频的需求。大多数开发者习惯从资源文件或网络 URL 加载图片但有时我们需要在运行时动态生成图像——比如根据用户配色方案生成头像占位图、绘制数据可视化图案、或创建程序化纹理。这些场景下文件资源和网络图片就派不上用场了。HarmonyOS NEXT 通过ohos.multimedia.imageImageKit提供了完整的图片处理能力。除了常规的图片解码/编码外它还支持从原始像素数据编程创建 PixelMap——这意味着你可以像操作数组一样控制每一个像素的 RGBA 值然后将它渲染到屏幕上。本文将构建一个像素图实验室Demo展示三个核心能力用纯色、渐变、棋盘三种图案模式填充像素缓冲区通过createPixelMap()构建 PixelMap 对象再通过getImageInfo()读取图像元数据进行诊断展示。读完本文你将掌握PixelMap 编程创建createPixelMap(colors, options)从 ArrayBuffer 构建像素图像素数据填充Uint8Array 逐像素写入 RGB/A 通道元数据诊断getImageInfo()获取尺寸、格式、AlphaType内存管理release()及时释放 PixelMap 资源组件渲染Image 组件直接接收 PixelMap 对象并显示环境与权限ohos.multimedia.image属于ImageKit工具包自 API 6 开始提供不需要申请任何权限即可使用基础的 PixelMap 创建功能。import{image}fromkit.ImageKit;注意导入路径是kit.ImageKit而非ohos.multimedia.image。这是 HarmonyOS NEXT 的 Kit 化导入方式系统会自动映射到对应的模块。一、核心概念PixelMap 是什么在深入 API 之前先理解 PixelMap 的本质。PixelMap 是一块存在于内存中的位图它由三要素组成像素数据Pixel Data一块连续的字节数组按行排列存储每个像素的颜色信息图像信息ImageInfo描述如何解读这些字节——宽度、高度、像素格式、Alpha 类型内存管理PixelMap 占用堆内存使用完毕必须调用release()释放与从文件中解码得到的 PixelMap 不同编程创建的 PixelMap 让你完全控制像素数据。你可以把 PixelMap 想象成一块画布Uint8Array就是你的画笔。像素格式PixelMapFormat像素格式决定了每个像素占用多少字节以及各颜色通道的排列顺序格式字节/像素通道排列说明RGBA_88884R,G,B,A最常用格式每个通道 8 位BGRA_88884B,G,R,AWindows 常用的像素顺序ARGB_88884A,R,G,BmacOS/iOS 常用的像素顺序RGB_8883R,G,B无 Alpha 通道文件体积更小RGB_5652R(5),G(6),B(5)低色彩精度适合嵌入式设备ALPHA_81A仅 Alpha 通道用于遮罩本文使用RGBA_8888格式。这意味着一个 200×200 的图像需要200 × 200 × 4 160,000字节的像素数据。AlphaTypeAlphaType描述 Alpha 通道的编码方式OPAQUE不透明图像没有 Alpha 通道或所有像素完全不透明PREMUL预乘 Alpha每个颜色通道已经乘以了 Alpha 值。这是渲染性能最高的格式UNPREMUL非预乘 Alpha颜色通道存储原始值Alpha 独立存储。这是最常见的格式对于编程创建的 RGBA_8888 PixelMap通常使用 OPAQUE如果 A 总是 255或 UNPREMUL如果需要透明度。二、InitializationOptions创建参数createPixelMap()需要一个InitializationOptions对象来指定 PixelMap 的元数据interfaceInitializationOptions{size:Size;// 必填{ width: number, height: number }pixelFormat?:PixelMapFormat;// 可选目标像素格式默认 RGBA_8888srcPixelFormat?:PixelMapFormat;// 可选源数据像素格式API 12editable?:boolean;// 可选是否允许编辑默认 falsealphaType?:AlphaType;// 可选Alpha 类型API 12}最少只需要提供sizeletopts:image.InitializationOptions{size:{width:200,height:200},pixelFormat:image.PixelMapFormat.RGBA_8888,editable:true// 设为 true 才能后续修改像素};editable参数决定了创建后的 PixelMap 是否可被修改。如果设为false默认PixelMap 是只读的有助于内存优化。在 Demo 中我们设为true展示完善的资源管理。三、核心 API 详解3.1 createPixelMap() — 从字节数组创建这是本文最核心的 API。函数签名如下functioncreatePixelMap(colors:ArrayBuffer,options:InitializationOptions):PromisePixelMap;colors参数是一个ArrayBuffer包含按行排列的原始像素数据。数据的字节数必须与options.size和options.pixelFormat匹配——200×200 的 RGBA_8888 图像需要恰好 160,000 字节。如果字节数不匹配API 会抛出401参数错误。完整创建流程import{image}fromkit.ImageKit;letwidth200;letheight200;letbytesPerPixel4;lettotalByteswidth*height*bytesPerPixel;// 1. 创建 ArrayBufferletbufnewArrayBuffer(totalBytes);letviewnewUint8Array(buf);// 2. 填充像素数据红色for(leti0;itotalBytes;i4){view[i]255;// Rview[i1]0;// Gview[i2]0;// Bview[i3]255;// A}// 3. 创建 PixelMapletopts:image.InitializationOptions{size:{width:width,height:height},pixelFormat:image.PixelMapFormat.RGBA_8888,editable:true};image.createPixelMap(buf,opts).then((pixelMap:image.PixelMap){console.log(PixelMap 创建成功: pixelMap.getBytesNumberPerRow() bytes/row);// 这里可以将 pixelMap 传给 Image 组件显示}).catch((err:Error){console.error(创建失败: err.message);});3.2 getImageInfo() — 读取图像元数据创建 PixelMap 后可以通过getImageInfo()获取完整的图像描述信息pixelMap.getImageInfo().then((info:image.ImageInfo){console.log(尺寸: info.size.widthxinfo.size.height);console.log(像素格式: info.pixelFormat);console.log(Alpha 类型: info.alphaType);console.log(色彩空间: info.colorSpace);});ImageInfo包含四个关键属性interfaceImageInfo{size:Size;// { width: number, height: number }pixelFormat:PixelMapFormat;alphaType:AlphaType;colorSpace:colorSpaceManager.ColorSpaceManager;}在 Demo 中我们将这些信息展示在PixelMap 信息面板中让开发者可以直观地看到每个像素图的内存布局。3.3 getBytesNumberPerRow() — 获取每行字节数这个方法返回图像一行像素占用的字节数它是width × bytesPerPixel的结果但可能因为内存对齐而大于预期值letbytesPerRow:numberpixelMap.getBytesNumberPerRow();// 对于 200px 宽的 RGBA_8888 图像200 × 4 800 字节/行这个值对于理解 PixelMap 的内存布局至关重要——总字节数 bytesPerRow × height。3.4 readPixelsToBuffer() — 读出像素数据如果想读取 PixelMap 的原始像素数据到缓冲区letdstnewArrayBuffer(totalBytes);pixelMap.readPixelsToBuffer(dst).then((){letpixelsnewUint8Array(dst);console.log(第一个像素 R: pixels[0]);console.log(第一个像素 G: pixels[1]);});注意readPixelsToBuffer会读取当前的像素数据。如果你创建时指定了不同的pixelFormat如将 RGB_565 源数据转换为 RGBA_8888读出的数据将是转换后的格式。3.5 release() — 释放内存PixelMap 占用的是 native 堆内存不会自动被 JS GC 回收。使用完毕后必须手动释放pixelMap.release().then((){console.log(PixelMap 已释放);});在 Demo 中每次生成新 PixelMap 之前都会先释放旧的避免内存泄漏。组件销毁时也会在aboutToDisappear()生命周期中释放。四、三种图案的像素填充算法Demo 中实现了三种图案填充模式它们是理解编程生成图像的关键。4.1 纯色填充Solid最简单的填充方式所有像素写入相同的 RGBA 值。fillSolid(buf:Uint8Array,color:number[],w:number,h:number):void{letidx0;for(lety0;yh;y){for(letx0;xw;x){buf[idx]color[0];// Rbuf[idx1]color[1];// Gbuf[idx2]color[2];// Bbuf[idx3]color[3];// Aidx4;}}}虽然是两层循环但对 200×200 的图像来说总共只有 40,000 个像素填充时间可以忽略不计。4.2 水平渐变Gradient在 y 轴方向上从颜色 C1 线性过渡到 C2。每一行的颜色值由当前 y 坐标决定fillGradient(buf:Uint8Array,c1:number[],c2:number[],w:number,h:number):void{letidx0;for(lety0;yh;y){letty/(h-1);// 0..1 的插值因子letrMath.round(c1[0](c2[0]-c1[0])*t);letgMath.round(c1[1](c2[1]-c1[1])*t);letbMath.round(c1[2](c2[2]-c1[2])*t);for(letx0;xw;x){buf[idx]r;buf[idx1]g;buf[idx2]b;buf[idx3]255;idx4;}}}这里使用线性插值公式result C1 (C2 - C1) × t。由于像素值必须是整数所以用Math.round()四舍五入。4.3 棋盘格Checker将图像分成 20×20 像素的小格子偶数行列用颜色 C1奇数行列用颜色 C2fillChecker(buf:Uint8Array,c1:number[],c2:number[],w:number,h:number):void{letcellSize20;letidx0;for(lety0;yh;y){letcyMath.floor(y/cellSize);for(letx0;xw;x){letcxMath.floor(x/cellSize);letuseC1(cxcy)%20;buf[idx]useC1?c1[0]:c2[0];buf[idx1]useC1?c1[1]:c2[1];buf[idx2]useC1?c1[2]:c2[2];buf[idx3]255;idx4;}}}(cx cy) % 2 0这个条件产生了经典的棋盘交替效果。将% 2改为% 4可以产生更复杂的图案但交替效果是最好的视觉验证——一眼就能看出像素是否正确排列。五、在 Image 组件中渲染 PixelMap创建好的 PixelMap 对象可以直接传给 ArkUI 的Image组件StatepixelMap:image.PixelMap|nullnull;build(){Column(){if(this.pixelMap!null){Image(this.pixelMap).width(200).height(200).objectFit(ImageFit.Contain)}}}Image组件的构造函数接受三种类型的 sourcestring图片文件路径或网络 URLResource应用资源引用PixelMap内存中的像素图对象第三种方式就是本文的核心用法。Image组件会直接渲染 PixelMap无需额外的编码或文件写入步骤。六、实战 Demo像素图实验室页面结构像素图实验室 ├── 状态栏 — 操作反馈 ├── 图案类型选择 — 纯色 / 渐变 / 棋盘 ├── 颜色选择器 — 10 种预定义颜色 │ ├── 主色10 色小圆点 │ └── 副色渐变/棋盘模式显示 ├── 尺寸 选项 │ ├── 像素图尺寸 — 100/150/200/300 │ ├── 可编辑模式 — Toggle 开关 │ ├── 生成 PixelMap 按钮 │ └── 释放 按钮 ├── 像素图预览 — Image 组件渲染区域 ├── PixelMap 信息 — 宽/高/格式/每行字节/总字节/AlphaType/可编辑 ├── 数据流示意图 — Uint8Array → createPixelMap() → Image() └── 核心 API 参考4 个交互点图案切换— 在纯色/渐变/棋盘之间切换改变像素填充逻辑颜色选择— 从 10 种预定义颜色中选择主色和副色实时影响生成效果尺寸调节— 在 100/150/200/300 四种尺寸间切换观察不同尺寸下的内存占用生成与释放— 点击生成按钮创建 PixelMap 并实时预览点击释放按钮销毁资源核心代码位置完整代码在dev/entry/src/main/ets/pages/PixelMapLabPage.ets约 360 行路由已注册为pages/PixelMapLabPage。七、内存管理最佳实践PixelMap 占用的是 native 内存而非 JS 堆内存因此需要特别注意1. 及时释放旧 PixelMapif(this.pixelMap!null){awaitthis.pixelMap.release();this.pixelMapnull;}在创建新的 PixelMap 之前先释放旧的。否则每次创建都会泄漏一块 native 内存。2. 使用 aboutToDisappear 兜底aboutToDisappear():void{if(this.pixelMap!null){this.pixelMap.release();this.pixelMapnull;}}页面离开时必须释放资源这是 ArkUI 的生命周期最佳实践。3. editable 参数的影响editable: true创建的 PixelMap 占用更多内存因为需要保留写入能力如果只是显示用途建议使用默认的editable: false。八、API 总结表API返回值说明image.createPixelMap(buf, opts)PromisePixelMap从 ArrayBuffer 创建 PixelMappm.getImageInfo()PromiseImageInfo获取图像元数据pm.getBytesNumberPerRow()number获取每行字节数pm.readPixelsToBuffer(dst)Promisevoid读取像素数据到缓冲区pm.writePixelsToBuffer(src)Promisevoid写入像素数据需要 editablepm.writeBufferToPixels(src)Promisevoid从缓冲区写入像素pm.release()Promisevoid释放 PixelMap 内存Image(pixelMap)组件在 Image 组件中直接渲染九、与 ComponentSnapshot 的对比第 17 篇文章介绍了ComponentSnapshot——从 UI 组件捕获生成 PixelMap。而本文的createPixelMap是反向操作——从原始像素数据创建 PixelMap。维度ComponentSnapshotcreatePixelMap数据来源UI 组件截图原始像素数组像素可控性不可控取决于渲染结果完全可控逐像素编程适用场景分享截图、生成缩略图程序化纹理、数据可视化权限要求无无两者结合使用可以实现截图 → 像素级处理 → 重新渲染的完整图片处理流水线。十、总结ohos.multimedia.image的createPixelMap()为 HarmonyOS NEXT 应用提供了完全编程化的像素图创建能力从零构建— 用ArrayBufferUint8Array直接控制每个像素的 RGBA三种图案— 纯色、渐变、棋盘格演示了不同的像素填充算法元数据诊断—getImageInfo()getBytesNumberPerRow()揭示内存布局组件渲染—Image(pixelMap)直接渲染无需中间文件严格内存管理—release()释放 native 内存aboutToDisappear兜底这个模块与ComponentSnapshot、createImageSource、createImagePacker共同构成了 HarmonyOS 的图像处理基石。在需要程序化绘制、实时图像生成、或像素级图像编辑的场景中createPixelMap是不可替代的基础 API。