HarmonyOS APP实战-基于Image Kit的图像处理APP - 第6篇:图片滤镜效果实现

HarmonyOS APP实战-基于Image Kit的图像处理APP - 第6篇:滤镜与色彩矩阵

1. 开篇

在上篇「旋转翻转效果」中,我们实现了 RotationPage 页面,用户可以通过点击按钮或滑块对图片进行 90°、180° 旋转以及水平/垂直翻转。核心代码使用了 PixelMaprotateflip 方法,并建立了 RotationManager 工具类封装旋转逻辑,同时通过 image.createPixelMap 创建新的 PixelMap 来保存处理结果。

本篇我们将继续深化图像处理能力,聚焦于滤镜与色彩矩阵 。我们将利用 PixelMap.colorMatrix 属性,实现灰度、怀旧、冷色等预定义滤镜,同时支持用户自定义 RGB 色彩矩阵参数,所有调整均能实时更新预览。这会显著提升 APP 的实用性和视觉效果,让用户能一键为图片赋予不同风格。

2. 核心实现

2.1 基础配置与常量定义

首先,我们需要了解 colorMatrix 的工作原理。它是一个 4×5 的浮点矩阵(共 20 个值),用于控制像素的 RGBA 通道。矩阵格式为:

复制代码
[ r, r, r, r, r ]  → 控制红色通道
[ g, g, g, g, g ]  → 控制绿色通道
[ b, b, b, b, b ]  → 控制蓝色通道
[ a, a, a, a, a ]  → 控制 Alpha 通道

矩阵与像素颜色值的计算方式为:新颜色 = 矩阵 × 原颜色。

我们将在 ColorMatrixManager 类中预定义几种常见滤镜矩阵:

typescript 复制代码
// colorMatrixManager.ets
/**
 * 色彩矩阵管理器
 * 提供预定义滤镜矩阵和自定义矩阵工具
 */
import { image } from '@kit.ImageKit';

// 预定义滤镜类型枚举
export enum FilterType {
  NORMAL = '原图',
  GRAY = '灰度',
  VINTAGE = '怀旧',
  COOL = '冷色',
  WARM = '暖色',
  SEPIA = '棕褐色',
  INVERT = '负片'
}

// 各滤镜对应的 4x5 矩阵(20个浮点数)
export const FILTER_MATRICES: Record<string, number[]> = {
  // 灰度滤镜:将RGB转换为亮度值
  [FilterType.GRAY]: [
    0.33, 0.59, 0.11, 0, 0,
    0.33, 0.59, 0.11, 0, 0,
    0.33, 0.59, 0.11, 0, 0,
    0, 0, 0, 1, 0
  ],
  // 怀旧滤镜:降低饱和度,偏黄棕色调
  [FilterType.VINTAGE]: [
    0.5, 0.3, 0.2, 0, 40,
    0.4, 0.6, 0.2, 0, 20,
    0.2, 0.3, 0.4, 0, 10,
    0, 0, 0, 1, 0
  ],
  // 冷色滤镜:增加蓝色、减少红色
  [FilterType.COOL]: [
    0.5, 0.5, 1.0, 0, 0,
    0.5, 0.8, 1.0, 0, 0,
    1.0, 0.8, 1.2, 0, 0,
    0, 0, 0, 1, 0
  ],
  // 暖色滤镜:增加红/黄色
  [FilterType.WARM]: [
    1.2, 0.9, 0.5, 0, 10,
    1.0, 1.0, 0.5, 0, 5,
    0.6, 0.7, 0.8, 0, -10,
    0, 0, 0, 1, 0
  ],
  // 棕褐色滤镜:经典老照片效果
  [FilterType.SEPIA]: [
    0.39, 0.77, 0.19, 0, 0,
    0.35, 0.69, 0.17, 0, 0,
    0.27, 0.53, 0.13, 0, 0,
    0, 0, 0, 1, 0
  ],
  // 负片滤镜:颜色取反
  [FilterType.INVERT]: [
    -1, 0, 0, 0, 255,
    0, -1, 0, 0, 255,
    0, 0, -1, 0, 255,
    0, 0, 0, 1, 0
  ],
  // 原图滤镜:单位矩阵
  [FilterType.NORMAL]: [
    1, 0, 0, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 0, 0, 1, 0
  ]
};

关键点说明

  • 矩阵的 20 个浮点数对应 4 行 5 列,分别控制 R、G、B、A 通道。第 5 列是颜色偏移值(即亮度 / 对比度调整)。
  • 灰度滤镜将彩色转换为亮度值,亮度公式为 0.33R + 0.59G + 0.11B。
  • 怀旧和冷色等滤镜通过调整各通道的权重和偏移来改变色温。
  • 所有矩阵值都在合理的范围内,避免产生溢出或失真。

