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 — 确认使用照片
private confirmPhoto() {
AppStorage.setOrCreate<string>('capture_image_path', this.previewPath)
this.getUIContext().getRouter().pushUrl({
url: 'pages/AnalyzingPage',
params: { imagePath: this.previewPath }
})
}
// AnalyzingPage — 接收图片
private async startAnalysis() {
const imagePath = AppStorage.get<string>('capture_image_path') ?? ''
const source = AppStorage.get<string>('capture_source') ?? 'camera'
// ...
}
二、图片解码:PixelMap
2.1 从文件加载 PixelMap
import { image } from '@kit.ImageKit'
async function loadPixelMap(filePath: string): Promise<image.PixelMap> {
const source = image.createImageSource(filePath)
const pixelMap = await source.createPixelMap({
desiredPixelFormat: image.ImagePixelFormat.RGBA_8888,
desiredSize: { width: 1080, height: 1920 },
fitDensity: 0
})
source.release()
return pixelMap
}
参数说明:
| 参数 | 作用 | 推荐值 | 说明 |
|---|---|---|---|
desiredPixelFormat |
像素格式 | RGBA_8888 |
最高质量 |
desiredSize |
目标尺寸 | 1080x1920 |
限制最大分辨率 |
fitDensity |
密度适配 | 0 |
不需要适配 |
2.2 从 PixelMap 保存为文件
async function savePixelMap(pixelMap: image.PixelMap, outputPath: string): Promise<void> {
const packer = image.createImagePacker()
const packedImage = await packer.packing(pixelMap, {
format: 'image/jpeg',
quality: 85
})
const file = fs.openSync(outputPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
file.writeSync(packedImage.data.buffer)
fs.closeSync(file)
packer.release()
}
三、图片裁剪
3.1 裁剪功能
async function cropImage(
sourcePath: string,
outputPath: string,
region: image.Region
): Promise<void> {
const source = image.createImageSource(sourcePath)
const pixelMap = await source.createPixelMap({
cropRegion: region, // 裁剪区域
desiredPixelFormat: image.ImagePixelFormat.RGBA_8888,
})
// 保存裁剪后的图片
const packer = image.createImagePacker()
const packedImage = await packer.packing(pixelMap, {
format: 'image/jpeg',
quality: 85
})
const file = 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 // 裁剪尺寸
}
await cropImage(inputPath, outputPath, region)
四、图片缩放
4.1 缩放到指定尺寸
async function resizeImage(
sourcePath: string,
outputPath: string,
maxWidth: number,
maxHeight: number
): Promise<void> {
const source = image.createImageSource(sourcePath)
// 获取原始尺寸
const info = await source.getImageInfo(0) as {
size?: { width: number, height: number }
}
const srcWidth = info.size?.width ?? 1920
const srcHeight = info.size?.height ?? 1080
// 等比缩放计算
let targetWidth = srcWidth
let targetHeight = srcHeight
if (targetWidth > maxWidth) {
targetHeight = Math.round(targetHeight * maxWidth / targetWidth)
targetWidth = maxWidth
}
if (targetHeight > maxHeight) {
targetWidth = Math.round(targetWidth * maxHeight / targetHeight)
targetHeight = maxHeight
}
// 创建缩放的 PixelMap
const pixelMap = await source.createPixelMap({
desiredSize: { width: targetWidth, height: targetHeight },
desiredPixelFormat: image.ImagePixelFormat.RGBA_8888,
})
// 保存
const packer = image.createImagePacker()
const packedImage = await packer.packing(pixelMap, {
format: 'image/jpeg',
quality: 80
})
const file = 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 方向信息,需要修正:
async function correctOrientation(filePath: string): Promise<void> {
const source = image.createImageSource(filePath)
// 读取图像属性
const info = await source.getImageInfo(0)
const orientation = (info as Record<string, Object>)['exifOrientation'] as number ?? 0
// 根据方向旋转
let rotation = 0
switch (orientation) {
case 3: rotation = 180; break
case 6: rotation = 90; break
case 8: rotation = 270; break
}
if (rotation > 0) {
// 如需旋转,重新生成 PixelMap 并保存覆盖原文件
// ...
}
source.release()
}
六、AnalyzingPage 中的图片使用
6.1 页面入口
// AnalyzingPage.ets
aboutToAppear() {
this.startAnalysis()
// ...
}
private async startAnalysis() {
const imagePath = AppStorage.get<string>('capture_image_path') ?? ''
const source = AppStorage.get<string>('capture_source') ?? 'camera'
// 模拟 5 层分析进度
for (let i = 0; i < 5; i++) {
this.currentStep = i
await new Promise<void>(resolve => setTimeout(() => resolve(), 800))
}
// 生成模拟推理结果
const result = this.generateMockResult(source, archiveId)
// 写入 DB
await HandwritingDao.create({ /* ... */ })
await ReportDao.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 函数中执行,不阻塞 UI
private async processImage(imagePath: string): Promise<string> {
// 1. 解码图片(耗时操作)
const imageSource = image.createImageSource(imagePath)
const pixelMap = await imageSource.createPixelMap({
desiredSize: { width: 1024, height: 1024 },
desiredSamplingQuality: image.SamplingQuality.MEDIUM
})
// 2. 压缩编码(耗时操作)
const packer = image.createImagePacker()
const packOptions: image.PackingOption = { format: 'image/jpeg', quality: 80 }
const arrayBuffer = await packer.packing(pixelMap, packOptions)
// 3. 写入文件
const outputPath = `${getContext().cacheDir}/processed_${Date.now()}.jpg`
const file = fs.openSync(outputPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY)
fs.writeSync(file.fd, arrayBuffer)
fs.closeSync(file)
// 4. 释放 PixelMap 内存
pixelMap.release()
return outputPath
}
7.2 PixelMap 内存管理
PixelMap 占用大量内存,使用后必须及时释放:
let pixelMap: image.PixelMap | null = null
try {
pixelMap = await imageSource.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 图像处理工具函数封装
// 项目中封装的图像处理工具
export class ImageUtils {
// 解码图片为 PixelMap(带缩放)
static async decodeImage(path: string, maxSize: number = 1024): Promise<image.PixelMap> {
const source = image.createImageSource(path)
return source.createPixelMap({ desiredSize: { width: maxSize, height: maxSize } })
}
// 压缩 PixelMap 并写入文件
static async compressToFile(pixelMap: image.PixelMap, outputPath: string, quality: number = 80): Promise<void> {
const packer = image.createImagePacker()
const buffer = await packer.packing(pixelMap, { format: 'image/jpeg', quality })
const file = fs.openSync(outputPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY)
fs.writeSync(file.fd, buffer)
fs.closeSync(file)
}
}
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
更多推荐



所有评论(0)