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 → 裁剪/缩放 → 编码输出原始图片Camera / Albumimage.createImageSource解码图片PixelMap像素图对象crop 裁剪提取手写区域scale 缩放统一到 512x512rotate 旋转修正 EXIF 方向ImagePacker 编码压缩为 JPEG/PNG写入本地文件缓存目录一、图像处理链路1.1 数据流拍照/相册选择 ↓ 原始图片路径URI / 文件路径 ↓ 图片解码PixelMap ↓ 图片裁剪 / 缩放可选 ↓ 保存至缓存目录 ↓ 传入 AnalyzingPage ↓ OCR 识别 特征提取AI 服务1.2 项目中的图片传递// CapturePage — 确认使用照片privateconfirmPhoto(){AppStorage.setOrCreatestring(capture_image_path,this.previewPath)this.getUIContext().getRouter().pushUrl({url:pages/AnalyzingPage,params:{imagePath:this.previewPath}})}// AnalyzingPage — 接收图片privateasyncstartAnalysis(){constimagePathAppStorage.getstring(capture_image_path)??constsourceAppStorage.getstring(capture_source)??camera// ...}二、图片解码PixelMap2.1 从文件加载 PixelMapimport{image}fromkit.ImageKitasyncfunctionloadPixelMap(filePath:string):Promiseimage.PixelMap{constsourceimage.createImageSource(filePath)constpixelMapawaitsource.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):Promisevoid{constpackerimage.createImagePacker()constpackedImageawaitpacker.packing(pixelMap,{format:image/jpeg,quality:85})constfilefs.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):Promisevoid{constsourceimage.createImageSource(sourcePath)constpixelMapawaitsource.createPixelMap({cropRegion:region,// 裁剪区域desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,})// 保存裁剪后的图片constpackerimage.createImagePacker()constpackedImageawaitpacker.packing(pixelMap,{format:image/jpeg,quality:85})constfilefs.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):Promisevoid{constsourceimage.createImageSource(sourcePath)// 获取原始尺寸constinfoawaitsource.getImageInfo(0)as{size?:{width:number,height:number}}constsrcWidthinfo.size?.width??1920constsrcHeightinfo.size?.height??1080// 等比缩放计算lettargetWidthsrcWidthlettargetHeightsrcHeightif(targetWidthmaxWidth){targetHeightMath.round(targetHeight*maxWidth/targetWidth)targetWidthmaxWidth}if(targetHeightmaxHeight){targetWidthMath.round(targetWidth*maxHeight/targetHeight)targetHeightmaxHeight}// 创建缩放的 PixelMapconstpixelMapawaitsource.createPixelMap({desiredSize:{width:targetWidth,height:targetHeight},desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,})// 保存constpackerimage.createImagePacker()constpackedImageawaitpacker.packing(pixelMap,{format:image/jpeg,quality:80})constfilefs.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):Promisevoid{constsourceimage.createImageSource(filePath)// 读取图像属性constinfoawaitsource.getImageInfo(0)constorientation(infoasRecordstring,Object)[exifOrientation]asnumber??0// 根据方向旋转letrotation0switch(orientation){case3:rotation180;breakcase6:rotation90;breakcase8:rotation270;break}if(rotation0){// 如需旋转重新生成 PixelMap 并保存覆盖原文件// ...}source.release()}六、AnalyzingPage 中的图片使用6.1 页面入口// AnalyzingPage.etsaboutToAppear(){this.startAnalysis()// ...}privateasyncstartAnalysis(){constimagePathAppStorage.getstring(capture_image_path)??constsourceAppStorage.getstring(capture_source)??camera// 模拟 5 层分析进度for(leti0;i5;i){this.currentStepiawaitnewPromisevoid(resolvesetTimeout(()resolve(),800))}// 生成模拟推理结果constresultthis.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时指定desiredSizeJPEG 压缩质量过低设置不当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 orientation90/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):Promisestring{// 1. 解码图片耗时操作constimageSourceimage.createImageSource(imagePath)constpixelMapawaitimageSource.createPixelMap({desiredSize:{width:1024,height:1024},desiredSamplingQuality:image.SamplingQuality.MEDIUM})// 2. 压缩编码耗时操作constpackerimage.createImagePacker()constpackOptions:image.PackingOption{format:image/jpeg,quality:80}constarrayBufferawaitpacker.packing(pixelMap,packOptions)// 3. 写入文件constoutputPath${getContext().cacheDir}/processed_${Date.now()}.jpgconstfilefs.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|nullnulltry{pixelMapawaitimageSource.createPixelMap(options)// ... 处理图片}finally{if(pixelMap){pixelMap.release()// 释放 native 内存pixelMapnull}}7.3 图像处理性能指标操作耗时参考4000x3000 图片优化建议解码原始200-500ms设置 desiredSize 减小尺寸裁剪 50msPixelMap.crop() 原地裁剪缩放50-150msdesiredSize 在解码时同步缩放JPEG 编码quality80100-300ms适当降低 quality 提速7.4 图像处理工具函数封装// 项目中封装的图像处理工具exportclassImageUtils{// 解码图片为 PixelMap带缩放staticasyncdecodeImage(path:string,maxSize:number1024):Promiseimage.PixelMap{constsourceimage.createImageSource(path)returnsource.createPixelMap({desiredSize:{width:maxSize,height:maxSize}})}// 压缩 PixelMap 并写入文件staticasynccompressToFile(pixelMap:image.PixelMap,outputPath:string,quality:number80):Promisevoid{constpackerimage.createImagePacker()constbufferawaitpacker.packing(pixelMap,{format:image/jpeg,quality})constfilefs.openSync(outputPath,fs.OpenMode.CREATE|fs.OpenMode.WRITE_ONLY)fs.writeSync(file.fd,buffer)fs.closeSync(file)}}如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力