Is there any way to colorize a white PNG image with CSS only?
Asked Answered
M

4

41

I know there are many css filters out there especially for webkit, but I can't find a solution for colorize a white (#FFFFFF) image. I've tried some combination of the filters, but none of them make my image colorized. I can only change the image in the range of grayscale, or sepia.

So my question is: Is there any way to change my totally white png to (for example) red using css only?

Like shown on this image:

enter image description here

Margravine answered 26/3, 2015 at 14:18 Comment(8)
Can you use a color instead of black/white/gray? If so, then you can use hue rotate to change the color.Bircher
I know that hue-rotate can change a color of an (already colorful) image, but I need to use a white image for start.Proptosis
One alternative to css filters is using an icon font, see: https://mcmap.net/q/63413/-change-color-of-png-image-via-css and fontawesome.io/icon/commentWillettawillette
Fontawesome can be a good solution, because I only want it to apply to some basic icons. Thanks for your comment! :)Proptosis
SVG can also be a good solution, but I don't want to change the whole markup because of such a little thing. We need to start the website this week, so I don't have too much time for little design elemets, but I want to find a good solution to use in future projects too.Proptosis
It's a shame css masking isn't well supported yet. Since your image is white, it would work perfectly for masking. A similar thing can be done with SVG, but as you've said, would require considerable markup change: jsfiddle.net/up5p7sdx/1Surface
I know the svg clip-path way, and I think this would be the ultimate cross-browser solution for my problem, because pure css is only supported in webkit.Proptosis
Possible hack: sepia, colorize, and hue-rotate? This will be highly browser-dependent if they have different interpretations of "sepia", but it's well-defined in the spec, so they may all be the same.Britisher
S
33

This can be done with css masking, though unfortunately browser support is really bad (I believe webkit only).

http://jsfiddle.net/uw1mu81k/1/

