大家好,我是陈杨。相信大家也都认识我了,我们前面也写了很多篇文章,感兴趣可以打开我的主页去了解一下。觉得写的好的,不要忘记给我点赞哦,感谢!!

今天我们来讲鸿蒙的图像处理能力,图片处理是高频场景——从简单的图片显示,到复杂的 HDR 合成、多图处理,都离不开高效的工具支持。指令魔方也处理了很多种图片场景,比如:复制粘贴的、访问相册的、保存图片、切割图片等等操作。华为提供的 Image Kit(图片处理服务)正是为此而生,它封装了编解码、编辑、元数据处理等核心能力,支持 HEIF、JPEG、PNG 等主流格式,还能通过 AI 实现 SDR 转 HDR,兼顾功能丰富度与性能优化。

我们将结合实际开发场景,从基础概念、核心功能实现(含代码示例)、内存优化三个维度,带你吃透 Image Kit 的使用,避开常见坑点。

一、先搞懂两个核心概念:PixelMap 与 Picture

在使用 Image Kit 前,必须先明确两个基础对象的定位——它们是所有图片操作的核心载体,理解其差异才能选对使用场景。

1.1 核心对象解析

对象 定义与用途 关键操作
PixelMap 位图对象,存储单张图片的像素数据,是图片显示、编辑的直接载体 裁剪、缩放、旋转、镜像、设置透明度、读写像素数据、获取图片信息(含 HDR 元数据)
Picture 多图对象,包含主图、辅助图(附加信息)和元数据,专为多图协同场景设计 获取主图/辅助图、设置元数据、合成 HDR 图片

1.2 适用场景对比

  • 若需显示单张图片、做基础编辑(如头像裁剪)→ 用 PixelMap
  • 若需处理多图关联场景(如 HDR 合成、辅助图存储)→ 用 Picture

二、核心功能实战:从解码到编辑的完整流程

Image Kit 的核心流程可概括为「解码(文件→PixelMap/Picture)→ 编辑处理 → 编码(PixelMap/Picture→文件)」,下面分模块拆解实现细节。

image.png

image.png

2.1 图片解码:将文件转为可操作对象

解码是所有图片操作的第一步,目标是把磁盘/资源中的图片文件,转为 PixelMap(单图)或 Picture(多图)。Image Kit 支持 4 种图片获取方式,这里选最常用的「沙箱路径」和「资源文件 Buffer」展开。

2.1.1 单图解码(转 PixelMap)

支持格式:JPEG、PNG、GIF、WebP、BMP、SVG 等,v22+ 新增 CR2、ARW 等专业相机格式预览解码。

实现步骤 + 代码解析

  1. 导入核心模块
  2. 获取图片资源(以沙箱路径为例)
  3. 创建 ImageSource 实例(解码入口)
  4. 配置解码参数(如是否可编辑、像素格式、动态范围)
  5. 生成 PixelMap 并释放资源
// 1. 导入依赖模块
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

// 2. 获取沙箱路径下的图片(应用沙箱内文件,无需额外权限)
function getImageFilePath(context: Context, fileName: string): string {
  // 缓存目录存储图片,避免占用过多存储空间
  return `${context.cacheDir}/${fileName}`;
}

// 3. 单图解码核心逻辑
async function decodeToPixelMap(context: Context, fileName: string): Promise<image.PixelMap | undefined> {
  try {
    // 获取图片路径
    const filePath = getImageFilePath(context, fileName);
    // 创建ImageSource实例(解码的核心入口)
    const imageSource = image.createImageSource(filePath);
    
    // 4. 配置解码参数
    const decodingOptions: image.DecodingOptions = {
      editable: true, // 允许后续编辑(如裁剪、缩放)
      desiredPixelFormat: image.PixelMapFormat.RGBA_8888, // 像素格式(兼容大多数显示场景)
      desiredDynamicRange: image.DecodingDynamicRange.HDR, // 自动识别HDR并解码
    };
    
    // 5. 生成PixelMap
    const pixelMap = await imageSource.createPixelMap(decodingOptions);
    if (!pixelMap) {
      console.error('解码失败:未生成PixelMap');
      return undefined;
    }
    
    // 验证是否为HDR图片
    const imageInfo = await pixelMap.getImageInfo();
    console.log(`解码成功:图片尺寸${imageInfo.size.width}x${imageInfo.size.height},是否HDR:${imageInfo.isHdr}`);
    
    // 释放ImageSource(PixelMap独立存在,不影响后续使用)
    imageSource.release();
    return pixelMap;
  } catch (error: unknown) {
    const err = error as BusinessError;
    console.error(`解码异常:code=${err.code}, message=${err.message}`);
    return undefined;
  }
}

