跳到主要内容

RGB 单色调

RGB 这个名字容易让人以为它是"分别调整红绿蓝三个通道"。 它实际做的是单色调重染:先算出每个像素的亮度, 再用这个亮度去调制你指定的那一种颜色。

结果是一张只有一种色相、靠明暗表现层次的图—— 类似黑白照片染上一层颜色。

node.cache();
node.filters([Konva.Filters.RGB]);
node.red(255);
node.green(120);
node.blue(0); // 三个值都是 0 ~ 255,默认 0

算法​

const brightness = (0.34 * r + 0.5 * g + 0.16 * b) / 255;
data[i] = brightness * red;
data[i + 1] = brightness * green;
data[i + 2] = brightness * blue;

有两点值得注意。

原像素的颜色信息被完全丢弃,只有亮度被保留。 所以红花绿叶经过 RGB 之后会变成同一种色相的深浅两块—— 这是不可逆的,没法再调回来。

亮度权重是 0.34 / 0.5 / 0.16,不是常见的 sRGB 亮度系数 (0.2126 / 0.7152 / 0.0722)也不是 Rec.601(0.299 / 0.587 / 0.114)。 这组权重给红色的比重偏高、绿色偏低, 所以偏红的区域在结果里会比用标准公式时显得更亮一些。 要精确匹配其他工具的灰度换算,得自己写 自定义滤镜。

和 RGBA 的区别​

两个滤镜共用 red / green / blue 三个属性 (访问器由 RGB 注册,RGBA 直接复用), 但算法完全不同,不是"RGBA 等于 RGB 加个透明度"。

做的事原图颜色
RGB按亮度重染成单色调完全丢弃
RGBA把指定颜色按 alpha 线性混合到原图上按比例保留

RGBA 的公式是 结果 = 目标色 × alpha + 原色 × (1 - alpha), 也就是一层半透明的纯色蒙版。alpha 为 0 时完全不变,为 1 时变成纯色块。

所以:要保留原图色彩、只加一层色调用 RGBA; 要做单色化效果(老照片、图标着色、主题配色)用 RGB。

常见问题​

怎么做出蓝调的黑白效果?​

把三个分量设成同一个色相的比例即可:

node.red(100);
node.green(140);
node.blue(200); // 偏蓝的冷色调

想要纯黑白就三个都设成 255—— 不过那样不如直接用 Grayscale, 它更快,也用的是标准的亮度公式。

为什么设了颜色图片变得很暗?​

因为 brightness 是 0 到 1 之间的系数, 最终的通道值是 brightness × 你设的值。 如果原图整体偏暗(brightness 平均只有 0.3), 即使你把 red 设成 255,输出的红通道也只有 76。

想让结果更亮,先用 Brightness 提亮再上色, 注意滤镜数组里的顺序——靠前的先执行。

三个分量都不设会怎样?​

默认值都是 0,输出是全黑。

这和 Brightness 的情况不同—— 那个滤镜对未设置的属性做了特殊处理(当作 1), RGB 没有这层保护。挂了 RGB 却不设颜色,画面直接变黑。

性能提示​

RGB 是逐像素滤镜,每像素三次乘法加一次加权求和,成本与 Brightness 相当,属于最便宜的一档。

值得注意的是一个用它替代其他方案的机会: 如果你的需求只是"把一批单色图标染成主题色", 那么与其给每个图标节点都 cache() 再加 RGB 滤镜, 不如直接在绘制时设 fill—— 对 Konva.Path 这类矢量图形,改 fill 不需要任何缓存, 成本几乎为零,而滤镜方案要为每个图标建一张离屏画布。

只有当源素材是位图(PNG / JPEG)、没有矢量版本时, RGB 才是合理的选择。这种情况下:

  • 图标通常很小,缓存面积小,滤镜开销可以忽略。
  • 但数量可能很多。一百个图标就是一百张离屏画布, 内存占用比预期高。可以考虑把同色调的图标合并到一个 Group 里整体缓存加滤镜, 而不是逐个处理。
  • 颜色不变时不要重复 cache()。缓存一次,之后只在换主题时改属性并重绘。