HarmonyOS图片处理应用开发实战:从零构建全功能图片编辑器

前言

随着移动互联网的快速发展,图片处理已成为用户日常高频使用的功能之一。本文将基于HarmonyOS 5.0.2平台,从零开始构建一款功能完善的图片处理应用——可可图片编辑。该应用涵盖图片压缩、裁剪、滤镜美化、水印添加等核心功能,通过本实战项目,您将全面掌握HarmonyOS图像处理的关键技术和最佳实践。

值得一提的是,本项目在5.0.2版本基础上,已经前瞻性地集成了部分HarmonyOS 6.0的新特性。如果您使用的是HarmonyOS 6.0设备,可以直接体验到更加流畅的性能表现和更加丰富的系统能力,充分感受鸿蒙生态的持续演进。

项目已上架华为应用市场,读者可扫码体验:

应用界面展示

扫码体验

扫码下载

  • 应用市场地址:https://appgallery.huawei.com/app/detail?id=tupianbmjidashi.qinglanzhuma.huawei

一、项目整体架构设计

1.1 技术栈选型

本项目采用以下技术方案:

  • 开发环境:DevEco Studio
  • SDK版本:HarmonyOS 5.0.2(14)
  • 路由方案:ZRouter
  • 状态管理:V2架构
  • UI框架:ArkUI声明式开发范式

1.2 核心功能模块

可可图片编辑提供以下核心能力:

  • 图片压缩:高效压缩图片大小,节省存储空间
  • 精准裁剪:自定义裁剪区域,满足多样化需求
  • 滤镜美化:多种滤镜效果,一键提升图片质感
  • 水印添加:支持文字和图片水印,保护原创内容
  • 拼图创作:多图组合创意拼接
  • 绘画涂鸦:自由涂鸦标记

1.3 工程初始化

使用DevEco Studio创建标准的Stage模型工程,并集成ZRouter路由管理方案:

# 安装ZRouter
ohpm install @hzw/zrouter

工程基础架构采用Tab组件搭建多页面结构,实现功能模块的清晰划分。

Tab结构示意


二、图片压缩功能实战

图片压缩是图片处理应用的基础功能,主要涉及四个核心步骤:选择图片 → 压缩处理 → 保存到图库 → 分享图片。

图片压缩流程

2.1 选择图片

HarmonyOS提供了PhotoViewPicker选择器,可便捷地从相册选择或拍照获取图片:

PhotoViewPicker选择器

import { photoAccessHelper } from '@kit.MediaLibraryKit';

async selectPhoto() {
  // 创建照片选择参数对象
  const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
  // 仅选择图片类型
  photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
  // 限制最多选择1张
  photoSelectOptions.maxSelectNumber = 1;
  
  // 创建系统相册选择器实例
  const photoPicker = new photoAccessHelper.PhotoViewPicker();
  // 打开选择器并获取用户选择结果
  const photoSelectResult = await photoPicker.select(photoSelectOptions);
  
  // 保存图片URI供后续使用
  this.selectedPhotoUri = photoSelectResult?.photoUris?.[0] ?? '';
}

2.2 图片压缩核心逻辑

使用ImagePacker进行图片压缩编码,支持自定义压缩质量:

ImagePacker压缩

import { image } from '@kit.ImageKit';
import { fileIo } from '@kit.CoreFileKit';

