36 图像预处理与裁剪:AnalyzingPage 中的图片处理
36 图像预处理与裁剪:AnalyzingPage 中的图片处理
前言
图:36 图像预处理与裁剪:AnalyzingPage 中的图片处理 运行效果截图(HarmonyOS NEXT)
拍摄或选择图片后,应用通常需要对原始图片进行预处理——裁剪、缩放、格式转换等。这既是 UI 展示的需要,也是后续 AI 分析(OCR、特征提取)的前置条件。
本文以"鹿鹿·笔迹心理分析"项目中 [CapturePage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets) 和 [AnalyzingPage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/AnalyzingPage.ets) 之间的图片传递链路为例,解析鸿蒙应用中图像的处理路径和优化策略。
鸿蒙官方·图像处理文档:developer.huawei.com
项目源码仓库:harmony-app GitHub
图:图像处理管道——原始图片 → ImageSource → PixelMap → 裁剪/缩放 → 编码输出
一、图像处理链路
1.1 数据流
拍照/相册选择 ↓ 原始图片路径(URI / 文件路径) ↓ 图片解码(PixelMap) ↓ 图片裁剪 / 缩放(可选) ↓ 保存至缓存目录 ↓ 传入 AnalyzingPage ↓ OCR 识别 + 特征提取(AI 服务)1.2 项目中的图片传递
// CapturePage — 确认使用照片privateconfirmPhoto(){AppStorage.setOrCreate<string>('capture_image_path',this.previewPath)this.getUIContext().getRouter().pushUrl({url:'pages/AnalyzingPage',params:{imagePath:this.previewPath}})}// AnalyzingPage — 接收图片privateasyncstartAnalysis(){constimagePath=AppStorage.get<string>('capture_image_path')??''constsource=AppStorage.get<string>('capture_source')??'camera'// ...}二、图片解码:PixelMap
2.1 从文件加载 PixelMap
import{image}from'@kit.ImageKit'asyncfunctionloadPixelMap(filePath:string):Promise<image.PixelMap>{constsource=image.createImageSource(filePath)constpixelMap=awaitsource.createPixelMap({desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,desiredSize:{width:1080,height:1920},fitDensity:0})source.release()returnpixelMap}参数说明:
| 参数 | 作用 | 推荐值 | 说明 |
|---|---|---|---|
desiredPixelFormat | 像素格式 | RGBA_8888 | 最高质量 |
desiredSize | 目标尺寸 | 1080x1920 | 限制最大分辨率 |
fitDensity | 密度适配 | 0 | 不需要适配 |
2.2 从 PixelMap 保存为文件
asyncfunctionsavePixelMap(pixelMap:image.PixelMap,outputPath:string):Promise<void>{constpacker=image.createImagePacker()constpackedImage=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality:85})constfile=fs.openSync(outputPath,fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE)file.writeSync(packedImage.data.buffer)fs.closeSync(file)packer.release()}三、图片裁剪
3.1 裁剪功能
asyncfunctioncropImage(sourcePath:string,outputPath:string,region:image.Region):Promise<void>{constsource=image.createImageSource(sourcePath)constpixelMap=awaitsource.createPixelMap({cropRegion:region,// 裁剪区域desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,})// 保存裁剪后的图片constpacker=image.createImagePacker()constpackedImage=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality:85})constfile=fs.openSync(outputPath,fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE)file.writeSync(packedImage.data.buffer)fs.closeSync(file)source.release()packer.release()}// 使用示例image.Region={x:50,y:100,// 左上角坐标width:800,height:1000// 裁剪尺寸}awaitcropImage(inputPath,outputPath,region)四、图片缩放
4.1 缩放到指定尺寸
asyncfunctionresizeImage(sourcePath:string,outputPath:string,maxWidth:number,maxHeight:number):Promise<void>{constsource=image.createImageSource(sourcePath)// 获取原始尺寸constinfo=awaitsource.getImageInfo(0)as{size?:{width:number,height:number}}constsrcWidth=info.size?.width??1920constsrcHeight=info.size?.height??1080// 等比缩放计算lettargetWidth=srcWidthlettargetHeight=srcHeightif(targetWidth>maxWidth){targetHeight=Math.round(targetHeight*maxWidth/targetWidth)targetWidth=maxWidth}if(targetHeight>maxHeight){targetWidth=Math.round(targetWidth*maxHeight/targetHeight)targetHeight=maxHeight}// 创建缩放的 PixelMapconstpixelMap=awaitsource.createPixelMap({desiredSize:{width:targetWidth,height:targetHeight},desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,})// 保存constpacker=image.createImagePacker()constpackedImage=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality:80})constfile=fs.openSync(outputPath,fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE)file.writeSync(packedImage.data.buffer)fs.closeSync(file)source.release()packer.release()}五、图像旋转与方向修正
5.1 EXIF 方向处理
拍摄的图片可能包含 EXIF 方向信息,需要修正:
asyncfunctioncorrectOrientation(filePath:string):Promise<void>{constsource=image.createImageSource(filePath)// 读取图像属性constinfo=awaitsource.getImageInfo(0)constorientation=(infoasRecord<string,Object>)['exifOrientation']asnumber??0// 根据方向旋转letrotation=0switch(orientation){case3:rotation=180;breakcase6:rotation=90;breakcase8:rotation=270;break}if(rotation>0){// 如需旋转,重新生成 PixelMap 并保存覆盖原文件// ...}source.release()}六、AnalyzingPage 中的图片使用
6.1 页面入口
// AnalyzingPage.etsaboutToAppear(){this.startAnalysis()// ...}privateasyncstartAnalysis(){constimagePath=AppStorage.get<string>('capture_image_path')??''constsource=AppStorage.get<string>('capture_source')??'camera'// 模拟 5 层分析进度for(leti=0;i<5;i++){this.currentStep=iawaitnewPromise<void>(resolve=>setTimeout(()=>resolve(),800))}// 生成模拟推理结果constresult=this.generateMockResult(source,archiveId)// 写入 DBawaitHandwritingDao.create({/* ... */})awaitReportDao.create({/* ... */})// 跳转到报告详情页this.getUIContext().getRouter().replaceUrl({url:'pages/ReportDetailPage',params:{imagePath,reportId}})}6.2 图片路径传递方式对比
| 方式 | 项目使用 | 优点 | 缺点 |
|---|---|---|---|
| 文件路径 | ✅previewPath | 简单直接,可跨页面传递 | 文件可能被清理 |
| AppStorage | ✅ 全局存储 | 跨页面共享,不依赖路由参数 | 需要手动清理 |
| 路由参数 | ✅ pushUrl params | 与导航绑定 | 仅适用于相邻页面 |
七、图片处理的常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 图片方向不对 | EXIF 方向未处理 | correctOrientation()修正 |
| 图片太大导致 OOM | 原始分辨率过高 | createPixelMap时指定desiredSize |
| JPEG 压缩质量过低 | 设置不当 | quality: 80-85平衡质量与文件大小 |
| 处理耗时导致 ANR | 同步操作 | 全部使用async/await异步 API |
八、图像处理 API 汇总
| API | 模块 | 用途 | 是否异步 |
|---|---|---|---|
image.createImageSource(path) | @kit.ImageKit | 创建图片源 | ✅ |
source.createPixelMap(options) | @kit.ImageKit | 解码为 PixelMap | ✅ |
image.createImagePacker() | @kit.ImageKit | 编码为文件格式 | ✅ |
packer.packing(pixelMap, options) | @kit.ImageKit | 压缩编码 | ✅ |
fs.openSync(path, mode) | @kit.CoreFileKit | 打开文件 | ❌(无同步版本) |
file.writeSync(data) | @kit.CoreFileKit | 写入数据 | ❌ |
总结
本文解析了鸿蒙应用中图像预处理到 AI 分析的完整链路:
- 图片传递:通过文件路径 + AppStorage + 路由参数 3 种方式跨页面传递
- PixelMap 解码:
image.createImageSource()→createPixelMap()加载图片 - 裁剪与缩放:
cropRegion裁剪区域,desiredSize控制目标尺寸 - 方向修正:读取 EXIF orientation,90/180/270 度旋转
- 输出编码:
ImagePacker.packing()输出为 JPEG 文件 - 分析链路:图片路径 → AnalyzingPage → 模拟推理 → 写入 DB → 报告详情
下一篇文章将介绍相册读取与图片选择——PhotoViewPicker 的完整使用。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
参考资源:
- Image Kit 开发指南
- PixelMap API 参考
- [CapturePage 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets)
- [AnalyzingPage 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/AnalyzingPage.ets)
- ImagePacker 压缩编码
- EXIF 方向处理
- @kit.ImageKit 模块
- HarmonyOS 开发文档
七、图像处理性能优化
7.1 异步处理避免 UI 阻塞
图像解码和压缩是 CPU 密集型操作,必须在异步上下文中执行:
// ✅ 正确:在 async 函数中执行,不阻塞 UIprivateasyncprocessImage(imagePath:string):Promise<string>{// 1. 解码图片(耗时操作)constimageSource=image.createImageSource(imagePath)constpixelMap=awaitimageSource.createPixelMap({desiredSize:{width:1024,height:1024},desiredSamplingQuality:image.SamplingQuality.MEDIUM})// 2. 压缩编码(耗时操作)constpacker=image.createImagePacker()constpackOptions:image.PackingOption={format:'image/jpeg',quality:80}constarrayBuffer=awaitpacker.packing(pixelMap,packOptions)// 3. 写入文件constoutputPath=`${getContext().cacheDir}/processed_${Date.now()}.jpg`constfile=fs.openSync(outputPath,fs.OpenMode.CREATE|fs.OpenMode.WRITE_ONLY)fs.writeSync(file.fd,arrayBuffer)fs.closeSync(file)// 4. 释放 PixelMap 内存pixelMap.release()returnoutputPath}7.2 PixelMap 内存管理
PixelMap 占用大量内存,使用后必须及时释放:
letpixelMap:image.PixelMap|null=nulltry{pixelMap=awaitimageSource.createPixelMap(options)// ... 处理图片}finally{if(pixelMap){pixelMap.release()// 释放 native 内存pixelMap=null}}7.3 图像处理性能指标
| 操作 | 耗时参考(4000x3000 图片) | 优化建议 |
|---|---|---|
| 解码(原始) | 200-500ms | 设置 desiredSize 减小尺寸 |
| 裁剪 | < 50ms | PixelMap.crop() 原地裁剪 |
| 缩放 | 50-150ms | desiredSize 在解码时同步缩放 |
| JPEG 编码(quality=80) | 100-300ms | 适当降低 quality 提速 |
7.4 图像处理工具函数封装
// 项目中封装的图像处理工具exportclassImageUtils{// 解码图片为 PixelMap(带缩放)staticasyncdecodeImage(path:string,maxSize:number=1024):Promise<image.PixelMap>{constsource=image.createImageSource(path)returnsource.createPixelMap({desiredSize:{width:maxSize,height:maxSize}})}// 压缩 PixelMap 并写入文件staticasynccompressToFile(pixelMap:image.PixelMap,outputPath:string,quality:number=80):Promise<void>{constpacker=image.createImagePacker()constbuffer=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality})constfile=fs.openSync(outputPath,fs.OpenMode.CREATE|fs.OpenMode.WRITE_ONLY)fs.writeSync(file.fd,buffer)fs.closeSync(file)}}如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!