
想给照片加滤镜用 effectKit 三行代码搞定你有没有想过手机相册里那些复古、“黑白”、模糊滤镜底层到底是怎么实现的其实原理很简单拿到图片的每一个像素按照某种数学规则重新算一遍颜色值就完事了。比如灰度滤镜就是把 RGB 三个通道的值加权平均变成一个灰度值模糊滤镜就是把每个像素和它周围的像素混合一下。听起来好像要写很多代码在 HarmonyOS 里不用。effectKit这个模块把这些图像效果都封装好了你只需要创建滤镜 → 添加效果 → 导出图片三步。今天我们就来做一个照片编辑 APP 的滤镜功能看看怎么用effectKit给图片加模糊、调亮度、变灰度、做反转。先认识一下 effectKiteffectKit属于ArkGraphics2D这个 Kit专门用来做离线图像处理。什么叫离线就是你有一张图片比如用户从相册选的你想给它加个效果处理完再展示出来。它不是实时渲染的那种——如果你要给屏幕上的组件加实时模糊那是uiEffect干的事不一样。effectKit提供了三个核心角色Filter效果类你可以往上面挂各种效果比如模糊、灰度、亮度它会把这些效果串成一条链最后一次性应用到图片上。ColorPicker智能取色器能从一张图片里提取主色调。这个我们下一篇再聊。Color颜色类就是 RGBA 四个值用来保存取色结果。今天我们重点讲 Filter。下面是 effectKit 滤镜处理的整体流程是否导入 effectKit 模块获取图片的 PixelMapcreateEffect 创建滤镜头节点添加滤镜效果需要叠加更多效果?getEffectPixelMap 导出处理后图片展示或保存结果第一步导入模块import{effectKit}fromkit.ArkGraphics2D;一行搞定。不过后面我们还需要ImageKit来创建 PixelMap以及AbilityKit来读取图片文件所以一般会这样写import{image}fromkit.ImageKit;import{effectKit}fromkit.ArkGraphics2D;import{common}fromkit.AbilityKit;第二步拿到一张图片的 PixelMap在 HarmonyOS 里图片不是直接用文件路径的而是要用PixelMap——你可以把它理解成图片在内存里的表示。不管用户给你的是一张 PNG、JPEG还是一个 ArrayBuffer比如从网络下载的你都得先把它转成 PixelMap。怎么转用image.createImageSource创建一个图片源再用createPixelMap()解码// 假设你已经有了图片的 ArrayBuffer 数据letimageSourceimage.createImageSource(Image);letpixelMapawaitimageSource.createPixelMap();如果你的图片放在项目的rawfile文件夹里比如rawfile/image.png读取方式是这样的asyncgetFileBuffer():PromiseArrayBuffer|undefined{try{constcontext:Contextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constfileData:Uint8Arrayawaitcontext.resourceManager.getRawFileContent(image.png);constbuffer:ArrayBufferfileData.buffer.slice(0);returnbuffer;}catch(err){returnundefined;}}这里getRawFileContent返回的是Uint8Array我们要的是ArrayBuffer所以用.buffer.slice(0)转一下。为什么要slice(0)因为Uint8Array.buffer可能包含多余的数据偏移量slice(0)会拷贝一份干净的 ArrayBuffer 出来。第三步创建 Filter 实例有了 PixelMap就可以创建 Filter 了letheadFiltereffectKit.createEffect(pixelMap);这行代码做了什么它传入一张图片返回一个 Filter 对象。这个 Filter 是链表的头节点——你可以把它想象成一根绳子的头后面你可以往绳子上一个一个挂效果。为什么要用链表因为滤镜是可以叠加的。你可以先加模糊再加灰度再加反转这些效果会按顺序依次应用。链表结构天然支持这种串起来的操作。第四步添加效果模糊效果headFilter.blur(5);blur方法接收一个radius参数就是模糊半径单位是像素。值越大模糊效果越明显。打个比方radius5 就像隔着一层薄纱看照片radius50 就像隔着一层毛玻璃。从 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 减去原来的值。自定义颜色矩阵高级玩法如果你觉得上面那些效果太固定了想自己定义颜色变换规则可以用setColorMatrixletcolorMatrix:Arraynumber[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 表示保持原始权重。第五步效果叠加滤镜链的叠加处理是按顺序依次执行的下面是效果链的处理模型原始图片 PixelMap第一个效果: brightness 调亮第二个效果: blur 模糊第三个效果: grayscale 灰度getEffectPixelMap 触发实际处理输出处理后的新 PixelMap原图不受影响前面说过Filter 是链表结构所以你可以连续调用多个效果方法它们会串在一起letheadFiltereffectKit.createEffect(pixelMap);if(headFilter!null){headFilter.brightness(0.3);// 先调亮headFilter.blur(10);// 再模糊headFilter.grayscale();// 最后变灰度}效果的顺序很重要。先调亮再模糊和先模糊再调亮结果是不一样的。你可以把它想象成 PS 里的图层——从上往下依次执行。第六步导出处理后的图片效果加完了怎么拿到处理后的图片用getEffectPixelMapheadFilter.getEffectPixelMap().then(imageData{// imageData 就是处理后的 PixelMap// 你可以把它赋值给 Image 组件显示出来})这个方法会按照你添加的效果顺序依次对图片进行处理最后返回一个新的 PixelMap。注意原图不会被修改它返回的是一张新的图片。从 API version 20 开始getEffectPixelMap还支持指定渲染模式headFilter.getEffectPixelMap(false);// false GPU渲染默认是 CPU 渲染trueGPU 渲染在处理大图片时会更快。但目前 GPU 渲染还在实验阶段生产环境建议还是用 CPU。把它们组合起来完整的滤镜页面好现在我们把所有步骤串起来做一个完整的 ArkUI 页面。用户打开页面看到一张加了模糊效果的图片import{image}fromkit.ImageKit;import{effectKit}fromkit.ArkGraphics2D;import{common}fromkit.AbilityKit;// 图片处理函数传入图片数据返回加了模糊效果的 PixelMapfunctionImageBlur(Image:ArrayBuffer):Promiseimage.PixelMap{returnnewPromise(async(resolve,reject){letimageSourceimage.createImageSource(Image);awaitimageSource.createPixelMap().then(async(pixelMap:image.PixelMap){letradius5;letheadFiltereffectKit.createEffect(pixelMap);if(headFilter!null){// 对图片添加模糊效果headFilter.blur(radius);}// 按照添加的效果标识对图片进行处理并且返回处理好的图片数据headFilter.getEffectPixelMap().then(imageData{resolve(imageData);})})})}EntryComponentstruct Index{StateimagePixelMap:image.PixelMap|nullnull;privateimageBuffer:ArrayBuffer|undefinedundefined;// 读取 rawfile 文件夹下的图片文件asyncgetFileBuffer():PromiseArrayBuffer|undefined{try{constcontext:Contextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constfileData:Uint8Arrayawaitcontext.resourceManager.getRawFileContent(image.png);constbuffer:ArrayBufferfileData.buffer.slice(0);returnbuffer;}catch(err){returnundefined;}}asyncaboutToAppear():Promisevoid{this.imageBufferawaitthis.getFileBuffer();if(this.imageBufferundefined){return;}// 图片处理为异步操作用 await 等待处理完成this.imagePixelMapawaitImageBlur(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 可能是 nulleffectKit.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——怎么从一张照片里智能提取主色调用来做主题色自动适配之类的功能。