HarmonyOS APP实战-画图APP - 第8篇:图像效果应用(亮度/模糊/灰度)

HarmonyOS APP实战-画图APP - 第8篇:使用effectKit处理图像

1. 开篇

上一篇我们实现了在画布上添加文字的功能,用户可以通过对话框输入文字内容、选择字体大小和颜色,并通过拖拽将文字放置在画布任意位置。文字绘制基于CanvasRenderingContext2DfillText方法,配合measureText实现精准定位。至此,画图APP已经具备了形状绘制、手绘、颜色调节、文字标注等核心交互能力。

然而,用户还希望对自己的作品应用滤镜效果------比如调节整体亮度、添加模糊或变为灰度风格。HarmonyOS提供的@ohos.effectKit正能满足这一需求,它能对PixelMap图像高效地进行亮度调节、模糊化、灰度调节等操作。本篇我们将将这个能力集成到APP中:用户选择一个效果并调整参数后,画布图像会实时更新,并支持反复应用不同效果。

2. 核心实现

2.1 基础配置 -- 导入模块与类型定义

首先在项目根目录的oh-package.json5中确认@ohos.effectKit已被包含(默认系统能力无需额外安装)。然后在需要使用的地方导入模块:

typescript 复制代码
// utils/EffectProcessor.ts
import { effectKit } from '@ohos.effectKit'; // 图像效果模块
import { image } from '@ohos.multimedia.image'; // PixelMap 类型

@ohos.effectKit提供了EffectKit类,通过createEffect方法为PixelMap创建图像效果对象,再调用blurbrightnessgrayscale等方法应用效果,最后通过getPixelMap获取结果。

关键点说明:

  • effectKitcreateEffect接受一个PixelMap参数,返回ImageEffect实例。
  • brightness(brightness: number):参数范围-1到1,-1为全黑,0为原始,1为全白。
  • blur(radius: number):参数为模糊半径,数值越大越模糊。
  • grayscale():无参数,直接转为灰度。
  • 每个方法返回ImageEffect自身,可以链式调用,但建议分开调用以保持清晰。

2.2 核心逻辑 -- EffectProcessor封装

封装一个工具类EffectProcessor,包含三个静态方法,分别对传入的PixelMap进行处理并返回新的PixelMap。注意:原始PixelMap不应被修改,因此每次处理都通过createEffect创建副本。

typescript 复制代码
// utils/EffectProcessor.ts
import { effectKit, ImageEffect } from '@ohos.effectKit';
import { image } from '@ohos.multimedia.image';

export class EffectProcessor {
  /**
   * 亮度调节
   * @param source 源PixelMap
   * @param brightness 亮度值,-1~1
   * @returns 处理后的PixelMap
   */
  static async applyBrightness(source: image.PixelMap, brightness: number): Promise<image.PixelMap> {
    // 创建效果对象
    const effect: ImageEffect = effectKit.createEffect(source);
    // 应用亮度调节
    effect.brightness(brightness);
    // 获取结果PixelMap
    return await effect.getPixelMap();
  }

  /**
   * 模糊化
   * @param source 源PixelMap
   * @param radius 模糊半径
   * @returns 处理后的PixelMap
   */
  static async applyBlur(source: image.PixelMap, radius: number): Promise<image.PixelMap> {
    const effect: ImageEffect = effectKit.createEffect(source);
    effect.blur(radius);
    return await effect.getPixelMap();
  }

  /**
   * 灰度调节
   * @param source 源PixelMap
   * @returns 处理后的PixelMap
   */
  static async applyGrayscale(source: image.PixelMap): Promise<image.PixelMap> {
    const effect: ImageEffect = effectKit.createEffect(source);
    effect.grayscale();
    return await effect.getPixelMap();
  }
}

关键点说明:

  • createEffect不会修改源PixelMap,它会基于源数据生成一个内部副本。
  • getPixelMap()是异步方法,返回Promise<PixelMap>,需要await。
  • 每次调用均应捕获异常,如参数越界可能导致崩溃。实际应用中建议增加参数校验和try-catch。

2.3 完整模块 -- EffectDialog与集成

接下来实现EffectDialog自定义弹窗,让用户选择效果类型并调节对应参数。同时需要修改主页面,增加"效果"按钮并调用此对话框。

2.3.1 EffectDialog组件

