为什么 CSS 过滤器 hue-rotate 会产生奇怪的结果?

Why does the CSS filter hue-rotate produce wierd results?

我正在尝试 emulate Photoshop's "Color Overlay" using CSS filters,在这样做的同时,我发现 CSS 滤镜对颜色的作用与癫痫发作一样稳定。

考虑颜色 #FF0000。如果我们将它的色调旋转 120deg,我们应该得到 #00FF00,而旋转 240deg 我们应该得到 #0000FF。这是理智的境界。现在让我们输入 CSS 个过滤器:

body { font: bold 99px Arial }
span { color: #F00; }
.daltonics-wont-notice {
    -webkit-filter: hue-rotate(120deg);
    filter: hue-rotate(120deg);
}
.precision-is-overrated {
    -webkit-filter: hue-rotate(240deg);
    filter: hue-rotate(240deg);
}
<span class="red">☺</span>
<span class="daltonics-wont-notice">☹</span>
<span class="precision-is-overrated">☹</span>

应该#00FF00的是#007100,应该#0000FF的是 #0132FF通过使用 hue-rotate,色调、饱和度和亮度已设置为无意义的水平,跨浏览器。

我需要赶上 Cthulhu 并找出他编码的逻辑,以便我可以解决它。

这是一种与 HSV or HSL 无关的奇怪颜色 space 吗?是否可以将 HSV、HSL 或 RGB 坐标转换为这个异想天开的维度?它有名字吗?一个标准?狂热追随者?

我还是不敢相信这是跨浏览器的。我的意思是,我一直在谷歌上搜索颜色空间,但找不到 any 他们对 "hue" 的定义是有意义的。他们完全把它从屁股里拉了出来,就像一个又大又尖的镀锌愚蠢固体块。

无论如何,我已经阅读了 inscriptions,并且在仔细检查了魔法咒语之后,我制作了一个 javascript 版本的 hue-rotate 算法浏览器。目前正在遭受.

这里是 a jsfiddle version,这里是代码片段:

function calculate() {
    // Get the RGB and angle to work with.
    var color = document.getElementById('color').value;
    if (! /^[0-9A-F]{6}$/i.test(color)) return alert('Bad color!');
    var angle = document.getElementById('angle').value;
    if (! /^-?[0-9]+$/i.test(angle)) return alert('Bad angle!');
    var r = parseInt(color.substr(0, 2), 16);
    var g = parseInt(color.substr(2, 2), 16);
    var b = parseInt(color.substr(4, 2), 16);
    var angle = (parseInt(angle) % 360 + 360) % 360;
    
    // Hold your breath because what follows isn't flowers.
    
    var matrix = [ // Just remember this is the identity matrix for
        1, 0, 0,   // Reds
        0, 1, 0,   // Greens
        0, 0, 1    // Blues
    ];
    
    // Luminance coefficients.
    var lumR = 0.2126;
    var lumG = 0.7152;
    var lumB = 0.0722;
    
    // Hue rotate coefficients.
    var hueRotateR = 0.143;
    var hueRotateG = 0.140;
    var hueRotateB = 0.283;
    
    var cos = Math.cos(angle * Math.PI / 180);
    var sin = Math.sin(angle * Math.PI / 180);
    
    matrix[0] = lumR + (1 - lumR) * cos - lumR * sin;
    matrix[1] = lumG - lumG * cos - lumG * sin;
    matrix[2] = lumB - lumB * cos + (1 - lumB) * sin;
    
    matrix[3] = lumR - lumR * cos + hueRotateR * sin;
    matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin;
    matrix[5] = lumB - lumB * cos - hueRotateB * sin;
    
    matrix[6] = lumR - lumR * cos - (1 - lumR) * sin;
    matrix[7] = lumG - lumG * cos + lumG * sin;
    matrix[8] = lumB + (1 - lumB) * cos + lumB * sin;
    
    function clamp(num) {
        return Math.round(Math.max(0, Math.min(255, num)));
    }
    
    var R = clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b);
    var G = clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b);
    var B = clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b);
    
    // Output the result
    var result = 'The original color, rgb(' + [r,g,b] + '), '
               + 'when rotated by ' + angle + ' degrees '
               + 'by the devil\'s logic, gives you '
               + 'rgb(' + [R,G,B] + '). If I got it right.';
    document.getElementById('result').innerText = result;
}
// Listen for Enter key press.
['color', 'angle'].forEach(function(i) {
    document.getElementById(i).onkeypress = function(event) {
        var e = event || window.event, c = e.which || e.keyCode;
        if (c == '13') return calculate();
    }
});
body {
    font: 14px sans-serif;
    padding: 6px 8px;
}

input {
    width: 64px;
}
<p>
    This algorithm emulates the wierd, nonsensical and completely 
    idiotic <code>hue-rotate</code> CSS filter. I wanted to know
    how it worked, because it is out of touch with any definition
    of "hue" I've ever seen; the results it produces are stupid
    and I believe it was coded under extreme influence of meth,
    alcohol and caffeine, by a scientologist listening to Death Metal.
</p>
<span>#</span>
<input type="text" id="color" placeholder="RRGGBB">
<input type="text" id="angle" placeholder="degrees">
<button onclick="calculate()">Calculate</button>
<p id="result"></p>

请注意,在某些时候他们可能会发现该算法是由一名实习生在 4 月 1 日编写的,他想对每个人进行恶作剧。他们甚至可能找到用于选择系数的骰子。

不管怎样,了解所采用的随机逻辑有助于我解决这个问题,这就是我这样做的原因。希望有人会发现它,也许有一天我们会有固定版本的 hue-rotate 和浏览器附带的公司。


作为奖励,以防对任何人有帮助:这是 Sepia 的计算方式:

var newPixel = {
    newRed:   oldRed * 0.393 + oldGreen * 0.769 + oldBlue * 0.189,
    newGreen: oldRed * 0.349 + oldGreen * 0.686 + oldBlue * 0.168,
    newBlue:  oldRed * 0.272 + oldGreen * 0.534 + oldBlue * 0.131,
};

我发现不仅CSS but also other implementation在使用相同的算法。

这是合理的explanation.

A rotation of 120.0 degrees will exactly map Red into Green, Green into Blue and Blue into Red. This transformation has one problem, however, the luminance of the input colors is not preserved.

因此该算法在保持亮度的同时进行色调旋转,而不是仅旋转色调通道。

这是一种保持亮度的色调旋转。它应该像那样工作。如果在转换为单色之前对图像进行色调旋转,它应该具有相同的感知亮度。 #00FF00 和#0000FF 不会。