2.2 核心逻辑:ColorMatrixManager 类

接下来我们实现 ColorMatrixManager 类,封装应用滤镜的核心操作:

typescript 复制代码
// colorMatrixManager.ets (续)
/**
 * 色彩矩阵应用管理器
 * 负责将滤镜矩阵应用到 PixelMap 上
 */
export class ColorMatrixManager {
  /**
   * 应用色彩矩阵到 PixelMap
   * @param pixelMap 原始 PixelMap
   * @param matrix 4x5 色彩矩阵(20个浮点数)
   * @returns 处理后的新 PixelMap
   */
  public static async applyColorMatrix(
    pixelMap: image.PixelMap,
    matrix: number[],
    context: Object
  ): Promise<image.PixelMap> {
    if (!pixelMap) {
      throw new Error('PixelMap 不能为空');
    }
    if (matrix.length !== 20) {
      throw new Error('矩阵必须包含 20 个浮点数');
    }

    // 获取原始图片信息
    const imageSize: image.Size = {
      height: pixelMap.size.height,
      width: pixelMap.size.width
    };
    const pixelMapInfo = await pixelMap.getImageInfo();

    // 调用 PixelMap 的 colorMatrix 属性设置色彩矩阵
    // 使用可变区域(mutable)的 PixelMap 进行修改
    pixelMap.colorMatrix(matrix);

    // 返回修改后的 PixelMap(已原地修改)
    return pixelMap;
  }

  /**
   * 应用滤镜并返回新的 PixelMap(不修改原图)
   * @param pixelMap 原始 PixelMap
   * @param filterType 滤镜类型
   * @returns 处理后的新 PixelMap
   */
  public static async applyFilter(
    pixelMap: image.PixelMap,
    filterType: FilterType
  ): Promise<image.PixelMap> {
    // 获取对应滤镜矩阵
    const matrix = FILTER_MATRICES[filterType];
    if (!matrix) {
      throw new Error(`不支持的滤镜类型: ${filterType}`);
    }

    // 将矩阵应用到 PixelMap
    pixelMap.colorMatrix(matrix);

    return pixelMap;
  }

  /**
   * 自定义色彩矩阵
   * @param pixelMap 原始 PixelMap
   * @param customMatrix 自定义的 20 个浮点数矩阵
   * @returns 处理后的 PixelMap
   */
  public static async applyCustomMatrix(
    pixelMap: image.PixelMap,
    customMatrix: number[]
  ): Promise<image.PixelMap> {
    if (customMatrix.length !== 20) {
      throw new Error('自定义矩阵必须包含 20 个浮点数');
    }

    pixelMap.colorMatrix(customMatrix);
    return pixelMap;
  }
}

关键点说明

  • pixelMap.colorMatrix(matrix)PixelMap 的内置方法,直接传入一个 20 个浮点数的数组即可应用色彩矩阵。
  • 该方法会原地修改 PixelMap 的像素数据,因此我们需要确保传入的 PixelMap 是可变的(mutable)。
  • 文档中明确要求矩阵数组长度为 20,否则会抛出异常。
  • 我们提供了 applyFilter 便捷方法,只需传入滤镜枚举即可一键应用。
  • 注意:colorMatrix 方法直接修改原始 PixelMap,若需保留原图,应在调用前先复制一份(通过 image.createPixelMap 创建副本)。

2.3 完整模块:FilterPage 页面

现在我们来实现完整的 FilterPage 页面,包含滤镜选择和预览功能:

typescript 复制代码
// FilterPage.ets
/**
 * 滤镜与色彩矩阵页面
 * 支持选择预定义滤镜和自定义色彩矩阵参数
 */
import { image } from '@kit.ImageKit';
import { ColorMatrixManager, FilterType, FILTER_MATRICES } from '../utils/colorMatrixManager';

