代码之家  ›  专栏  ›  技术社区  ›  Maros Urbanec

为什么色调旋转+180度和-180度不能产生原始颜色?

  •  17
  • Maros Urbanec  · 技术社区  · 13 年前

    通过阅读HSL/HSV颜色理论,我得到的印象是,色调分量是一种周期性属性,每360度重复一次,可以独立于饱和度和亮度/值进行更改。如果我错了,请纠正我,但这些说法在逻辑上遵循了前面的定义:

    1. 将色调旋转360度可产生相同的颜色
    2. 将色调旋转180度两次可生成原始颜色
    3. 将色调旋转180度,然后旋转-180度,生成原始颜色

    但是,只有选项1是正确的。将色调旋转4次+90度,会产生一种与原始颜色根本不相似的颜色。

    此外,使用-webkit过滤器和SVG

    <filter><feColorMatrix in="SourceGraphic" type="hueRotate" values="..." /></filter>
    

    对于相同的旋转不会产生相同的结果。另一方面,SVG过滤器生成的颜色在浏览器之间是一致的。

    色调旋转是否存在使操作不关联的“隐藏”特性?


    webkit过滤器和SVG的示例可以在这里找到: http://jsfiddle.net/maros_urbanec/ARsjb/5/

    3 回复  |  直到 13 年前
        1
  •  46
  •   Michael Mullany    13 年前

    在CSS和SVG过滤器中,都没有转换为HSV或HSL——色调旋转游击手使用RGB空间中的线性矩阵近似来执行色调旋转。对于小的旋转和高饱和的颜色,这并不能很好地保持饱和度或亮度——正如你所看到的。

    真正的色调旋转,将首先将输入RGB颜色转换为HSL,调整H,然后转换回RGB。过滤器不能做到这一点。而且这种转换不能用线性矩阵精确地近似,所以当色调被精确地改变(大部分)时,饱和度和亮度会到处变化。这些效果是非线性的,所以将较小的操作加在一起会产生不同的颜色,而不是进行一次大的操作。

    (SVG和CSS过滤器中色调的差异可能是由于使用了不同的颜色空间(sRGB与linearRGB)-这些 应该 相同。)

    更新: 我有足够的兴趣去做一个手工比较。正如你所看到的,滤镜在0到180度范围内旋转纯色的色调方面做得很糟糕。此图像将通过手动插入hsl颜色(外圈)完成的手动色调旋转与基本颜色(内圈)上的滤色器色调旋转进行比较

    Explicit HSL Hue Rotation vs. CSS Filter Hue Rotation

    但是,正如你所看到的,它们在不太纯净的颜色上做得更好,比如hsl(0,50%,75%)。 hue rotation with mid HSL

    代码笔链接,如果您想播放: http://codepen.io/mullany/pen/fwHrd

        2
  •  9
  •   Community Mohan Dere    9 年前

    迈克尔的回答太棒了,我真希望我以前看过;但既然我不仅需要明白他们是该死的wierd,而且 以哪种方式 (我想研究他们的逻辑,所以我需要数学),我已经编码了 hue-rotate Javascript中的实现(主要来自于阅读Firefox的源代码),它模拟 色相旋转 Webkit/Blink/Gecko使用的。

    同样,这里的重点只是了解它会产生什么结果。

    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>

    片段取自 this answer .

        3
  •  2
  •   imuli    9 年前

    tl;博士 将颜色从浮点(过滤器内部)转换为字节(其他位置)时出错。

    所以比这复杂一点 spec 为色调旋转矩阵提供了一个很好的公式,例如180度的公式是(不包括alpha和偏移):

    -0.5747  1.4304   0.1444
     0.4252  0.4304   0.1444
     0.4252  1.4304  -0.8556
    

    注意,如果你将其本身相乘,你会得到(小数点后四位):

     0.9999  0.0001   0.0000
     0.0000  1.0      0.0
     0.0000  0.0000   1.0
    

    其非常接近单位矩阵或零变换。

    这将是完美的,除了浏览器在每个过滤器之间转换回RGB。看看当我们色调旋转明亮的红色时会发生什么:

    -0.5747  1.4304   0.1444     1     -0.5747
     0.4252  0.4304   0.1444  *  0  =   0.4252
     0.4252  1.4304  -0.8556     0      0.4252
    

    我们得到了一种不可能用RGB表示的颜色,其值从0到255。所以它被绑定并取整为 0 0.4235 0.4235 在RGB转换过程中,当它再次旋转时,我们会得到一个暗的去饱和红色, 0.6667 0.2431 0.2431 而不是我们刚开始的那种明亮的纯红色。