// 6. 资源释放(避免内存泄漏)
function releasePixelMap(pixelMap?: image.PixelMap) {
  if (pixelMap) {
    // 若用Image组件显示,无需手动释放;自行处理时必须释放
    pixelMap.release();
  }
}

关键解析

  • editable: true:必须设为 true 才能后续编辑(如裁剪),默认 false
  • desiredDynamicRange: HDR:自动适配 SDR/HDR 图片,无需手动判断
  • 资源释放:ImageSource 解码后可立即释放,PixelMap 需在不使用时释放(尤其是大图片)
2.1.2 多图解码(转 Picture)

适用于需要处理主图+辅助图的场景(如 HDR 合成、深度信息存储),支持格式:JPEG、HEIF。

实现步骤 + 代码解析

// 1. 导入依赖
import { image } from '@kit.ImageKit';
import { resourceManager } from '@kit.LocalizationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 2. 从资源文件获取ArrayBuffer(适合打包在应用内的图片)
async function getImageBuffer(context: Context, fileName: string): Promise<ArrayBuffer | undefined> {
  try {
    const resourceMgr = context.resourceManager;
    // 获取资源文件的Uint8Array
    const fileData = await resourceMgr.getRawFileContent(fileName);
    // 转为ArrayBuffer供解码使用
    return fileData.buffer.slice(0);
  } catch (error) {
    console.error(`获取图片Buffer失败:${error}`);
    return undefined;
  }
}

// 3. 多图解码核心逻辑(获取主图+辅助图)
async function decodeToPicture(context: Context, fileName: string): Promise<image.Picture | undefined> {
  try {
    // 获取图片Buffer
    const imageBuffer = await getImageBuffer(context, fileName);
    if (!imageBuffer) return undefined;
    
    // 创建ImageSource实例
    const imageSource = image.createImageSource(imageBuffer);
    
    // 配置解码参数:指定需要解码的辅助图类型(GAINMAP为HDR相关辅助图)
    const decodingOptions: image.DecodingOptionsForPicture = {
      desiredAuxiliaryPictures: [image.AuxiliaryPictureType.GAINMAP]
    };
    
    // 生成Picture对象
    const picture = await imageSource.createPicture(decodingOptions);
    if (!picture) {
      console.error('多图解码失败:未生成Picture');
      return undefined;
    }
    
    // 读取辅助图信息
    const auxPicture = picture.getAuxiliaryPicture(image.AuxiliaryPictureType.GAINMAP);
    if (auxPicture) {
      const auxInfo = auxPicture.getAuxiliaryPictureInfo();
      console.log(`辅助图信息:尺寸${auxInfo.size.width}x${auxInfo.size.height},像素格式${auxInfo.pixelFormat}`);
      // 读取辅助图像素数据(如需处理)
      const auxBuffer = await auxPicture.readPixelsToBuffer();
      console.log(`辅助图像素数据长度:${auxBuffer.byteLength}字节`);
      // 释放辅助图
      auxPicture.release();
    }
    
    // 释放ImageSource
    imageSource.release();
    return picture;
  } catch (error: unknown) {
    const err = error as BusinessError;
    console.error(`多图解码异常:code=${err.code}, message=${err.message}`);
    return undefined;
  }
}

关键解析

  • 辅助图类型需明确指定(如 GAINMAP),否则解码后无法获取
  • Picture 包含主图+辅助图,需分别释放资源,避免内存泄漏

2.2 内存分配优化:选择合适的内存类型

图片解码的内存占用直接影响应用性能,尤其是大尺寸图片(如 4K),选对内存类型能大幅减少卡顿。Image Kit 提供两种内存类型:SHARE_MEMORY 和 DMA_ALLOC。

2.2.1 内存类型对比(实战选型关键)
特性 SHARE_MEMORY(共享内存) DMA_ALLOC(DMA内存)
核心优势 灵活,支持多进程/线程数据共享 零拷贝,CPU占用极低,渲染速度快
适用场景 图片后处理、算法中间结果交换 4K图片显示、HDR解码、视频预览等高带宽场景
4K图片渲染耗时 约20ms 约4ms(性能提升5倍)
硬件依赖 无(依赖系统共享内存机制) 强依赖硬件DMA控制器
限制 需CPU参与数据复制,大图片易卡顿 需连续物理内存,部分低端设备不支持
2.2.2 内存分配实战(自定义选择)

