This is actually quite difficult to do straight away, since the feColorMatrix
handles colors in RGB and there's not yet a way to do that in HSL. (Correct me if I'm wrong.)
So I found a solution that comes close to what you might want. The idea is to first mask away the colors you don't want to hue-rotate. Then hue-rotate the remainder and paste that on top of the original.
The code for the SVG with filter looks something like:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<filter id="partial-hue-rotation">
<!--
1) Mask away the colors that shouldn't be hue-rotated.
This is done based on the R-channel value only.
The R-channel value comes in at [0-1],
so multiply it by 255 to get the original value (as in rgb()).
Then subtract (lowest R-channel value of color range - 1)
to leave all color with a R-channel value higher than that.
-->
<feColorMatrix
color-interpolation-filters="sRGB"
type="matrix"
in="SourceGraphic"
result="only-red-visible"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
255 0 0 0 -171"
/><!-- Colors with R-channel > 171 will be left (and thus effected). -->
<!--
2) Apply hue rotation to remaining colors.
-->
<feColorMatrix
type="hueRotate"
values="45"
in="only-red-visible"
result="rotated-part"
/>
<!--
3) Now paste the rotated part on top of the original.
-->
<feMerge>
<feMergeNode in="SourceGraphic" />
<feMergeNode in="rotated-part" />
</feMerge>
</filter>
<!--
This filter is to check if the right range is hue-rotated.
All white areas will be rotated.
The bottom row of values can be copied over the bottom row
of the filter above.
-->
<filter id="test-partial-hue-rotation">
<feColorMatrix
color-interpolation-filters="sRGB"
type="matrix"
in="SourceGraphic"
result="marked-range"
values="0 0 0 0 1
0 0 0 0 1
0 0 0 0 1
255 0 0 0 -171"
/><!-- Colors with R-channel > 171 will be white. -->
<feMerge>
<feMergeNode in="marked-range" />
</feMerge>
</filter>
</svg>
To apply the filter, just add filter: url(#partial-hue-rotation)
to an element's CSS.
To test to see if you are effecting the right colors/parts, you can add filter: url(#test-partial-hue-rotation);
to an element's CSS. All white parts will be hue-rotated. (You might want to set the background color of the parent to black to see it.)
Notes and limitations:
- This method only works when the colors you want to hue-rotate can be separated by a single RGB-channel. For example: all colors that have R-values > X. Then you put the
255
in the first column and -X
in the last column of the alpha row in the matrix. (255
in second column for B-value selection, etc)
- This is not a final solution, since all lighter colors (having R, G and B values that are probably higher then the threshold) will also be hue-rotated.
- Obviously, since the alpha channel is used for masking, this only works on opaque colors/content.
- Also, this filter is hardcoded for a specific color range and rotational value. So, not quite scalable, but perhaps useful for individual instances.
- Apparently there's also a difference between how the CSS
hue-rotate
and feColorMatix
's hueRotate
is calculated (source). This might be eliminated by adding color-interpolation-filters="sRGB"
to the hueRotate
feColorMatrix
tag (not sure).
Anyway, it is a first attempt at this and maybe this approach can help you on your way. :)
Working JSFiddle here
More information:
feColorMatrix
documentation
For more information on how the color matrix for hue-rotate is calculated, see the C++ implementation of the Chrome browser.
See also matrix equivalents of shorthand filters.
And this post.
UPDATE: version 2
So after some reading and thinking, I came up with the idea to use the blend mode difference
to provide the filter with the information about which colors are 'in range' and should be effected.
This works as follows:
- Fill the entire image (area) with the mid-color of your range (e.g. red).
- Use
<feBlend>
in difference mode with the original and the flood. (The darkest parts have the most overlap with the mid-color, e.g. are closest to it on the color wheel.)
- Invert the differences and convert them to average greyscale. (Since it's a pure numerical average we need, all channels are taken 0.3333 times.)
- Using a
feColorMatrix
we now translate this greyscale to alpha values and at the same time map these to have the lowest 2/3 be transparent (will be removed).
- Use
feComposite
to mask the original image and apply the effect (hue rotation) to this part only.
- Then paste the effected part on top of the original.
- Done!
The mid-point and width of the to-be-effected color range can be chosen:
- Mid-color is set as the
flood-color
of the feFlood
. (Use fully saturated and 50% lightness colors for best effect, so #ff0000
, #00ff00
, etc.)
- Width is chosen by the alpha channel's offset in the
feColorMatrix
with result="alpha-mask"
. Example: keep 1/3 of the color wheel gives an offset value of (2/3) * -255
.
Updated working JSFiddle here. (The bottom one, filter #partial-hue-rotation.)
Note:
The hue rotation effect does a horrible job, so not sure what goes wrong there, but the resulting colors are the same with CSS's hue-rotate()
filter.. so, yeah..
UPDATE: version 3
Unfortunately, the filter above does not work correctly for all colors. For a SVG filter that correctly converts the SourceGraphic
to greyscale hue values (where 0deg = black
and 360deg = white
), have a look at the #hue-values
filter I made in this JSFiddle.
If you want to only apply filter effects to all reds/greens/blues/cyans/magentas/yellows, the #tonegroup-select
filter in the same JSFiddle can be used.
The code of this filter is:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="tonegroup-select"
x="0%" y="0%"
width="100%" height="100%"
primitiveUnits="objectBoundingBox"
color-interpolation-filters="sRGB"
>
<!-- Compare RGB channel values -->
<feColorMatrix type="matrix" in="SourceGraphic" result="test-r-gte-g"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 -255 0 0 1"
/>
<feColorMatrix type="matrix" in="SourceGraphic" result="test-r-gte-b"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0 -255 0 1"
/>
<feColorMatrix type="matrix" in="SourceGraphic" result="test-g-gte-r"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -255 255 0 0 1"
/>
<feColorMatrix type="matrix" in="SourceGraphic" result="test-g-gte-b"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 -255 0 1"
/>
<feColorMatrix type="matrix" in="SourceGraphic" result="test-b-gte-r"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -255 0 255 0 1"
/>
<feColorMatrix type="matrix" in="SourceGraphic" result="test-b-gte-g"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -255 255 0 1"
/>
<!-- Logic masks for tone groups -->
<!-- For example: all red colors have red channel values greater than or equal to the green and blue values -->
<feComposite operator="in" in="test-r-gte-g" in2="test-r-gte-b" result="red-mask" />
<feComposite operator="in" in="test-g-gte-r" in2="test-g-gte-b" result="green-mask" />
<feComposite operator="in" in="test-b-gte-r" in2="test-b-gte-g" result="blue-mask" />
<feComposite operator="in" in="test-g-gte-r" in2="test-b-gte-r" result="cyan-mask" />
<feComposite operator="in" in="test-b-gte-g" in2="test-r-gte-g" result="magenta-mask" />
<feComposite operator="in" in="test-r-gte-b" in2="test-g-gte-b" result="yellow-mask" />
<!-- Select all colors in tone group -->
<!-- Note: uncomment the right tone group selection here -->
<!-- Note: greyscale colors will always be selected -->
<feComposite operator="in" in="SourceGraphic" in2="red-mask" result="selection" />
<!-- <feComposite operator="in" in="SourceGraphic" in2="green-mask" result="selection" /> -->
<!-- <feComposite operator="in" in="SourceGraphic" in2="blue-mask" result="selection" /> -->
<!-- <feComposite operator="in" in="SourceGraphic" in2="cyan-mask" result="selection" /> -->
<!-- <feComposite operator="in" in="SourceGraphic" in2="magenta-mask" result="selection" /> -->
<!-- <feComposite operator="in" in="SourceGraphic" in2="yellow-mask" result="selection" /> -->
<!-- Cut selection from original image -->
<!-- Note: use same mask for `in2` attribute as with selection -->
<feComposite operator="out" in="SourceGraphic" in2="red-mask" result="not-selected-source" />
<!-- Apply effects to `selection` only -->
<feColorMatrix
type="saturate"
values="0"
in="selection"
result="edited-selection"
/>
<!-- After all effects, adjustments, etc -->
<!-- the last `result` output name should be "edited-selection" -->
<!-- Bring it all together -->
<feMerge>
<!-- <feMergeNode in="selection" /> --><!-- Uncomment to check selection -->
<feMergeNode in="not-selected-source" />
<feMergeNode in="edited-selection" />
</feMerge>
</filter>
</defs>
</svg>
In the comments inside the code, you find further information on the working and instructions on how to use it.
For more information and reference, have a look at: