鸿蒙开发-想给照片加滤镜?effectKit的滤镜链怎么用
本文介绍了如何使用HarmonyOS的effectKit模块快速实现图片滤镜效果。通过简单的三步操作:创建滤镜、添加效果、导出图片,开发者可以轻松实现模糊、亮度调节、灰度转换和颜色反转等常见滤镜功能。文章详细讲解了effectKit的核心组件Filter的工作原理和使用方法,包括如何叠加多个滤镜效果,以及通过颜色矩阵实现高级自定义效果。最后展示了如何将处理后的图片导出为PixelMap格式,并强调
想给照片加滤镜?用 effectKit 三行代码搞定
你有没有想过,手机相册里那些"复古"、“黑白”、"模糊"滤镜,底层到底是怎么实现的?
其实原理很简单:拿到图片的每一个像素,按照某种数学规则重新算一遍颜色值,就完事了。比如"灰度滤镜"就是把 RGB 三个通道的值加权平均,变成一个灰度值;"模糊滤镜"就是把每个像素和它周围的像素混合一下。
听起来好像要写很多代码?在 HarmonyOS 里,不用。effectKit 这个模块把这些图像效果都封装好了,你只需要:创建滤镜 → 添加效果 → 导出图片,三步。
今天我们就来做一个照片编辑 APP 的滤镜功能,看看怎么用 effectKit 给图片加模糊、调亮度、变灰度、做反转。
先认识一下 effectKit
effectKit 属于 ArkGraphics2D 这个 Kit,专门用来做离线图像处理。什么叫离线?就是你有一张图片(比如用户从相册选的),你想给它加个效果,处理完再展示出来。它不是实时渲染的那种——如果你要给屏幕上的组件加实时模糊,那是 uiEffect 干的事,不一样。
effectKit 提供了三个核心角色:
- Filter:效果类,你可以往上面"挂"各种效果,比如模糊、灰度、亮度,它会把这些效果串成一条链,最后一次性应用到图片上。
- ColorPicker:智能取色器,能从一张图片里提取主色调。这个我们下一篇再聊。
- Color:颜色类,就是 RGBA 四个值,用来保存取色结果。
今天我们重点讲 Filter。
下面是 effectKit 滤镜处理的整体流程:
第一步:导入模块
import { effectKit } from "@kit.ArkGraphics2D";
一行搞定。不过后面我们还需要 ImageKit 来创建 PixelMap,以及 AbilityKit 来读取图片文件,所以一般会这样写:
import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
import { common } from '@kit.AbilityKit';
第二步:拿到一张图片的 PixelMap
在 HarmonyOS 里,图片不是直接用文件路径的,而是要用 PixelMap——你可以把它理解成"图片在内存里的表示"。不管用户给你的是一张 PNG、JPEG,还是一个 ArrayBuffer(比如从网络下载的),你都得先把它转成 PixelMap。
怎么转?用 image.createImageSource 创建一个图片源,再用 createPixelMap() 解码:
// 假设你已经有了图片的 ArrayBuffer 数据
let imageSource = image.createImageSource(Image);
let pixelMap = await imageSource.createPixelMap();
如果你的图片放在项目的 rawfile 文件夹里(比如 rawfile/image.png),读取方式是这样的:
async getFileBuffer(): Promise<ArrayBuffer | undefined> {
try {
const context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const fileData: Uint8Array = await context.resourceManager.getRawFileContent('image.png');
const buffer: ArrayBuffer = fileData.buffer.slice(0);
return buffer;
} catch (err) {
return undefined;
}
}
这里 getRawFileContent 返回的是 Uint8Array,我们要的是 ArrayBuffer,所以用 .buffer.slice(0) 转一下。为什么要 slice(0)?因为 Uint8Array.buffer 可能包含多余的数据(偏移量),slice(0) 会拷贝一份干净的 ArrayBuffer 出来。
第三步:创建 Filter 实例
有了 PixelMap,就可以创建 Filter 了:
let headFilter = effectKit.createEffect(pixelMap);
这行代码做了什么?它传入一张图片,返回一个 Filter 对象。这个 Filter 是链表的头节点——你可以把它想象成一根绳子的头,后面你可以往绳子上一个一个挂效果。
为什么要用链表?因为滤镜是可以叠加的。你可以先加模糊,再加灰度,再加反转,这些效果会按顺序依次应用。链表结构天然支持这种"串起来"的操作。
第四步:添加效果
模糊效果
headFilter.blur(5);
blur 方法接收一个 radius 参数,就是模糊半径,单位是像素。值越大,模糊效果越明显。打个比方,radius=5 就像隔着一层薄纱看照片,radius=50 就像隔着一层毛玻璃。
从 API version 14 开始,blur 还多了一个重载,可以指定平铺模式:
headFilter.blur(30, effectKit.TileMode.DECAL);
TileMode 控制的是图片边缘的模糊效果怎么处理。目前只支持 DECAL,意思是"只在图片原始边界内渲染",不会让边缘的模糊溢出到外面去。
亮度调节
headFilter.brightness(0.5);
bright 参数取值范围是 [0, 1]。0 表示不改变,1 表示达到预设的最大亮度。你可能会问:为什么不是 0-100 或者 0-255?因为这里用的是归一化值,方便和其他效果统一处理。
如果你觉得图片太暗了,传个 0.5-0.8 的值;如果想做"曝光过度"的特效,直接传 1。
灰度效果
headFilter.grayscale();
灰度效果不需要参数,它会把彩色图片变成黑白的。原理是把 RGB 三个通道的值按照人眼感知的权重(大约是 R:0.2126, G:0.7152, B:0.0722)加权平均。你会发现绿色通道的权重最大,因为人眼对绿色最敏感。
颜色反转
headFilter.invert();
也不需要参数。它会把每个像素的颜色值反转——黑色变白色,红色变青色,就像照片的"底片"效果。实现方式就是用 255 减去原来的值。
自定义颜色矩阵(高级玩法)
如果你觉得上面那些效果太"固定"了,想自己定义颜色变换规则,可以用 setColorMatrix:
let colorMatrix: Array<number> = [
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0
];
headFilter.setColorMatrix(colorMatrix);
这是一个 5x4 的矩阵。什么意思呢?它定义了"输出颜色 = 输入颜色 × 矩阵"的计算规则。
矩阵的每一行对应一个输出通道:第一行是输出的 R,第二行是 G,第三行是 B,第四行是 A。每一列对应一个输入通道:前四列分别是输入的 R、G、B、A,第五列是常量偏移。
上面这个矩阵其实就是灰度效果的数学表达——每一行都是同样的权重 0.2126, 0.7152, 0.0722,意思是输出的 R、G、B 都等于输入的灰度值。
你可以用这个矩阵做出各种神奇的效果。比如想让图片偏暖色调(增强红色),可以调大第一行的第一个值;想做旧照片效果,可以调整矩阵让整体偏黄。
矩阵元素取值范围是 [0, 1],0 表示该颜色通道不参与计算,1 表示保持原始权重。
第五步:效果叠加
滤镜链的叠加处理是按顺序依次执行的,下面是效果链的处理模型:
前面说过,Filter 是链表结构,所以你可以连续调用多个效果方法,它们会串在一起:
let headFilter = effectKit.createEffect(pixelMap);
if (headFilter != null) {
headFilter.brightness(0.3); // 先调亮
headFilter.blur(10); // 再模糊
headFilter.grayscale(); // 最后变灰度
}
效果的顺序很重要。先调亮再模糊,和先模糊再调亮,结果是不一样的。你可以把它想象成 PS 里的图层——从上往下依次执行。
第六步:导出处理后的图片
效果加完了,怎么拿到处理后的图片?用 getEffectPixelMap:
headFilter.getEffectPixelMap().then(imageData => {
// imageData 就是处理后的 PixelMap
// 你可以把它赋值给 Image 组件显示出来
})
这个方法会按照你添加的效果顺序,依次对图片进行处理,最后返回一个新的 PixelMap。注意,原图不会被修改,它返回的是一张新的图片。
从 API version 20 开始,getEffectPixelMap 还支持指定渲染模式:
headFilter.getEffectPixelMap(false); // false = GPU渲染
默认是 CPU 渲染(true),GPU 渲染在处理大图片时会更快。但目前 GPU 渲染还在实验阶段,生产环境建议还是用 CPU。
把它们组合起来:完整的滤镜页面
好,现在我们把所有步骤串起来,做一个完整的 ArkUI 页面。用户打开页面,看到一张加了模糊效果的图片:
import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
import { common } from '@kit.AbilityKit';
// 图片处理函数:传入图片数据,返回加了模糊效果的 PixelMap
function ImageBlur(Image: ArrayBuffer): Promise<image.PixelMap> {
return new Promise(async (resolve, reject) => {
let imageSource = image.createImageSource(Image);
await imageSource.createPixelMap().then(async (pixelMap: image.PixelMap) => {
let radius = 5;
let headFilter = effectKit.createEffect(pixelMap);
if (headFilter != null) {
// 对图片添加模糊效果
headFilter.blur(radius);
}
// 按照添加的效果标识对图片进行处理并且返回处理好的图片数据
headFilter.getEffectPixelMap().then(imageData => {
resolve(imageData);
})
})
})
}
@Entry
@Component
struct Index {
@State imagePixelMap: image.PixelMap | null = null;
private imageBuffer: ArrayBuffer | undefined = undefined;
// 读取 rawfile 文件夹下的图片文件
async getFileBuffer(): Promise<ArrayBuffer | undefined> {
try {
const context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const fileData: Uint8Array = await context.resourceManager.getRawFileContent('image.png');
const buffer: ArrayBuffer = fileData.buffer.slice(0);
return buffer;
} catch (err) {
return undefined;
}
}
async aboutToAppear(): Promise<void> {
this.imageBuffer = await this.getFileBuffer();
if (this.imageBuffer == undefined) {
return;
}
// 图片处理为异步操作,用 await 等待处理完成
this.imagePixelMap = await ImageBlur(this.imageBuffer);
}
build() {
Column() {
Image(this.imagePixelMap)
.width(304)
.height(305)
}
.height('100%')
.width('100%')
}
}
我们来拆解一下这段代码:
-
ImageBlur函数:接收图片的 ArrayBuffer,创建 PixelMap,加模糊效果,返回处理后的 PixelMap。整个过程是异步的,所以用Promise包起来。 -
getFileBuffer方法:从项目的rawfile文件夹读取图片。resourceManager.getRawFileContent返回的是Uint8Array,我们用.buffer.slice(0)转成ArrayBuffer。 -
aboutToAppear生命周期:页面创建时,先读取图片,再处理图片。因为图片处理是异步的,所以用await等它完成,然后把结果赋给imagePixelMap。 -
build方法:用Image组件显示处理后的图片。当imagePixelMap变化时,UI 会自动刷新。
如果你把 blur 换成 brightness(0.5),就是调亮效果;换成 grayscale(),就是黑白效果;换成 invert(),就是底片效果。你可以自己试试不同的组合。
几个容易踩的坑
1. headFilter 可能是 null
effectKit.createEffect(pixelMap) 在失败时会返回 null。所以一定要先判断 if (headFilter != null) 再调用后续方法,不然会崩。
2. 效果是"标记"而不是"执行"
调用 blur(5) 并不会立刻处理图片,它只是在链表上标记了"这里要模糊"。真正的处理发生在 getEffectPixelMap() 的时候。所以你可以放心地连续添加多个效果,不用担心性能问题。
3. 原图不会变
getEffectPixelMap() 返回的是新图片,原图的 PixelMap 不受影响。如果你想同时展示原图和效果图,直接用两个 Image 组件分别绑定就行。
4. 这是离线处理,不是实时滤镜
effectKit 适合"用户选了一张图 → 加效果 → 展示/保存"这种场景。如果你要做相机实时预览的滤镜,需要用别的方案(比如 uiEffect 或者自定义渲染管线)。
小结
effectKit 的 Filter 用起来就三步:
createEffect(pixelMap)— 创建滤镜头节点.blur()/.brightness()/.grayscale()/.invert()/.setColorMatrix()— 添加效果getEffectPixelMap()— 导出处理后的图片
效果可以叠加,顺序可以自定义,还能用颜色矩阵玩出各种花样。对于一个照片编辑 APP 来说,这些基础滤镜功能已经够用了。
下一篇我们聊聊 ColorPicker——怎么从一张照片里"智能"提取主色调,用来做主题色自动适配之类的功能。
更多推荐


所有评论(0)