系统默认会根据图片格式、尺寸自动选择内存类型(如 HDR 图片默认用 DMA_ALLOC),也可通过 createPixelMapUsingAllocator 手动指定。

// 自定义内存分配的解码逻辑
async function decodeWithCustomAllocator(context: Context, fileName: string) {
  try {
    const resourceMgr = context.resourceManager;
    const rawFile = await resourceMgr.getRawFileContent(fileName);
    const imageSource = image.createImageSource(rawFile.buffer as ArrayBuffer);
    
    // 解码参数
    const decodingOptions: image.DecodingOptions = {
      desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
    };
    
    // 手动指定内存类型:DMA_ALLOC(适合大图片显示)
    const pixelMap = await imageSource.createPixelMapUsingAllocator(
      decodingOptions,
      image.AllocatorType.DMA_ALLOC
    );
    
    if (pixelMap) {
      // 获取stride(步幅):DMA_ALLOC必须用stride定位像素数据
      const imageInfo = await pixelMap.getImageInfo();
      console.log(`DMA_ALLOC内存:stride=${imageInfo.stride},图片高度=${imageInfo.size.height}`);
      
      // 计算像素内存大小:stride * height(而非width*height,因可能有填充数据)
      const pixelMemorySize = imageInfo.stride * imageInfo.size.height;
      console.log(`像素内存占用:${pixelMemorySize / 1024 / 1024}MB`);
      
      // 示例:裁剪图片(需用stride确保对齐)
      const cropRegion: image.Region = {
        x: 0,
        y: 0,
        size: { width: 500, height: 500 }
      };
      await pixelMap.crop(cropRegion);
      
      pixelMap.release();
    }
  } catch (error: unknown) {
    const err = error as BusinessError;
    console.error(`自定义内存解码失败:${err.message}`);
  }
}

关键优化点

  • 大尺寸图片(≥1024x1024)优先用 DMA_ALLOC,减少卡顿
  • SVG 格式仅支持 SHARE_MEMORY,强行指定 DMA_ALLOC 会抛出异常
  • 用 DMA_ALLOC 时必须通过 getImageInfo().stride 获取步幅,避免像素数据读取错位

2.3 图片编辑与元数据处理

Image Kit 提供丰富的编辑能力,基于 PixelMap 可实现裁剪、缩放、滤镜等操作,同时支持读写 EXIF 元数据(如拍照参数、GPS 信息)。

2.3.1 基础编辑实战(裁剪+缩放+透明度)
// 图片编辑工具类
class ImageEditor {
  // 1. 裁剪图片(按区域裁剪)
  static async cropImage(pixelMap: image.PixelMap, region: image.Region): Promise<image.PixelMap | undefined> {
    try {
      // 裁剪后返回新的PixelMap,原对象仍需释放
      const croppedPixelMap = await pixelMap.crop(region);
      console.log(`裁剪成功:新尺寸${region.size.width}x${region.size.height}`);
      return croppedPixelMap;
    } catch (error) {
      console.error(`裁剪失败:${error}`);
      return undefined;
    }
  }

  // 2. 缩放图片(按比例缩放)
  static async scaleImage(pixelMap: image.PixelMap, scale: number): Promise<image.PixelMap | undefined> {
    try {
      const imageInfo = await pixelMap.getImageInfo();
      const newSize: image.Size = {
        width: Math.floor(imageInfo.size.width * scale),
        height: Math.floor(imageInfo.size.height * scale)
      };
      // 缩放操作
      const scaledPixelMap = await pixelMap.scale(newSize);
      console.log(`缩放成功:原尺寸${imageInfo.size.width}x${imageInfo.size.height} → 新尺寸${newSize.width}x${newSize.height}`);
      return scaledPixelMap;
    } catch (error) {
      console.error(`缩放失败:${error}`);
      return undefined;
    }
  }

  // 3. 设置图片透明度
  static async setImageAlpha(pixelMap: image.PixelMap, alpha: number): Promise<void> {
    try {
      // alpha范围:0(完全透明)- 1(不透明)
      await pixelMap.setAlpha(alpha);
      console.log(`透明度设置成功:${alpha}`);
    } catch (error) {
      console.error(`透明度设置失败:${error}`);
    }
  }
}

