跳到主要内容

Sepia 棕褐色

老照片的棕褐色调。这是本章最简单的滤镜——没有任何参数。

node.cache();
node.filters([Konva.Filters.Sepia]);
// 就这样,没有可设的属性

固定矩阵,强度不可调​

源码里是一组写死的系数:

data[i] = min(255, r * 0.393 + g * 0.769 + b * 0.189);
data[i + 1] = min(255, r * 0.349 + g * 0.686 + b * 0.168);
data[i + 2] = min(255, r * 0.272 + g * 0.534 + b * 0.131);

这是业界通用的 sepia 矩阵(和 CSS filter: sepia(1) 用的是同一组)。 没有注册任何配置属性,所以你要么要全部效果,要么不用它。

注意系数行和大于 1(第一行 0.393+0.769+0.189 = 1.351), 所以亮部会被推到 255 饱和。这是 sepia 效果的一部分—— 旧照片的高光本来就是"糊掉"的。

需要半程效果怎么办​

写一个带插值的自定义滤镜。 核心就是把结果和原值按比例混合:

function SepiaAmount(imageData) {
const amount = this.getAttr('sepiaAmount');
const t = amount === undefined ? 1 : amount; // 0 ~ 1
const d = imageData.data;

for (let i = 0; i < d.length; i += 4) {
const r = d[i], g = d[i + 1], b = d[i + 2];
const sr = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189);
const sg = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168);
const sb = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131);
d[i] = r + (sr - r) * t;
d[i + 1] = g + (sg - g) * t;
d[i + 2] = b + (sb - b) * t;
}
}

node.setAttr('sepiaAmount', 0.5);
node.cache();
node.filters([SepiaAmount]);

t = 0 完全不变,t = 1 与内置的 Sepia 结果一致,中间值是线性过渡。 这个写法也适用于任何其他"想要半程效果"的固定滤镜。

常见问题​

和 Grayscale 有什么关系?​

Sepia 可以理解为"灰度化之后染上棕色", 但它不是真的先转灰度——矩阵一步完成, 而且保留了一点点原图的色彩倾向(三行系数不完全相同)。

如果你想要的是"先灰度、再精确控制染色色相", 用 Grayscale 加 RGB 更可控:

node.filters([Konva.Filters.Grayscale, Konva.Filters.RGB]);
node.red(200); node.green(160); node.blue(110);

为什么亮的地方全糊成一片?​

矩阵的系数行和大于 1,亮部计算出来超过 255 被裁切。 原图里较亮的区域会一起饱和成同一个米白色,细节丢失。

想保留亮部,先用 Brightness 压暗一点再上 sepia:

node.filters([Konva.Filters.Brightness, Konva.Filters.Sepia]);
node.brightness(0.85);

能叠加多次让效果更强吗?​

可以放两个 Konva.Filters.Sepia 进数组,但效果不是"更棕", 而是更快地全部饱和成米白——因为第二遍是在已经偏棕的结果上再乘一遍矩阵。

想要更浓的怀旧感,正确的组合是 sepia 加低对比度加暗角, 而不是重复 sepia。

与其他方案的取舍​

只想要一个棕褐色调、不需要调参 → 内置 Sepia,一行搞定。

需要调强度 → 上面那个插值版自定义滤镜,二十行代码, 可控性完整。

只是页面上的视觉效果、不需要读像素 → 用 CSS 滤镜:

node.filters(['sepia(0.6)']);

CSS 滤镜字符串原生支持强度参数,而且走浏览器的实现, 性能比 JS 逐像素循环好得多。如果你只是要 sepia 效果而不需要 后续读取像素数据,这才是首选——内置的 Konva.Filters.Sepia 在可调性和性能上都不如它。

内置滤镜真正不可替代的场景是:你要把处理后的像素 toDataURL() 导出,或者要和其他自定义滤镜串在同一条链上。

要做整套复古风格(褪色 + 暗角 + 颗粒 + 划痕)→ 单个滤镜堆不出来,考虑在离屏 canvas 上分层合成, 或者干脆用预制的纹理贴图叠加,比逐像素计算快得多。