async compressImage() {
  if (!this.selectedPhotoUri) {
    AlertDialog.show({ message: '请先选择图片' });
    return;
  }
  
  try {
    const context = this.getUIContext().getHostContext()!;
    
    // 以只读方式打开所选图片
    const file = fileIo.openSync(this.selectedPhotoUri, fileIo.OpenMode.READ_ONLY);
    
    // 基于文件描述符创建ImageSource
    const src = image.createImageSource(file.fd);
    
    // 创建ImagePacker实例
    const packer = image.createImagePacker();
    
    // 构造压缩目标路径
    const targetPath = context.cacheDir + '/' + Date.now() + '.jpg';
    const newFile = fileIo.openSync(targetPath,
      fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
    
    // 压缩图片为JPEG格式
    await packer.packToFile(src, newFile.fd, {
      format: 'image/jpeg',
      quality: 10  // 质量参数:0-100,数值越小压缩越强
    });
    
    this.compressedFilePath = targetPath;
    AlertDialog.show({ message: '压缩成功' });
  } catch (err) {
    AlertDialog.show({ message: `压缩失败:${JSON.stringify(err)}` });
  }
}

2.3 保存到图库

使用SaveButton安全控件和photoAccessHelper将压缩后的图片保存到系统图库:

import { fileUri } from '@kit.CoreFileKit';

async saveToGallery() {
  if (!this.compressedFilePath) {
    AlertDialog.show({ message: '请先执行压缩操作' });
    return;
  }
  
  try {
    const context = this.getUIContext().getHostContext()!;
    const uri = fileUri.getUriFromPath(this.compressedFilePath);
    
    // 创建图片资产改变请求
    const assetChangeRequest = 
      photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(context, uri);
    
    // 获取相册管理实例
    const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    
    // 应用图片资产改变请求
    await phAccessHelper.applyChanges(assetChangeRequest);
    
    AlertDialog.show({ message: '已保存到图库' });
  } catch (err) {
    AlertDialog.show({ message: `保存失败:${JSON.stringify(err)}` });
  }
}

关键点说明

  • SaveButton是安全控件,首次使用需用户授权,后续操作自动获得一分钟访问权限
  • 无需申请复杂的媒体库权限即可实现保存功能

2.4 分享图片

集成ShareKit实现跨应用分享:

分享功能

import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';

async shareImage() {
  try {
    const uiContext: UIContext = this.getUIContext();
    const context: common.UIAbilityContext = 
      uiContext.getHostContext() as common.UIAbilityContext;
    
    // 根据文件扩展名确定UTD类型
    let utdTypeId: string;
    const ext = this.selectedPhotoUri.split('.').pop();
    switch (ext) {
      case 'jpeg':
      case 'jpg':
        utdTypeId = uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(
          '.jpg', uniformTypeDescriptor.UniformDataType.IMAGE);
        break;
      case 'png':
        utdTypeId = uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(
          '.png', uniformTypeDescriptor.UniformDataType.IMAGE);
        break;
      default:
        utdTypeId = uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(
          '.jpg', uniformTypeDescriptor.UniformDataType.IMAGE);
    }
    
    // 创建分享数据
    const shareData: systemShare.SharedData = new systemShare.SharedData({
      utd: utdTypeId,
      title: '图片分享',
      description: '来自可可图片编辑',
      uri: fileUri.getUriFromPath(this.selectedPhotoUri),
    });
    
    // 创建分享控制器
    const controller: systemShare.ShareController = 
      new systemShare.ShareController(shareData);
    
    // 显示分享面板
    await controller.show(context, {
      selectionMode: systemShare.SelectionMode.SINGLE,
      previewMode: systemShare.SharePreviewMode.DETAIL
    });
    
    console.log('分享成功');
  } catch (e) {
    console.error('分享失败', e);
  }
}

三、图片裁剪功能实现

图片裁剪的核心是利用Canvas画布技术实现自定义区域选择和图像绘制。

Canvas裁剪原理

3.1 Canvas基础知识

Canvas组件提供了强大的2D绘图能力,使用步骤如下:

@Component
struct CanvasDemo {
  // 1. 配置抗锯齿参数
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  
  // 2. 创建画布上下文
  private context: CanvasRenderingContext2D = 
    new CanvasRenderingContext2D(this.settings);
  
  build() {
    Column() {
      // 3. 渲染画布组件
      Canvas(this.context)
        .width('100%')
        .height('100%')
        .onReady(() => {
          // 4. 在画布上绘制图形
          this.context.strokeRect(50, 50, 200, 150);
        })
    }
  }
}

Canvas坐标系:以左上角为原点(0,0),向右为X轴正方向,向下为Y轴正方向。

Canvas坐标系

3.2 Canvas常用绘图API

绘制直线
this.context.moveTo(10, 10);   // 起点
this.context.lineTo(100, 100); // 终点
this.context.stroke();          // 描边
绘制矩形
// 方式1:使用直线组合
this.context.moveTo(10, 10);
this.context.lineTo(300, 10);
this.context.lineTo(300, 300);
this.context.lineTo(10, 300);
this.context.closePath();  // 闭合路径
this.context.stroke();

// 方式2:直接绘制矩形
this.context.strokeRect(50, 50, 200, 150);
绘制弧形
// arc(x, y, radius, startAngle, endAngle, counterclockwise)
// 角度单位为弧度:360° = 2π ≈ 6.28
this.context.beginPath();
this.context.arc(100, 75, 50, 0, Math.PI / 2);
this.context.stroke();
绘制文本
this.context.font = '55px sans-serif';
this.context.fillText("Hello World!", 20, 60);    // 填充文本
this.context.strokeText("Hello World!", 20, 120); // 描边文本

3.3 图片裁剪核心实现

裁剪功能结合ImageSourcedrawImage方法实现:

import { image } from '@kit.ImageKit';

async cropImage() {
  if (!this.selectedPhotoUri) {
    AlertDialog.show({ message: '请先选择图片' });
    return;
  }
  
  try {
    // 打开图片文件
    const file = fileIo.openSync(this.selectedPhotoUri, fileIo.OpenMode.READ_ONLY);
    const src = image.createImageSource(file.fd);
    
    // 获取图片信息
    const info: image.ImageInfo = await src.getImageInfo();
    const w: number = info.size.width;
    const h: number = info.size.height;
    
    // 创建PixelMap
    const pm: image.PixelMap = await src.createPixelMap();
    
    // 定义裁剪区域参数
    const cropSize: number = 100;
    const sx: number = 200;  // 裁剪起始X坐标
    const sy: number = 200;  // 裁剪起始Y坐标
    const sw: number = Math.min(cropSize, w);  // 裁剪宽度
    const sh: number = Math.min(cropSize, h);  // 裁剪高度
    
    // 清空画布并绘制裁剪区域
    this.context.clearRect(0, 0, 200, 200);
    
    // drawImage(源图片, 源X, 源Y, 源宽, 源高, 目标X, 目标Y, 目标宽, 目标高)
    this.context.drawImage(pm, sx, sy, sw, sh, 0, 0, sw, sh);
    
    console.log(`裁剪成功: ${sx},${sy},${sw}x${sh}`);
  } catch (err) {
    AlertDialog.show({ message: `裁剪失败:${JSON.stringify(err)}` });
  }
}

技术要点

  • createPixelMap创建可操作的像素图对象
  • drawImage支持9参数模式实现精准裁剪
  • 通过调整源区域(sx, sy, sw, sh)实现任意位置裁剪

四、滤镜效果开发

滤镜功能基于Image组件的colorFilter属性实现,通过颜色矩阵变换改变图片色调。

滤镜效果展示

多种滤镜效果

4.1 colorFilter核心原理

colorFilter使用4×5颜色矩阵对每个像素的RGBA值进行数学变换:

R' = r1*R + r2*G + r3*B + r4*A + r5*255
G' = g1*R + g2*G + g3*B + g4*A + g5*255
B' = b1*R + b2*G + b3*B + b4*A + b5*255
A' = a1*R + a2*G + a3*B + a4*A + a5*255

矩阵格式:

[r1, r2, r3, r4, r5,
 g1, g2, g3, g4, g5,
 b1, b2, b3, b4, b5,
 a1, a2, a3, a4, a5]

颜色矩阵原理

4.2 常用滤镜矩阵

原图效果(单位矩阵)

原图效果

const originalMatrix = [
  1, 0, 0, 0, 0,
  0, 1, 0, 0, 0,
  0, 0, 1, 0, 0,
  0, 0, 0, 1, 0
];
灰度滤镜

灰度效果

const grayMatrix = [
  0.299, 0.587, 0.114, 0, 0,
  0.299, 0.587, 0.114, 0, 0,
  0.299, 0.587, 0.114, 0, 0,
  0,     0,     0,     1, 0
];

const grayFilter: ColorFilter = new ColorFilter(grayMatrix);
颜色反转(负片效果)

颜色反转效果

const invertMatrix = [
  -1, 0, 0, 0, 1,
   0,-1, 0, 0, 1,
   0, 0,-1, 0, 1,
   0, 0, 0, 1, 0
];
棕褐色怀旧效果

棕褐色效果

const sepiaMatrix = [
  0.393, 0.769, 0.189, 0, 0,
  0.349, 0.686, 0.168, 0, 0,
  0.272, 0.534, 0.131, 0, 0,
  0,     0,     0,     1, 0
];
亮度调节

亮度调节效果

const brightness = 0.3; // 正数变亮,负数变暗
const brightnessMatrix = [
  1, 0, 0, 0, brightness,
  0, 1, 0, 0, brightness,
  0, 0, 1, 0, brightness,
  0, 0, 0, 1, 0
];
暖色调/冷色调

冷暖色调效果

暖色调

const warmMatrix = [
  1.2, 0, 0, 0, 0,  // 增强红色
  0, 1.1, 0, 0, 0,  // 增强绿色
  0, 0, 0.9, 0, 0,  // 减弱蓝色
  0, 0, 0, 1, 0
];

冷色调

const coolMatrix = [
  0.9, 0, 0, 0, 0,  // 减弱红色
  0, 1.0, 0, 0, 0,
  0, 0, 1.2, 0, 0,  // 增强蓝色
  0, 0, 0, 1, 0
];

4.3 使用示例

@Component
struct FilterDemo {
  private grayMatrix: number[] = [
    0.299, 0.587, 0.114, 0, 0,
    0.299, 0.587, 0.114, 0, 0,
    0.299, 0.587, 0.114, 0, 0,
    0, 0, 0, 1, 0
  ];
  private grayFilter: ColorFilter = new ColorFilter(this.grayMatrix);

  build() {
    Column({ space: 20 }) {
      // 原图
      Image($r('app.media.startIcon'))
        .width(100)
        .height(100)
      
      // 应用灰度滤镜
      Image($r('app.media.startIcon'))
        .width(100)
        .height(100)
        .colorFilter(this.grayFilter)
    }
  }
}

4.4 使用DrawingColorFilter(推荐)

HarmonyOS还提供了更便捷的DrawingColorFilter API:

BlendMode混合模式

import { drawing } from '@kit.ArkGraphics2D';

// 方式1:使用混合模式着色
let redFilter = drawing.ColorFilter.createBlendModeColorFilter(
  { alpha: 255, red: 255, green: 0, blue: 0 },
  drawing.BlendMode.SRC_IN
);

// 方式2:使用矩阵
let matrix = [
  0.299, 0.587, 0.114, 0, 0,
  0.299, 0.587, 0.114, 0, 0,
  0.299, 0.587, 0.114, 0, 0,
  0, 0, 0, 1, 0
];
let grayFilter = drawing.ColorFilter.createMatrixColorFilter(matrix);

// 应用滤镜
Image($r('app.media.startIcon'))
  .colorFilter(redFilter);

五、水印功能实现

水印功能巧妙结合了UI层叠布局和组件截图技术。

水印效果展示

5.1 实现思路

  1. 预览阶段:使用Stack层叠布局将水印文本/图片覆盖在原图上
  2. 保存阶段:使用componentSnapshot对整个组件截图生成带水印的新图片

5.2 组件截图API详解

componentSnapshot是HarmonyOS提供的组件截图能力,可将UI组件转换为PixelMap图片数据。

基本使用步骤

1. 为组件添加唯一标识

Stack() {
  // 组件内容
}
.id("targetComponent")

2. 获取截图(异步方式)

async takeScreenshot() {
  try {
    const uiContext = this.getUIContext();
    
    const pixelMap = await uiContext.getComponentSnapshot()
      .get('targetComponent', {
        scale: 1.0,                      // 缩放比例
        waitUntilRenderFinished: true    // 等待渲染完成
      });
    
    // 使用pixelMap进行后续处理
    this.screenshotImage = pixelMap;
  } catch (error) {
    console.error('截图失败:', error);
  }
}

3. 获取截图(同步方式)

截图效果对比

takeScreenshot() {
  try {
    const uiContext = this.getUIContext();
    
    const pixelMap = uiContext.getComponentSnapshot()
      .getSync('targetComponent', {
        scale: 1.0,
        waitUntilRenderFinished: true
      });
    
    this.screenshotImage = pixelMap;
  } catch (error) {
    console.error('截图失败:', error);
  }
}

5.3 SnapshotOptions配置参数

参数 类型 说明
scale number 缩放比例,范围0.1-1.0,默认1.0
waitUntilRenderFinished boolean 是否等待渲染完成(推荐true)
region Object 指定截图区域
region.start number 起始X坐标
region.top number 起始Y坐标
region.end number 结束X坐标
region.bottom number 结束Y坐标

5.4 完整水印实现示例

import { image } from '@kit.ImageKit';

@ComponentV2
struct WatermarkDemo {
  @Local
  screenshotImage: image.PixelMap | undefined = undefined;
  
  // 添加水印并截图
  addWatermarkAndCapture = async () => {
    try {
      const uiContext = this.getUIContext();
      
      // 截取带水印的组件
      const pixelMap = await uiContext.getComponentSnapshot()
        .get('watermarkContainer', {
          scale: 1.0,
          waitUntilRenderFinished: true
        });
      
      this.screenshotImage = pixelMap;
      
      // 可选:保存到图库
      // await this.saveToGallery(pixelMap);
      
    } catch (error) {
      console.error('水印添加失败:', error);
    }
  }
  
  build() {
    Column({ space: 20 }) {
      Button("添加水印")
        .onClick(this.addWatermarkAndCapture)
      
      // 原图+水印层叠布局
      Stack({ alignContent: Alignment.BottomEnd }) {
        // 原始图片
        Image($r("app.media.startIcon"))
          .width(200)
          .height(200)
        
        // 水印文字
        Text("© 可可图片编辑")
          .fontColor(Color.White)
          .backgroundColor(Color.Black)
          .opacity(0.6)
          .padding(8)
      }
      .width(200)
      .height(200)
      .id("watermarkContainer")  // 重要:设置ID用于截图
      
      // 显示截图结果
      if (this.screenshotImage) {
        Image(this.screenshotImage)
          .width(100)
          .height(100)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

关键技术点

  1. 使用Stack实现水印覆盖布局
  2. 通过alignContent调整水印位置(TopStart/TopEnd/BottomStart/BottomEnd等)
  3. 使用opacity调整水印透明度
  4. waitUntilRenderFinished: true确保水印渲染完成后再截图

六、应用打包与发布

6.1 在AGC创建应用

访问AppGallery Connect控制台创建HarmonyOS应用:

  • 官方文档:https://developer.huawei.com/consumer/cn/doc/app/agc-help-createharmonyapp-0000001945392297

6.2 配置应用签名证书

在DevEco Studio中配置签名信息:

  1. 生成密钥库文件(.p12)
  2. 申请证书(.cer)
  3. 申请Profile文件(.p7b)
  4. 在build-profile.json5中配置签名信息

详细步骤参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-publish-app

6.3 构建发布包

# 构建Release版本APP包
hvigorw assembleApp --mode module -p product=default -p buildMode=release

6.4 发布到应用市场

通过AGC控制台上传APP包并完成应用发布:

  • 填写应用信息(名称、图标、截图、描述等)
  • 设置应用分类和标签
  • 提交审核
  • 审核通过后发布上架

发布指南:https://developer.huawei.com/consumer/cn/doc/app/agc-help-release-app-guide-0000002287176372


七、技术总结与最佳实践

7.1 核心技术点回顾

功能模块 核心API 关键技术
图片选择 PhotoViewPicker MediaLibraryKit
图片压缩 ImagePacker 质量参数控制
保存图库 SaveButton + photoAccessHelper 安全控件授权
图片分享 ShareKit UTD类型识别
图片裁剪 Canvas + drawImage 坐标系计算
滤镜效果 colorFilter 颜色矩阵变换
水印添加 componentSnapshot 组件截图

7.2 开发建议

  1. 权限管理:优先使用安全控件(SaveButton等)减少权限申请
  2. 性能优化:大图压缩前先进行尺寸缩放,避免内存溢出
  3. 用户体验:异步操作添加Loading提示,避免界面卡顿
  4. 错误处理:完善try-catch机制,提供友好的错误提示
  5. 代码复用:将通用图片处理逻辑封装为工具类

7.3 扩展方向

  • 支持批量图片处理
  • 添加更多滤镜效果(马赛克、模糊、锐化等)
  • 实现图片拼接与拼图功能
  • 集成AI能力(智能抠图、图像修复等)
  • 支持GIF动图编辑

结语

通过本文的实战教程,我们完整实现了一款功能丰富的HarmonyOS图片处理应用。项目涵盖了图片压缩、裁剪、滤镜、水印等核心功能,深入使用了HarmonyOS的媒体库、Canvas绘图、组件截图等关键技术。

希望本文能帮助开发者快速掌握HarmonyOS图像处理的开发技巧,为构建更多优秀的原生应用打下坚实基础。完整源码和更多技术细节,欢迎体验应用并交流讨论!


作者简介:HarmonyOS开发实践者,专注于移动应用开发与技术分享。

本文示例代码基于:HarmonyOS 5.0.2(14) SDK

参考文档

  • HarmonyOS官方开发文档
  • ArkUI组件参考
  • 媒体库开发指南
Logo

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

更多推荐