// 使用示例
async function editImageDemo(context: Context, fileName: string) {
  // 1. 解码获取PixelMap
  const pixelMap = await decodeToPixelMap(context, fileName);
  if (!pixelMap) return;
  
  // 2. 裁剪:从左上角裁剪500x500区域
  const cropRegion: image.Region = { x: 0, y: 0, size: { width: 500, height: 500 } };
  const croppedMap = await ImageEditor.cropImage(pixelMap, cropRegion);
  if (!croppedMap) {
    pixelMap.release();
    return;
  }
  
  // 3. 缩放:缩小到原来的0.8倍
  const scaledMap = await ImageEditor.scaleImage(croppedMap, 0.8);
  if (!scaledMap) {
    croppedMap.release();
    pixelMap.release();
    return;
  }
  
  // 4. 设置透明度为0.9
  await ImageEditor.setImageAlpha(scaledMap, 0.9);
  
  // 5. 传递给Image组件显示(此处省略组件渲染代码)
  // Image($r('app.media.test'), { pixelMap: scaledMap })
  
  // 6. 释放所有资源
  pixelMap.release();
  croppedMap.release();
  // scaledMap由Image组件管理,无需手动释放
}
2.3.2 EXIF 元数据读写
// 读取图片EXIF信息(如GPS、拍照参数)
async function readImageExif(pixelMap: image.PixelMap) {
  try {
    // 获取图片基础信息(宽高、旋转方向)
    const imageInfo = await pixelMap.getImageInfo();
    console.log(`基础信息:宽=${imageInfo.size.width},高=${imageInfo.size.height},旋转方向=${imageInfo.rotation}`);
    
    // 读取EXIF扩展信息(需通过PixelMap的元数据接口)
    // 注:文档中未明确具体EXIF字段API,此处按文档描述的支持范围示例
    const exifInfo = {
      gps: await pixelMap.getMetadata(image.MetadataKey.GPS_LOCATION), // GPS位置
      aperture: await pixelMap.getMetadata(image.MetadataKey.APERTURE), // 光圈值
      focalLength: await pixelMap.getMetadata(image.MetadataKey.FOCAL_LENGTH) // 焦距
    };
    
    console.log(`EXIF信息:GPS=${exifInfo.gps},光圈=${exifInfo.aperture},焦距=${exifInfo.focalLength}`);
  } catch (error) {
    console.error(`读取EXIF失败:${error}`);
  }
}

关键说明

  • 编辑操作(裁剪、缩放)会返回新的 PixelMap,原对象需手动释放
  • EXIF 支持的字段包括:基础信息(宽高、旋转)、拍照参数(光圈、焦距)、GPS 信息,具体字段需以设备支持为准

三、避坑指南与最佳实践

3.1 必须注意的约束与限制

  1. 权限申请:访问用户相册图片时,需申请读写权限(ohos.permission.READ_IMAGEVIDEOohos.permission.WRITE_IMAGEVIDEO),沙箱内图片无需权限
  2. C API 选择:v12+ 推荐使用「不依赖 JS 对象的 C API」(Image_NativeModule),旧 API(依赖 JS 对象)不再新增功能
  3. 内存限制:单张图片解码内存上限 2GB,超过需用 desiredSize 下采样(支持 JPG、PNG、HEIC 格式)
  4. 格式支持:专业相机格式(CR2、ARW 等)需 v22+,且仅支持预览图解码

3.2 性能优化建议

  1. 大图片优先用 DMA_ALLOC 内存类型,减少渲染卡顿
  2. 解码大图片时设置 desiredSize 下采样,例如:
    const decodingOptions: image.DecodingOptions = {
      desiredSize: { width: 1920, height: 1080 }, // 限制最大尺寸
      desiredSizeMode: image.DesiredSizeMode.SCALE_DOWN // 仅缩小,不放大
    };
    
  3. 页面切换、应用退后台时,释放不可见页面的 PixelMap
  4. 避免同时使用两套 C API,可能导致兼容性问题

四、总结

Image Kit 是 HarmonyOS 中处理图片的核心工具,其优势在于:

  • 全面支持主流图片格式,含 HDR、专业相机格式(v22+)
  • 提供 PixelMap/Picture 双对象模型,适配单图/多图场景
  • 支持 DMA 零拷贝内存,大幅提升大图片渲染性能
  • 丰富的编辑与元数据能力,满足大多数应用场景

开发时需重点关注:资源释放(避免内存泄漏)、内存类型选型(按场景选 DMA_ALLOC/SHARE_MEMORY)、权限申请(访问用户图片时),按本文的流程与最佳实践,可高效实现稳定、高性能的图片处理功能。

Logo

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

更多推荐