How to calculate required hue-rotate to generate specific colour?

The key in this case is to define an initial color. White nor black or any gray-scale is technically an actual color – you can’t saturate or rotate it. You’ll have to “colorize” it somehow, and the sepia filter is the only filter which do some form of colorizing.

It would be easier if your image was pure 100% red. Then you could just add the target degree directly and adjust saturation and lightness using HSL for target. For a white color start point the first step is to convert and define an intermediate color so we can saturate and rotate it later on.

Lets first darken the white image and apply sepia to get a “base” color we can work with:

filter: brightness(50%) sepia(1);

This will produce RGB color value of approximately:

rgb(178, 160, 128)

Step two is to convert that to HSL color-space which gives us:

hsl(38, 24.5%, 60%);

Base color result

div {
  background:url(http://richard.parnaby-king.co.uk/basket.svg) no-repeat;
  background-size:5em;
  width:5em;
  height:5em;
  -webkit-filter: brightness(50%) sepia(1);
  filter: brightness(50%) sepia(1);
}
<div></div>

Converting base color to target color

These two first steps are static and its result will be reused every time we need to find a target adjustment (the actual value of sepia is defined in the SVG Filters specification).

Now we need to calculate what we need to apply to this base color to get target color. First convert target color, for example #689d94 as given in the question, to HSL:

hsl(170, 21.3%, 51.2%);

Then we have to calculate the difference between those. Hue is calculated by simply subtracting base from target. The same for Saturation and Lightness, but as we assume 100% of the base value we need to subtract the result from 100% to end up with a diff for the accumulated values:

H:  170 - 38             ->  132°
S:  100 + (24.5 - 21.3)  ->  103.2%  (relative to base 100% =  3.2%)
L:  100 + (51.2 - 60.0)  ->   91.2%  (relative to base 100% = -8.8%)

Convert those values to a filter-string by appending it to the existing filter, then set it on the div:

/*      ------ base color ------  -------  new target -------------------------------*/
filter: brightness(50%) sepia(1)  hue-rotate(132deg) saturate(103.2%) brightness(91.2%);

And to set it you would probably do something like this assuming filter and divElement are already declared:

...
filter = "brightness(0.5) sepia(1) hue-rotate(132deg) saturate(103.2%) brightness(91.2%)";
divElement.style.filter = filter;
divElement.style.webkitFilter = filter;

Note that there is likely rounding errors as RGB is represented as integer, while HSL is floating point, so the actual result may not be exact, but it should get pretty close.

Live example

div {
  background:url(http://richard.parnaby-king.co.uk/basket.svg) no-repeat;
  background-size:5em;
  width:5em;
  height:5em;
  -webkit-filter: 
      brightness(50%) sepia(1) hue-rotate(132deg) saturate(103.2%) brightness(91.2%);
  filter: 
      brightness(50%) sepia(1) hue-rotate(132deg) saturate(103.2%) brightness(91.2%);
}
<div></div>
<span style="font:14px sans-serif;padding:7px;color:#fff;background:#689d94">
Target color</span>

Viable alternative options are:

  • Predefine SVGs with the color already set.
  • Work with HSL/RGB directly in JavaScript and modify the SVG tree with the color directly for the shape rather than using filters. Filters are expensive performance wise, especially if many are chained as here and they are in addition a dominant part of a page. They are neither supported in all browsers.

Leave a Comment