@Entry
@Component
struct FilterPage {
  @State private currentFilter: FilterType = FilterType.NORMAL;
  @State private selectedFilterIndex: number = 0;
  @State private pixelMap: image.PixelMap | null = null;
  @State private originalPixelMap: image.PixelMap | null = null;
  @State private customMatrix: number[] = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0];

  // 滤镜列表(用于 UI 展示)
  private filterList: FilterType[] = Object.values(FilterType);

  aboutToAppear() {
    // 模拟接收从上一页面传递过来的 PixelMap
    // 实际项目中通过 AppStorage 或页面路由传递
    AppStorage.get<image.PixelMap>('currentPixelMap').then((pixelMap) => {
      if (pixelMap) {
        this.originalPixelMap = pixelMap;
        // 创建副本用于滤镜处理
        this.copyPixelMap(pixelMap);
      }
    });
  }

  /**
   * 复制 PixelMap 用于滤镜处理
   */
  private async copyPixelMap(source: image.PixelMap) {
    const imageInfo = await source.getImageInfo();
    const pixelMap: image.PixelMap = await image.createPixelMap({
      width: imageInfo.size.width,
      height: imageInfo.size.height,
      pixelFormat: image.PixelMapFormats.RGBA_8888,
      alphaType: image.AlphaType.PREMUL,
      scaleMode: image.ScaleMode.FIT_TARGET_SIZE
    });
    // 复制像素数据
    const srcArrayBuffer = source.readPixelsToBuffer();
    await pixelMap.writeBufferToPixels(srcArrayBuffer);
    this.pixelMap = pixelMap;
  }

  /**
   * 应用滤镜
   */
  private async applyFilter(filterType: FilterType) {
    if (!this.pixelMap || !this.originalPixelMap) {
      console.error('PixelMap 未就绪');
      return;
    }

    try {
      // 重置为原图
      await this.copyPixelMap(this.originalPixelMap);
      // 应用滤镜
      await ColorMatrixManager.applyFilter(this.pixelMap!, filterType);
      this.currentFilter = filterType;
    } catch (error) {
      console.error('应用滤镜失败:', JSON.stringify(error));
    }
  }

  /**
   * 应用自定义矩阵
   */
  private async applyCustomMatrix() {
    if (!this.pixelMap || !this.originalPixelMap) {
      return;
    }

    try {
      await this.copyPixelMap(this.originalPixelMap);
      await ColorMatrixManager.applyCustomMatrix(this.pixelMap!, this.customMatrix);
      this.currentFilter = FilterType.NORMAL;
    } catch (error) {
      console.error('应用自定义矩阵失败:', error);
    }
  }

  build() {
    Column() {
      // 图片预览区域
      if (this.pixelMap) {
        Image(this.pixelMap)
          .width('100%')
          .height(300)
          .objectFit(ImageFit.Contain)
          .margin({ top: 10 })
      } else {
        Text('请先选择图片')
          .width('100%')
          .height(300)
          .textAlign(TextAlign.Center)
          .backgroundColor('#f0f0f0')
      }

      // 当前滤镜名称显示
      Text(`当前滤镜: ${this.currentFilter}`)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 10 })

      // 预定义滤镜列表
      Scroll() {
        Column() {
          ForEach(this.filterList, (filter: FilterType) => {
            Button(filter)
              .width('90%')
              .height(48)
              .margin({ top: 6 })
              .backgroundColor(filter === this.currentFilter ? '#007DFF' : '#E0E0E0')
              .fontColor(filter === this.currentFilter ? '#FFFFFF' : '#333333')
              .onClick(() => {
                this.applyFilter(filter);
              })
          })
        }
        .width('100%')
        .padding({ left: 16, right: 16 })
      }
      .height(150)

      // 自定义矩阵参数滑块(简化示例)
      Text('自定义颜色调整')
        .fontSize(14)
        .margin({ top: 10 })

      Row() {
        Text('红色:')
        Slider({
          value: this.customMatrix[0],
          min: 0.0,
          max: 2.0,
          step: 0.1
        })
          .onChange((value: number) => {
            this.customMatrix[0] = value;
            this.applyCustomMatrix();
          })
      }
      .padding({ left: 16, right: 16 })

      Row() {
        Text('绿色:')
        Slider({
          value: this.customMatrix[6],
          min: 0.0,
          max: 2.0,
          step: 0.1
        })
          .onChange((value: number) => {
            this.customMatrix[6] = value;
            this.applyCustomMatrix();
          })
      }
      .padding({ left: 16, right: 16 })

      Row() {
        Text('蓝色:')
        Slider({
          value: this.customMatrix[12],
          min: 0.0,
          max: 2.0,
          step: 0.1
        })
          .onChange((value: number) => {
            this.customMatrix[12] = value;
            this.applyCustomMatrix();
          })
      }
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

关键点说明

  • 页面初始化时从 AppStorage 获取上一页面传递的 PixelMap,并创建副本用于滤镜处理,避免破坏原始图片。
  • copyPixelMap 方法通过 image.createPixelMapwriteBufferToPixels 复制像素数据。
  • 滤镜列表使用 ForEach 渲染,选中按钮高亮显示。
  • 自定义矩阵滑块允许用户调整 RGB 三通道的权重,实时应用并预览效果。
  • colorMatrix 方法直接作用于当前 PixelMap,因此每次应用滤镜前需要恢复为原图。

