ArkGraphics2D effectKit 实战:离线图像效果链与智能取色,怎么用得“像工程”而不是“像 Demo”
一、先把“用在什么场景”想清楚:离线处理和实时效果不是一类问题
在鸿蒙图形体系里,很多人一上来就想“给组件加个模糊/灰度/反色”,然后把效果堆在 UI 上跑,结果就是:预览挺美,上真机就开始掉帧、发热、偶现卡顿。
我更建议先把需求分成两类:
-
离线图像处理(effectKit):针对一张
PixelMap/png/jpeg做一次性加工,比如生成封面缩略图、做头像虚化背景、导出分享图、预生成列表卡片背景图等。处理完得到新的PixelMap,后面展示就很轻松。 -
实时动态效果(uiEffect 或组件级实时模糊):效果要跟着屏幕帧更新,比如滚动时背景毛玻璃、弹窗背后实时虚化。这类更考验渲染链路与设备性能,需要更谨慎的策略(半径、区域、触发时机、降级等)。
一句话:**“生成一张图”用离线,“跟着帧跑”才考虑实时。**离线是你能把性能握在手里的那部分。
二、Filter 链怎么理解:createEffect 不是“开关”,是“管线头节点”
effectKit.createEffect(source: PixelMap) 返回的不是某个具体效果,而是一个 Filter 链表的头节点。你可以把它理解成:我拿到一个“可串联的处理管线”,后面调用 blur / grayscale / invert / brightness / setColorMatrix 这些方法,相当于往管线后面挂处理节点。
这个模型有两个很实用的工程价值:
-
可组合:你可以把“统一风格”封成方法,例如“封面图 = 轻微提亮 + 小半径模糊 + 灰度”。
-
可控输出时机:你什么时候
getEffectPixelMap(),什么时候真正计算;在此之前你只是在描述处理链路。
建议你把“构建链路”和“执行输出”拆开写,代码会更稳定、更好测。
三、getEffectPixelMap 的选择:把 CPU/GPU 当成两条不同的成本曲线
离线处理最容易踩坑的是:处理逻辑写对了,但 卡在执行那一下。因为真正耗时的是 getEffectPixelMap(...),尤其是:
-
图片分辨率大(例如 1440p 原图)
-
模糊半径大
-
你在列表里对很多张图同时处理
工程上我会按这个思路选:
-
小图/少量:CPU 渲染也够用,代码简单、兼容性稳。
-
大图/批量:优先考虑能否走 GPU 渲染(如果当前 API 版本支持并且设备表现稳定),同时做并发限制和降级策略。
你不需要一开始就追“最快”,先做到三件事就能立刻变顺滑:
-
先缩放再处理(很多视觉场景,512~768 的边长就够了)
-
限制并发(一次处理 1~2 张,别在列表滚动时全量开工)
-
结果缓存(同一张图同一套参数不要重复算)
四、ColorPicker 不是“取个颜色”,它可以直接变成你的 UI 主题发动机
很多同学只用 getMainColor() 做个“主色”,其实 ColorPicker 的价值是:它给了你几种不同的“代表色定义”,适配不同产品设计目标:
-
主色(getMainColor / Sync):更像“整体观感”,适合做页面背景基调、沉浸式标题栏色。
-
占比最多色(getLargestProportionColor):更像“画面主体”,适合做标签色、按钮强调色(尤其是人物/物体主体明显的图)。
-
占比 TopN(getTopProportionColors):适合做渐变、配色盘、骨架屏占位色策略。
-
最高饱和度色(getHighestSaturationColor):适合做点睛色,例如小红点、角标、强调描边。
-
平均色(getAverageColor):更像“背景融合色”,适合做卡片阴影/蒙层颜色。
还有一个特别实用的判断:isBlackOrWhiteOrGrayColor(...)。真实业务里你会遇到一堆“黑白灰封面图”,这时你直接用主色去驱动 UI,页面会显得“灰一片”。做法是:
如果是黑白灰 → 走默认主题配色;否则 → 用取色结果做动态主题。
这一下你的产品质感会明显上去。
五、region 取色区域:别把它当“裁剪”,它是你稳定性的保险丝
createColorPicker(pixelMap, region) 里这个 region 是归一化坐标 [left, top, right, bottom],取值 [0,1]。工程上它的意义是:
你可以避开“图片边缘的噪声”,例如:
-
头像:避开圆角透明边缘
-
海报:避开上下黑边/字幕区
-
截图:避开状态栏/导航栏区域
我常用的经验参数是取“中间偏上”区域(比如顶部标题区域不参与),这样主色更贴近主体内容,且跨图风格更稳定。
同时要记得做参数校验:right > left、bottom > top,并把范围 clamp 到 [0,1]。很多 401 参数错误,本质都是 region 传错或数组长度不对。
六、一个可复用的离线处理管线:模糊背景 + 动态主题色(小而完整)
下面这段思路是“工程版写法”:输入一张图,输出两样东西:
1)处理后的 PixelMap(比如模糊后的背景图)
2)一个推荐主题色(用于标题栏/按钮/渐变)
import { image } from "@kit.ImageKit";
import { effectKit } from "@kit.ArkGraphics2D";
type ThemeResult = {
blurred: image.PixelMap;
themeColor: effectKit.Color | null;
};
async function buildThemeFromBuffer(buffer: ArrayBuffer): Promise<ThemeResult> {
// 1) 解码成 PixelMap(建议:这里就做缩放到目标尺寸)
const imageSource = image.createImageSource(buffer);
const pixelMap = await imageSource.createPixelMap();
// 2) 生成模糊图(离线效果链)
const radius = 20; // 半径别一上来就很大,先从 12~24 找感觉
const head = effectKit.createEffect(pixelMap);
if (!head) {
return { blurred: pixelMap, themeColor: null };
}
head.blur(radius).brightness(0.1); // 轻微提亮,很多封面会更耐看
const blurred = await head.getEffectPixelMap(false); // 视 API/设备能力选择 CPU/GPU
// 3) 取色(建议限定区域,减少边缘噪声)
const picker = await effectKit.createColorPicker(pixelMap, [0.1, 0.1, 0.9, 0.8]);
const main = await picker.getMainColor();
// 4) 黑白灰兜底:避免页面“灰一片”
const argb = ((main.alpha & 0xff) << 24) | ((main.red & 0xff) << 16) | ((main.green & 0xff) << 8) | (main.blue & 0xff);
const isMono = picker.isBlackOrWhiteOrGrayColor(argb);
return { blurred, themeColor: isMono ? null : main };
}
你把它封成一个工具模块,配合缓存(key = 图片 id + 半径 + region),就能在列表/详情页稳定复用:
-
列表:只展示缓存结果,滚动不做重计算
-
详情:进入页后再异步生成更高质量版本,逐步替换
七、常见坑(真的很常见)
-
把离线处理放在 UI 关键路径:比如
aboutToAppear里 await 一堆处理,页面首帧就会抖。做法是:先渲染,再后台生成,生成完替换。 -
模糊半径越大越“高级”:视觉上确实更糊,但耗时往往呈非线性上升。先用小半径把设计感做出来,再考虑加大。
-
对原图直接处理:先缩放再处理,收益通常巨大。
-
重复计算:同图同参数反复跑是最亏的,缓存一定要上。
-
用废弃接口:能用
getEffectPixelMap就不要再走旧接口路径,减少兼容分支。
更多推荐


所有评论(0)