跳到主要内容

Contrast 对比度

对比度滤镜以中灰(128)为轴,把每个通道向两端推开或向中间压缩。

node.cache();
node.filters([Konva.Filters.Contrast]);
node.contrast(50); // 取值 −100 ~ 100,默认 0

换算方式与边界值​

Konva 内部先把 contrast 换算成一个乘数:

const adjust = Math.pow((contrast + 100) / 100, 2);

然后对每个通道做「归一化 → 减 0.5 → 乘 adjust → 加 0.5 → 还原」。

这个公式有两个值得注意的端点:

contrast(0) → adjust = 1,画面不变。这是默认值。

contrast(-100) → adjust = 0。每个通道都被压成 0.5, 也就是 rgb(128,128,128) 纯灰——整张图变成一块均匀的灰色, 而不是很多人预期的"对比度很低但还能看出内容"。

这是一个容易写出 bug 的地方:如果你把滑块范围直接设成 −100 到 100, 用户拖到最左边会得到一片灰,看起来像程序崩了。 实践中把下限设在 −90 左右更合理。

contrast(100) → adjust = 4,对比度大幅提高, 多数像素被推到 0 或 255,图像严重过曝/死黑。

常见问题​

为什么是平方而不是线性?​

因为人对对比度变化的感知不是线性的。平方让滑块在中间区段的变化更细腻, 两端更激进,手感上比线性映射自然。

副作用是负值区间的变化远比正值区间剧烈: 从 0 到 −50 时 adjust 从 1 降到 0.25,画面迅速发灰; 从 0 到 +50 时 adjust 只从 1 升到 2.25。 如果你要做一个对称的滑块,视觉上会觉得往左拖"更有效"。

对比度和亮度该用哪个?​

看你要解决什么问题:

  • 整体偏暗或偏亮 → Brightness,它是整体缩放。
  • 灰蒙蒙、层次不分明 → Contrast,它拉开明暗差距。
  • 褪色、颜色不鲜艳 → HSV 的 saturation。
  • 想把褪色的图恢复到满量程 → Enhance, 它按实际的通道范围自动拉伸,不需要手调。

能同时用对比度和亮度吗?​

可以,把两个滤镜都放进 filters() 数组:

node.cache();
node.filters([Konva.Filters.Brightness, Konva.Filters.Contrast]);
node.brightness(1.2);
node.contrast(30);

顺序有影响——数组里靠前的先执行,后一个作用在前一个的结果上。 先提亮再加对比,和先加对比再提亮,出来的画面不一样。 详见滤镜叠加。

性能提示​

Contrast 每像素要做三次除法、三次乘法、三次加法和六次比较, 比 Brightness 贵一些,但仍属于逐像素、无邻域采样的廉价滤镜, 成本随缓存面积线性增长。

真正的开销在 cache()。几条实践:

别为了调对比度而缓存整个图层。 layer.cache() 会建一张与舞台等大的 离屏画布,在 4K 屏上这是几千万像素。只缓存真正需要滤镜的那个节点。

缓存的 pixelRatio 直接决定滤镜成本。 默认值是设备像素比, 在 2x 屏上缓存面积是节点面积的四倍。如果这个节点不会被放大显示, cache({ pixelRatio: 1 }) 能省下四分之三的滤镜耗时, 视觉上在 2x 屏看不出差别的场景比想象中多。

拖滑块调参时不要重复 cache()。 缓存一次,之后只改 contrast() 再 layer.draw()。Konva 会在现有缓存上重跑滤镜链,不会重建缓存画布。 反过来,如果每次 input 事件都调一遍 cache(), 你会在拖动过程中反复分配和销毁大块内存,卡顿非常明显。