创建pages/EffectDialog.ets文件,使用@CustomDialog装饰器。对话框包含三个单选按钮(亮度/模糊/灰度)、对应的滑块(亮度和模糊需要调节),以及确定/取消按钮。

typescript 复制代码
// pages/EffectDialog.ets
import { EffectProcessor } from '../utils/EffectProcessor';
import { image } from '@ohos.multimedia.image';

@CustomDialog
export struct EffectDialog {
  controller: CustomDialogController;
  // 传入当前画布的PixelMap
  currentPixelMap: image.PixelMap | null = null;
  // 回调函数,返回处理后的PixelMap
  onConfirm: (result: image.PixelMap) => void;

  // 内部状态
  @State selectedEffect: number = 0; // 0:亮度 1:模糊 2:灰度
  @State brightnessValue: number = 0; // -1~1
  @State blurRadius: number = 5; // 0~20

  build() {
    Column() {
      Text('选择图像效果').fontSize(18).margin({ bottom: 16 });

      // 效果类型选择
      Radio({ value: 'brightness', group: 'effectGroup' })
        .checked(true)
        .onChange(() => { this.selectedEffect = 0; })
        .height(30)
      Text('亮度调节').margin({ left: 8 });

      Radio({ value: 'blur', group: 'effectGroup' })
        .onChange(() => { this.selectedEffect = 1; })
        .height(30)
      Text('模糊化').margin({ left: 8 });

      Radio({ value: 'grayscale', group: 'effectGroup' })
        .onChange(() => { this.selectedEffect = 2; })
        .height(30)
      Text('灰度').margin({ left: 8 });

      // 动态参数调节
      if (this.selectedEffect === 0) {
        Row() {
          Text('亮度').width(60);
          Slider({
            value: this.brightnessValue,
            min: -1,
            max: 1,
            step: 0.01
          }).onChange((val: number) => {
            this.brightnessValue = val;
          }).width(200);
        }.margin({ top: 12 });
      } else if (this.selectedEffect === 1) {
        Row() {
          Text('模糊半径').width(80);
          Slider({
            value: this.blurRadius,
            min: 0,
            max: 20,
            step: 1
          }).onChange((val: number) => {
            this.blurRadius = val;
          }).width(200);
        }.margin({ top: 12 });
      }

      // 预览与确认按钮
      Row() {
        Button('取消').onClick(() => {
          this.controller.close();
        });
        Button('应用').onClick(async () => {
          if (!this.currentPixelMap) {
            console.error('No pixelmap to process');
            return;
          }
          try {
            let result: image.PixelMap;
            switch (this.selectedEffect) {
              case 0:
                result = await EffectProcessor.applyBrightness(this.currentPixelMap, this.brightnessValue);
                break;
              case 1:
                result = await EffectProcessor.applyBlur(this.currentPixelMap, this.blurRadius);
                break;
              case 2:
                result = await EffectProcessor.applyGrayscale(this.currentPixelMap);
                break;
            }
            this.onConfirm(result);
            this.controller.close();
          } catch (err) {
            console.error('Effect application failed: ' + err);
          }
        });
      }.justifyContent(FlexAlign.Center).width('100%').margin({ top: 20 });
    }.padding(20).width('80%');
  }
}

关键点说明:

  • @CustomDialog的组件需要传入controller和回调。
  • 通过selectedEffect变量控制显示对应的滑块。
  • 点击"应用"后调用EffectProcessor的静态方法,并通过onConfirm将结果传回主页面。
  • 由于getPixelMap()是异步,这里使用async/await。
2.3.2 主页面集成效果按钮与画布快照

修改主页面pages/Index.ets,在顶部工具栏添加一个"效果"按钮。点击时:

  1. 通过Canvas的snapshot方法获取当前画布的PixelMap
  2. 弹出EffectDialog,将PixelMap传入。
  3. 对话框确认后,使用CanvasRenderingContext2DdrawImage将处理后的PixelMap绘制回画布。
typescript 复制代码
// pages/Index.ets(部分代码,仅展示新增部分)
import { EffectDialog } from './EffectDialog';
import { image } from '@ohos.multimedia.image';

// ... 已有状态和组件声明
@State canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();
private canvasController: CanvasController = new CanvasController();
dialogController: CustomDialogController | null = null;

