跳到主要内容

Solarize 曝光反转

曝光反转(日晒效果)来自暗房里的一个意外:冲洗过程中曝光过度的部分会反相。 数字实现就是"亮度超过某个值的像素取反"。

node.cache();
node.filters([Konva.Filters.Solarize]);
// 没有可设的属性

阈值硬编码为 128​

源码:

const threshold = 128; // 写死的局部常量
const L = 0.2126 * r + 0.7152 * g + 0.0722 * b; // sRGB 亮度
if (L >= threshold) {
d[i] = 255 - r;
d[i + 1] = 255 - g;
d[i + 2] = 255 - b;
}

两点值得注意:

threshold 是函数内部的局部常量,不是节点属性。 Solarize 没有注册任何访问器。如果你看到有代码写 node.threshold(0.3) 配合 Solarize,那个 threshold 其实是 Threshold 和 Mask 共用的属性, 对 Solarize 完全没有影响。

亮度用的是标准 sRGB 系数(0.2126 / 0.7152 / 0.0722), 和 RGB 滤镜里那组非标准权重不同。

判定是按整个像素的亮度做的,不是逐通道—— 一旦超过阈值,三个通道一起反相。所以结果里不会出现 "红通道反了但绿通道没反"这种情况。

可调阈值的版本​

阈值写死意味着深色图片几乎不变(没有像素超过 128), 亮色图片则大面积反相。要控制这一点,抄一份源码改成可调的 自定义滤镜:

function SolarizeAt(imageData) {
const threshold = this.getAttr('solarizeThreshold') ?? 128;
const d = imageData.data;
for (let i = 0; i < d.length; i += 4) {
const L = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
if (L >= threshold) {
d[i] = 255 - d[i];
d[i + 1] = 255 - d[i + 1];
d[i + 2] = 255 - d[i + 2];
}
}
}

node.setAttr('solarizeThreshold', 180); // 只反转很亮的部分
node.cache();
node.filters([SolarizeAt]);

阈值调高(180 以上)只反转高光,效果克制、更像真实的暗房日晒; 调低(80 以下)会让大部分画面反相,接近 Invert 的效果。

常见问题​

为什么我的图片加了 Solarize 几乎没变化?​

图片整体偏暗,没有多少像素的亮度超过 128。

验证一下平均亮度:如果一张图的平均亮度只有 60~70, 那么按 128 这个固定阈值,只有少数高光会被反转, 肉眼很难注意到。

两个办法:先用 Brightness 提亮再 solarize, 或者用上面那个可调版本把阈值降到图片实际的亮度区间里。

Solarize 和 Invert 有什么区别?​

Invert 反转所有像素, Solarize 只反转亮度超过阈值的部分。

所以 Solarize 的结果里暗部保持原样、亮部变成它的补色, 在明暗交界处形成强烈的分界线——这正是日晒效果的视觉特征。 Invert 则是整体的负片。

能反转暗部而不是亮部吗?​

把判断条件反过来即可,用上面的自定义滤镜模板, 把 L >= threshold 改成 L < threshold。

这不是标准的日晒效果,但用来做某些图形化的视觉处理挺有意思—— 暗部变亮、亮部保持,画面会呈现出一种"内发光"的感觉。

与其他方案的取舍​

Solarize 是一个效果固定、用途狭窄的滤镜。 它的全部价值在于"一行代码得到一个特定的艺术效果"。

需要任何程度的控制,就得换方案:

要调阈值 → 上面的自定义滤镜版本,二十行,完全可控。

要更丰富的艺术效果 → 单个滤镜做不到。 日晒通常要配合对比度、色调偏移一起用:

node.filters([
Konva.Filters.Solarize,
Konva.Filters.Contrast,
Konva.Filters.HSL,
]);
node.contrast(20);
node.hue(15);

注意顺序——靠前的先执行,先反相再调对比, 和先调对比再反相是两种完全不同的画面。

只是要个视觉效果、不读像素 → CSS 滤镜里没有 solarize, 这是少数几个必须用 Konva 内置滤镜(或自定义滤镜)的效果之一。 Invert 在 CSS 里有 invert(), 但那是全反相,不是日晒。

要精确复刻某个摄影软件的日晒曲线 → Konva 的实现是最简单的硬判定, 真实的日晒是一条平滑的 S 形曲线(超过阈值后逐渐反转而不是突变)。 需要那种效果得自己实现查找表(LUT), 在自定义滤镜里按曲线映射每个亮度值。