一、先把“用在什么场景”想清楚:离线处理和实时效果不是一类问题

在鸿蒙图形体系里,很多人一上来就想“给组件加个模糊/灰度/反色”,然后把效果堆在 UI 上跑,结果就是:预览挺美,上真机就开始掉帧、发热、偶现卡顿。

我更建议先把需求分成两类:

  • 离线图像处理(effectKit):针对一张 PixelMap/png/jpeg 做一次性加工,比如生成封面缩略图、做头像虚化背景、导出分享图、预生成列表卡片背景图等。处理完得到新的 PixelMap,后面展示就很轻松。

  • 实时动态效果(uiEffect 或组件级实时模糊):效果要跟着屏幕帧更新,比如滚动时背景毛玻璃、弹窗背后实时虚化。这类更考验渲染链路与设备性能,需要更谨慎的策略(半径、区域、触发时机、降级等)。

一句话:**“生成一张图”用离线,“跟着帧跑”才考虑实时。**离线是你能把性能握在手里的那部分。

二、Filter 链怎么理解:createEffect 不是“开关”,是“管线头节点”

effectKit.createEffect(source: PixelMap) 返回的不是某个具体效果,而是一个 Filter 链表的头节点。你可以把它理解成:我拿到一个“可串联的处理管线”,后面调用 blur / grayscale / invert / brightness / setColorMatrix 这些方法,相当于往管线后面挂处理节点。

这个模型有两个很实用的工程价值:

  1. 可组合:你可以把“统一风格”封成方法,例如“封面图 = 轻微提亮 + 小半径模糊 + 灰度”。

  2. 可控输出时机:你什么时候 getEffectPixelMap(),什么时候真正计算;在此之前你只是在描述处理链路。

建议你把“构建链路”和“执行输出”拆开写,代码会更稳定、更好测。

三、getEffectPixelMap 的选择:把 CPU/GPU 当成两条不同的成本曲线

离线处理最容易踩坑的是:处理逻辑写对了,但 卡在执行那一下。因为真正耗时的是 getEffectPixelMap(...),尤其是:

  • 图片分辨率大(例如 1440p 原图)

  • 模糊半径大

  • 你在列表里对很多张图同时处理

工程上我会按这个思路选:

  • 小图/少量:CPU 渲染也够用,代码简单、兼容性稳。

  • 大图/批量:优先考虑能否走 GPU 渲染(如果当前 API 版本支持并且设备表现稳定),同时做并发限制和降级策略。

你不需要一开始就追“最快”,先做到三件事就能立刻变顺滑:

  1. 先缩放再处理(很多视觉场景,512~768 的边长就够了)

  2. 限制并发(一次处理 1~2 张,别在列表滚动时全量开工)

  3. 结果缓存(同一张图同一套参数不要重复算)

四、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 > leftbottom > 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 就不要再走旧接口路径,减少兼容分支。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