How to Transform Black into Any Given Color Using Only CSS Filters

CSS - Add Color to Black & White PNG Image Using a Filter

You can do it with CSS filters, though I wouldn’t recommend that at all:

.colorizable {    filter:        /* for demonstration purposes; originals not entirely black */        contrast(1000%)        /* black to white */        invert(100%)        /* white to off-white */        sepia(100%)        /* off-white to yellow */        saturate(10000%)        /* do whatever you want with yellow */        hue-rotate(90deg);}
.example-clip { display: block; height: 20px; margin: 1em; object-fit: none; object-position: 0 0; width: 300px;}
.original { filter: contrast(1000%);}
body { background: #333;}
<img class="colorizable example-clip" src="https://cdn.sstatic.net/Sites/stackoverflow/img/wmd-buttons.svg" /><img class="original example-clip" src="https://cdn.sstatic.net/Sites/stackoverflow/img/wmd-buttons.svg" />

Change color of PNG image via CSS?

You can use filters with -webkit-filter and filter:
Filters are relatively new to browsers but supported in over 90% of browsers according to the following CanIUse table: https://caniuse.com/#feat=css-filters

You can change an image to grayscale, sepia and lot more (look at the example).

So you can now change the color of a PNG file with filters.

body {
background-color:#03030a;
min-width: 800px;
min-height: 400px
}
img {
width:20%;
float:left;
margin:0;
}
/*Filter styles*/
.saturate { filter: saturate(3); }
.grayscale { filter: grayscale(100%); }
.contrast { filter: contrast(160%); }
.brightness { filter: brightness(0.25); }
.blur { filter: blur(3px); }
.invert { filter: invert(100%); }
.sepia { filter: sepia(100%); }
.huerotate { filter: hue-rotate(180deg); }
.rss.opacity { filter: opacity(50%); }
<!--- img src http://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/500px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg -->
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="original">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="saturate" class="saturate">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="grayscale" class="grayscale">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="contrast" class="contrast">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="brightness" class="brightness">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="blur" class="blur">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="invert" class="invert">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="sepia" class="sepia">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="huerotate" class="huerotate">
<img alt="Mona Lisa" src="https://images.pexels.com/photos/40997/mona-lisa-leonardo-da-vinci-la-gioconda-oil-painting-40997.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260" title="opacity" class="rss opacity">

CSS filter: use hue rotation to change certain colors only (like Photoshop Hue/Saturation)

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:

  1. 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)
  2. 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.
  3. Obviously, since the alpha channel is used for masking, this only works on opaque colors/content.
  4. Also, this filter is hardcoded for a specific color range and rotational value. So, not quite scalable, but perhaps useful for individual instances.
  5. 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:

  1. Fill the entire image (area) with the mid-color of your range (e.g. red).
  2. 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.)
  3. 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.)
  4. 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).
  5. Use feComposite to mask the original image and apply the effect (hue rotation) to this part only.
  6. Then paste the effected part on top of the original.
  7. 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:

  • W3C Recommendation SVG 1.1 - Filter effects
  • W3C Editor's Draft - Compositing and Blending Level 2
  • W3C Editor's Draft - Filter Effects Module Level 1
  • W3C Working Draft - Filter Effects Module Level 1
  • W3C Candidate Recommendation - Compositing and Blending Level 1
  • MDN Web Docs - SVG
  • MDN Web Docs - SVG element reference
  • MDN Web Docs - SVG attribute reference
  • WebPlatform project (archived) - feColorMatrix
  • Chromium source code for feColorMatrix
  • Blending Modes in CSS: Color Theory and Practical Application
  • MDN Web Docs - CSS
  • Adobe Photoshop - Blending modes
  • Wikipedia - Blend modes
  • Fastest formula to get Hue from RGB
  • Math behind colorspace conversion - RGB-HSL
  • The Fundamentals of Color: Hue, Saturation, And Lightness
  • Color Theory 101: A Complete Guide to Color Wheels & Color Schemes
  • Css-tricks filter property

CSS Filter as a color modifier for one image

You can also colorize white images by adding sepia and then some saturation, hue-rotation, e.g. in CSS:

filter: sepia() saturate(10000%) hue-rotate(30deg)

This should make white image as green image.

http://jsbin.com/xetefa/1/edit



Related Topics



Leave a reply



Submit