-webkit-mask-box-image: url(http://yourimagehere);

Because your image is all white, it is a perfect candidate for masking. The way the mask works is that wherever the image is white, the original element is shown as normal, where black (or transparent) the original element is not shown. Anything in the middle has some level of transparency.

EDIT:

You can also get this to work in FireFox with slight help from svg.

http://jsfiddle.net/uw1mu81k/4/

div {
  width: 50px;
  height: 50px;
  mask: url(#mymask);
  -webkit-mask-box-image: url(http://www.clker.com/cliparts/F/5/I/M/f/U/running-icon-white-on-transparent-background-md.png);
}
<div style="background-color: red;"></div>
<div style="background-color: blue;"></div>
<div style="background-color: green;"></div>
<div style="background-color: orange;"></div>
<div style="background-color: purple;"></div>

<svg height="0" width="0">
  <mask id="mymask">
    <image id="img" xlink:href="http://www.clker.com/cliparts/F/5/I/M/f/U/running-icon-white-on-transparent-background-md.png" x="0" y="0" height="50px" width="50px" />
  </mask>
</svg>
Surface answered 26/3, 2015 at 15:18 Comment(6)
So I can set an alpha-channel for a div in webkit? It's a very nice feature. Thanks for your answer! :)Proptosis
But SVG clip path has much better support across browsers, so I think it's better to use in production websites (the effect is the same). By the way, you answered my question, so thank you so much! :)Proptosis
It's not working in my firefox 38 though it works in chrome and safari.Ankus
@sussan It seems newer firefox doesn't work with mask if the svg used is hidden. I updated the answer to instead set the width and height to 0 rather than hide it. Seems to work fine for me.Surface
Regarding browser support you can check out caniuse.comStrand
Thanks @JamesMontagne! The first part of this answer and jsfiddle were what I used to get around an ios/cordova bug in -webkit-mask-image. (see #51070292)Mcgehee
F
36

I recently have the same question and I think this approach is worth sharing for future readers. Try this

filter: brightness(50%) sepia(100) saturate(100) hue-rotate(25deg);

Brightness will darken the image, sepia will make it a bit yellowish, saturate to increase the color, and lastly hue-rotate to change the color. It's not cross browser friendly though:

Here are some useful tips and tools that I've used when I work with images/icons using css:

  • If you have the svg version of the image, you can convert them to font icons using this tool https://icomoon.io/ . To change color you just need color:#f00; just like font colors.
  • If you need to achieve this effect for hover state, Use red image with filter: brightness(0) invert(); on NON-hover state, and filter: brightness(1); on hover state. Hovever this will still won't work on IE
  • Use sprite sheet. You can create yourself using image editing tool or use sprite sheet generators available online. Then, you can use SpriteCow to get the css for every sub-image of your spritesheet.
Ferdinande answered 4/7, 2017 at 8:7 Comment(0)
S
33

This can be done with css masking, though unfortunately browser support is really bad (I believe webkit only).

http://jsfiddle.net/uw1mu81k/1/

-webkit-mask-box-image: url(http://yourimagehere);

Because your image is all white, it is a perfect candidate for masking. The way the mask works is that wherever the image is white, the original element is shown as normal, where black (or transparent) the original element is not shown. Anything in the middle has some level of transparency.

EDIT:

You can also get this to work in FireFox with slight help from svg.

http://jsfiddle.net/uw1mu81k/4/

div {
  width: 50px;
  height: 50px;
  mask: url(#mymask);
  -webkit-mask-box-image: url(http://www.clker.com/cliparts/F/5/I/M/f/U/running-icon-white-on-transparent-background-md.png);
}
<div style="background-color: red;"></div>
<div style="background-color: blue;"></div>
<div style="background-color: green;"></div>
<div style="background-color: orange;"></div>
<div style="background-color: purple;"></div>

<svg height="0" width="0">
  <mask id="mymask">
    <image id="img" xlink:href="http://www.clker.com/cliparts/F/5/I/M/f/U/running-icon-white-on-transparent-background-md.png" x="0" y="0" height="50px" width="50px" />
  </mask>
</svg>
Surface answered 26/3, 2015 at 15:18 Comment(6)
So I can set an alpha-channel for a div in webkit? It's a very nice feature. Thanks for your answer! :)Proptosis
But SVG clip path has much better support across browsers, so I think it's better to use in production websites (the effect is the same). By the way, you answered my question, so thank you so much! :)Proptosis
It's not working in my firefox 38 though it works in chrome and safari.Ankus
@sussan It seems newer firefox doesn't work with mask if the svg used is hidden. I updated the answer to instead set the width and height to 0 rather than hide it. Seems to work fine for me.Surface
Regarding browser support you can check out caniuse.comStrand
Thanks @JamesMontagne! The first part of this answer and jsfiddle were what I used to get around an ios/cordova bug in -webkit-mask-image. (see #51070292)Mcgehee
B
6

This can be done with a CSS filter that references an SVG Filter. Here is the SVG filter (the "1 1 1" in the first line of values converts the blue and green channels to red.)

<svg width="800px" height="600px">
    <filter id="redden" color-interpolation-filters="sRGB">
        <feColorMatrix type="matrix" values="1 1 1 0 0 
                                             0 0 0 0 0 
                                             0 0 0 0 0 
                                             0 0 0 1 0"/>
    </filter>
    
    <image filter="url(#redden)" width="190" height="400" 
        preserveAspectRatio="xMinYMin slice" 
        xlink:href="https://i.sstatic.net/e6MUC.jpg"/>
</svg>
Benedictine answered 28/7, 2016 at 1:5 Comment(3)
Your answer would be great with a little explanation on the SVG filter matrix.Proptosis
The matrix specifies the cross-multipliers between source and destination color channels. So the first row, first column is the red channel to red channel multiplier (1x). The first row, second column is the green channel to red channel multiplier (1x). The first row third column is the blue channel to red channel multiplier (1x). Similarly, the zeros in the next two rows will zero out the green and blue channels. While the fourth row, fourth column value (1x) leaves the alpha values unchanged. In other words, this matrix will convert "rgb(50,50,50, 0.5) to (rgb(150, 0, 0, 0.5)Benedictine
FWIW - the sepia/huerotate hack is popular, but this is the most compact and sensible way to do color conversions - as long as you're not allergic to learning how colormatrix works. And here is a little GUI tool to play with color matrix values: codepen.io/mullany/pen/qJCDkBenedictine
M
3

i tried to color my white cloud picture following @zekkai's answer, and then i wrote a filter generator.

var slider_huerotate = document.getElementById("slider_huerotate");
var slider_brightness = document.getElementById("slider_brightness");
var slider_saturate = document.getElementById("slider_saturate");
var slider_sepia = document.getElementById("slider_sepia");

var output = document.getElementById("demo");
var cloud = document.getElementById('cloud');
let [brightness, sepia, saturate, huerotate] = ["100", "80", "100", "360"];
var filtercolor = `brightness\(${brightness}%\) sepia\(${sepia}\) saturate\(${saturate}\) hue-rotate\(${huerotate}deg\)`

// Display the default slider value
output.innerHTML = filtercolor; 

// Update the current slider value (each time you drag the slider handle)
slider_huerotate.oninput = function() {
  huerotate = this.value;
  var filtercolor = `brightness\(${brightness}%\) sepia\(${sepia}\) saturate\(${saturate}\) hue-rotate\(${huerotate}deg\)`
  cloud.style.filter = filtercolor;
  output.innerHTML = filtercolor;
}

slider_brightness.oninput = function() {
  brightness = this.value;
  var filtercolor = `brightness\(${brightness}%\) sepia\(${sepia}\) saturate\(${saturate}\) hue-rotate\(${huerotate}deg\)`
  cloud.style.filter = filtercolor;
  output.innerHTML = filtercolor;
}

slider_sepia.oninput = function() {
  sepia = this.value;
  var filtercolor = `brightness\(${brightness}%\) sepia\(${sepia}\) saturate\(${saturate}\) hue-rotate\(${huerotate}deg\)`
  cloud.style.filter = filtercolor;
  output.innerHTML = filtercolor;
}

slider_saturate.oninput = function() {
  saturate = this.value;
  var filtercolor = `brightness\(${brightness}%\) sepia\(${sepia}\) saturate\(${saturate}\) hue-rotate\(${huerotate}deg\)`
  cloud.style.filter = filtercolor;
  output.innerHTML = filtercolor;
}
.slider {
  -webkit-appearance: none;
  width: 350px;
  height: 15px;
  border-radius: 5px;
  background: #d3d3d3;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #4CAF50;
  cursor: pointer;
}

.slider::-moz-range-thumb {
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #4CAF50;
  cursor: pointer;
}

img {
  width: 300px;
  z-index: 100;
  filter: brightness(100%) sepia(80) saturate(100) hue-rotate(360deg);
  padding: 70px 25px 50px 25px;
}

.wrapper {
  width: 600px;
  height: 500px;
  padding: 50px;
  font-size: small;
}

.slidecontainer_vertical {
  margin-top: 50px;
  transform: translate(0, 300px) rotate(270deg);
  -moz-transform: rotate(270deg);
}

.left {
  width: 50px;
  height: 300px;
  float: left;
}

.cloud {
  width: 100%;
}

.middle {
  width: 350px;
  height: 300px;
  float: left;
  margin: 0 auto;
}

.right {
  width: 50px;
  height: 300px;
  float: left;
}

#demo {
  width: 100%;
  text-align: center;
  padding-bottom: 20px;
  font-family: 'Handlee', cursive;
}
<head>
  <link href="https://fonts.googleapis.com/css?family=Handlee" rel="stylesheet">
</head>
<div class="wrapper">
  <div class="left">
    <div class="slidecontainer_vertical">
      <input type="range" min="0" max="360" value="360" class="slider" id="slider_huerotate">
    </div>
  </div>
  
  <div class="middle">
    <div class="slidecontainer">
      <input type="range" min="0" max="100" value="100" class="slider" id="slider_brightness">
    </div>

    <div id="cloud">
      <img src="https://docs.google.com/drawings/d/e/2PACX-1vQ36u4x5L_01bszwckr2tAGS560HJtQz4qblj0jnvFUPSgyM9DAh7z_L3mmDdF6XRgu8FkTjqJKSNBQ/pub?w=416&amp;h=288">
    </div>
    <div id="demo"></div>

    <div class="slidecontainer">
      <input type="range" min="0" max="100" value="80" class="slider" id="slider_sepia">
    </div>
  </div>
  
  <div class="right">
    <div class="slidecontainer_vertical">
      <input type="range" min="0" max="100" value="100" class="slider" id="slider_saturate">
    </div>
  </div>
</div>
Mak answered 21/3, 2018 at 9:54 Comment(2)
Very nice addition to my question. Useful little app, i like it but intensive colors (like #F00, #0F0, #FF0, and so on) are still impossible to achieve...Proptosis
@BalázsVarga i have edited my code and now you can probably generate intensive colors. i notice that you can generate different colors when you add or remove an '%' to 'sepia' and 'saurate' of the filter, e.g: "brightness(50%) sepia(100) saturate(100) hue-rotate(25deg);" is different from "brightness(50%) sepia(100%) saturate(100%) hue-rotate(25deg);". By removing the '%', you can generate more intensive colors.Mak

© 2022 - 2024 — McMap. All rights reserved.