I'm trying to emulate Photoshop's "Color Overlay" using CSS filters, and while doing so, found out the CSS filters operate on colors as consistently as an epileptic seizure.
Consider the color #FF0000
. If we rotate its hue by 120deg
, we should get #00FF00
, and by 240deg
we should get #0000FF
. This is the realm of sanity. Now let's enter CSS filters:
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>
What should be #00FF00
is #007100
, and what should be #0000FF
is #0132FF
. By using hue-rotate
, the hue, saturation and brightness have been set to nonsensical levels, cross-browser.
I need to catch up with Cthulhu and figure out what logic He coded so I can work around it.
Is this a wierd color space unrelated to HSV or HSL? Is it possible to translate HSV, HSL or RGB coordinates into this whimsical dimension? Does it have a name? A standard? A cult following?
I still cannot believe this is cross-browser. I mean, I've been googling for color spaces and couldn't find any where their definition of "hue" makes sense. They pulled it completely out of their asses, as a big, spiky solid block of galvanized stupidity.
Either way, I have read the inscriptions, and after careful examination of the magic incantations, I've produced a javascript version of the horribly-broken hue-rotate
algorithm browsers are currently suffering from.
Here's a jsfiddle version and here's it as a code snippet:
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>
Note that at some point they may find out that the algorithm was coded by an intern on April 1st that wanted to pull a prank on everyone. They may even find the dice used to choose coefficients.
One way or another, knowing the random logic employed helps me work around it, and that's why I did this. Hopefully someone will stuble upon it, and maybe some day we'll have fixed versions of hue-rotate
and company shipped with browsers.
As a bonus, in case it helps anyone: this is how Sepia is calculated:
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,
};
I found that not only CSS but also other implementation are using the same algorithm.
Here is an reasonable 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.
So the algorithm makes hue rotation while preserving luminance against just rotating hue channel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With