跳到主要内容

HSV 色相饱和明度

HSV 与 HSL 是两种不同的色彩模型,Konva 各提供了一个滤镜。 它们的前两个参数同名同值,区别在第三个:HSV 是 value(明度), HSL 是 luminance(亮度)。

node.cache();
node.filters([Konva.Filters.HSV]);
node.hue(200); // 色相旋转角度,默认 0
node.saturation(1.5); // 饱和度增量,默认 0
node.value(0.3); // 明度增量,默认 0

什么时候该选 HSV 而不是 HSL​

选择的依据只有一个:你想让"变亮"这件事如何影响色彩浓度。

用 rgb(200,80,40)(一个偏橙的红)做对照,两边都加 0.3:

结果绿通道变化
HSL 的 luminance(0.3)238,118,7880 → 118
HSV 的 value(0.3)246,98,4980 → 98

HSL 的 luminance 本质是向白色靠拢——各通道一起往上抬, 通道之间的差距被压缩,颜色因此变淡、发灰。 上表里绿通道涨了 38,红绿差从 120 缩到 120,整体明显"粉"了。

HSV 的 value 是抬高明度上限——它更多地按比例缩放, 通道间的相对关系保留得更好,颜色依然浓。绿通道只涨了 18。

实践上的对应关系:

  • 要提亮但保持鲜艳(照片调色、让暗部看清)→ HSV 的 value
  • 要做褪色、朦胧、被雾笼罩的效果 → HSL 的 luminance
  • 要做禁用态的灰化 → HSL 的 luminance 配合负的 saturation, 比 HSV 更容易做出均匀的灰

不要和 HSL 同时启用​

hue 和 saturation 这两个属性由 HSL 和 HSV 共同注册,是同一个属性。 同时把两个滤镜放进 filters(),它们会各读一次同样的值、各做一次变换。

实测 rgb(200,80,40):HSL 单独 hue(120) 与 HSV 单独 hue(120) 结果完全相同(都是 102,83,255);两个一起挂、仍然只设 hue(120), 得到 40,159,25——接近单独旋转 240° 的效果。

完整的实测对照表见 HSL 页。

常见问题​

只想调明度,不想动色相,需要设 hue 吗?​

不需要。hue 默认是 0,不设就不旋转。

但要小心属性是挂在节点上的、会被记住。 如果这个节点之前用过 HSL 滤镜并设过 hue, 换成 HSV 之后那个 hue 值依然在,会继续生效。 换滤镜时如果行为不符预期,先 node.hue(0) 显式归零。

value 能设负数吗?​

能,负值让画面变暗。value(-0.5) 会明显压暗, 这是做"失焦""禁用""背景虚化"效果的常用手段。

比起用 Brightness 乘一个小于 1 的数, value 的好处是暗部不会一起被压死—— 乘法对已经很暗的像素影响是成比例的,容易让暗部细节全部糊成黑块。

HSV 和 HSL 哪个更快?​

两者都是逐像素、无邻域采样,成本几乎相同, 选哪个应该完全基于视觉效果而不是性能。

真正影响性能的是缓存面积和是否每帧重建缓存,见下面。

性能提示​

HSV 的计算量略高于 Brightness——每个像素要在 RGB 与 HSV 之间往返转换, 包含若干次比较和除法。但它仍属于便宜的逐像素滤镜, 在静态场景下几乎不需要考虑优化。

需要留意的是三种典型的浪费:

在动画里重复 cache()。 这是最常见也最致命的错误。 缓存一次,之后每帧只改 hue() / value() 再 layer.draw()。 每帧 cache() 意味着每帧都要分配一张离屏画布并重绘节点, 帧率会从 60 掉到个位数。

给整个图层加缓存。 layer.cache() 建的是与舞台等大的画布。 只有真正需要滤镜的那个节点才该缓存。

缓存分辨率过高。 默认 pixelRatio 是设备像素比, 2x 屏上面积是四倍,滤镜耗时也是四倍。 调色类滤镜对分辨率不敏感(它不改变形状,只改变颜色), cache({ pixelRatio: 1 }) 在多数场景下肉眼无差别, 却能省掉四分之三的计算。这是本章里性价比最高的一条优化。

如果只是要一个整体的色相偏移而不需要精细控制, CSS 滤镜的 hue-rotate() 走浏览器原生实现, 比任何 JS 逐像素循环都快。