2.4 PixelMapUtils 工具类

为了便于资源管理,我们创建一个 PixelMapUtils 类来封装 PixelMap 的复制和释放操作:

typescript 复制代码
// pixelMapUtils.ets
/**
 * PixelMap 工具类
 * 提供 PixelMap 复制、释放等通用操作
 */
import { image } from '@kit.ImageKit';

export class PixelMapUtils {
  /**
   * 复制 PixelMap
   * @param source 源 PixelMap
   * @returns 复制的 PixelMap
   */
  public static async copyPixelMap(source: image.PixelMap): Promise<image.PixelMap> {
    const imageInfo = await source.getImageInfo();
    const width = imageInfo.size.width;
    const height = imageInfo.size.height;

    // 创建新的 PixelMap
    const pixelMap: image.PixelMap = await image.createPixelMap({
      width: width,
      height: height,
      pixelFormat: image.PixelMapFormats.RGBA_8888,
      alphaType: image.AlphaType.PREMUL,
      scaleMode: image.ScaleMode.FIT_TARGET_SIZE
    });

    // 读取源像素并写入新 PixelMap
    const buffer = source.readPixelsToBuffer();
    await pixelMap.writeBufferToPixels(buffer);

    return pixelMap;
  }

  /**
   * 释放 PixelMap 资源
   * @param pixelMap 要释放的 PixelMap
   */
  public static releasePixelMap(pixelMap: image.PixelMap | null) {
    if (pixelMap) {
      try {
        pixelMap.release();
      } catch (error) {
        console.error('释放 PixelMap 失败:', JSON.stringify(error));
      }
    }
  }
}

关键点说明

  • copyPixelMap 方法先创建与原图尺寸相同的 RGBA_8888 格式 PixelMap,再通过 readPixelsToBufferwriteBufferToPixels 复制像素数据。
  • releasePixelMap 用于及时释放不再需要的 PixelMap 资源,避免内存泄漏。
  • 该工具类后续在页面切换或批量处理中非常有用。

3. 运行验证

完成上述代码后,将 FilterPage 添加到项目主页面中,例如通过 Tab 切换或页面路由跳转。运行效果如下:

  1. APP 启动后选择一张图片,进入滤镜模块。
  2. 显示原始图片,下方列出所有预定义滤镜按钮。
  3. 点击「灰度」按钮,图片变为黑白效果。
  4. 点击「怀旧」按钮,图片呈现泛黄的老照片风格。
  5. 通过底部自定义滑块调整红色/绿色/蓝色权重,图片颜色实时变化。

4. 小结与预告

本篇我们成功实现了滤镜与色彩矩阵模块,核心成果包括:

  • ColorMatrixManager 类封装了 7 种预定义滤镜矩阵和自定义矩阵应用方法。
  • FilterPage 页面提供了完整的滤镜选择和实时预览功能,支持一键切换。
  • PixelMapUtils 工具类简化了 PixelMap 的复制和资源管理。

这些功能使 APP 的图像处理能力从基本的几何变换跃升至色彩风格调整,大幅提升了用户视觉体验和实用性。下篇我们将继续深入色彩调整领域,聚焦亮度、对比度、饱和度的精细调节,通过像素点操作和色彩矩阵组合实现更专业的图像优化。

相关推荐
拥抱太阳06162 小时前
HarmonyOS 应用开发《掌上英语》第9篇—ArkTS @ObservedV2 + @Trace 响应式编程精讲
华为·harmonyos
拥抱太阳06162 小时前
HarmonyOS 应用开发《掌上英语》第10篇——@Local vs @Param vs @Trace 组件状态管理三剑客
华为·harmonyos
CCC:CarCrazeCurator4 小时前
A100作为深度学习基础计算节点的原因
人工智能·深度学习
hhzz4 小时前
Barbershop:基于GAN和分割Mask的图像合成技术——从理论到实战全解析
图像处理·人工智能·python·深度学习·计算机视觉
春卷同学8 小时前
HarmonyOS掌上记账APP开发实践第8篇:构建可测试的鸿蒙应用 — MoneyTrack 的自动化测试体系
华为·harmonyos
梦想不只是梦与想9 小时前
鸿蒙中declare关键字
harmonyos·declare
数智工坊10 小时前
RealNet:用于异常检测的特征选择网络,具备真实的合成异常样本
人工智能·深度学习
FrameNotWork10 小时前
HarmonyOS 6.0 输入法与键盘避让 — 聊天页面底部输入栏被键盘挡住的绝望
华为·计算机外设·harmonyos