// 效果按钮点击事件
async onEffectClick() {
  // 步骤1: 获取当前画布快照为PixelMap
  try {
    const pixelMap: image.PixelMap = await this.canvasController.snapshot();
    // 步骤2: 创建并打开效果对话框
    this.dialogController = new CustomDialogController({
      builder: EffectDialog({
        currentPixelMap: pixelMap,
        onConfirm: (result: image.PixelMap) => {
          // 步骤3: 将处理后的PixelMap绘制到画布
          this.canvasContext.drawImage(result, 0, 0);
        }
      }),
      cancel: () => { console.log('效果取消'); },
      autoCancel: true
    });
    this.dialogController.open();
  } catch (err) {
    console.error('Snapshot failed: ' + err);
  }
}

// build()中添加效果按钮
ToolbarButton({ text: '效果', onClick: this.onEffectClick.bind(this) })

关键点说明:

  • CanvasControllersnapshot()方法返回Promise<PixelMap>,是HarmonyOS Canvas组件提供的官方API。
  • drawImage可以将PixelMap直接绘制到Canvas上,完整替换画布内容(之前绘制的图形都会保留,但被覆盖)。
  • 如果需要保留历史,建议在应用效果前将原始PixelMap推入撤销栈(下一系列会实现),这里为了简洁直接替换。
2.3.4 工具方法异常处理补充

EffectProcessor中添加基本的异常捕获,避免应用崩溃:

typescript 复制代码
// 在applyBrightness中添加 try-catch 示例
static async applyBrightness(source: image.PixelMap, brightness: number): Promise<image.PixelMap> {
  try {
    const effect = effectKit.createEffect(source);
    effect.brightness(brightness);
    return await effect.getPixelMap();
  } catch (error) {
    console.error(`applyBrightness error: ${error.code}, ${error.message}`);
    throw error; // 向上传递,由调用方处理
  }
}

3. 运行验证

完成上述代码后,运行APP。在顶部工具栏可以看到新增的"效果"按钮(图标可自定义,本文略)。点击后弹出效果选择对话框,默认选中"亮度调节",滑块范围-1到1。调节滑块并点击"应用",画布上的所有内容(包括之前绘制的图形和文字)会实时应用亮度效果。选择"模糊化"并调节半径,画布变模糊;选择"灰度"后画布变为黑白风格。

可通过多次点击"效果"应用不同效果的组合(例如先模糊再调整亮度)。每次应用前都会基于当前画布快照进行处理,叠加效果自然。

注意:由于snapshot获取的是当前画布像素,因此应用效果会覆盖原来的绘制内容。若要保留原始图层,可考虑在前端维护一个单独的PixelMap副本,但本系列聚焦于功能完整性,暂不深入。

4. 小结与预告

第8篇我们成功将@ohos.effectKit集成到画图APP中,实现了亮度调节、模糊化和灰度调节三个实用图像效果。核心模块EffectProcessor封装了效果处理逻辑,EffectDialog提供了友好的交互界面。通过本次集成,APP的功能从"绘制"扩展到"图像后期处理",用户体验更加丰富。

下一篇《智能取色与色板生成》将继续使用effectKit的智能取色能力,让用户从画布中提取主色并自动生成色板,进一步提升画图工具的专业性。我们将实现取色区域选择、主色提取算法以及色板UI展示,敬请期待。

相关推荐
●VON1 小时前
HarmonyKit | 鸿蒙开发展望:HarmonyKit 未来路线图与社区共建
华为·单元测试·harmonyos·鸿蒙
HarmonyOS_SDK1 小时前
借助游戏预启动提前加载游戏,读条快人一步
harmonyos
不羁的木木2 小时前
HarmonyOS APP实战-画图APP - 第3篇:矩形与圆形绘制
华为·harmonyos
绝世番茄3 小时前
Column 与 if/else 条件渲染:动态子组件的布局行为
华为·harmonyos·鸿蒙
花开彼岸天~3 小时前
鸿蒙实战:AppStorage 全局状态管理
华为·harmonyos·鸿蒙系统
爱吃大芒果4 小时前
鸿蒙 7 新特性科普:什么是系统 Agent 智能体?
华为·harmonyos·智能体
listening7774 小时前
HarmonyOS 6.1 元服务实战:把电商卡片装进系统“负一屏”
华为·harmonyos
蓝速科技4 小时前
蓝速科技鸿蒙信创终端全场景落地实效展示
人工智能·科技·华为·harmonyos
子非鱼94274 小时前
10-Flutter调用鸿蒙原生能力前置课:MethodChannel和PlatformView准备
学习·flutter·华为·harmonyos