CodeMoose
CodeMoose

Reputation: 3025

Most browser-supported method to change the color of a simple PNG image?

During a site build, I came across this direction for mouseover styles in the design (paraphrased by me):

paraphrased comp style

This is simple enough to do with js (or even css) image swapping... but it made me ponder. There are going to be a few dozen such icon links across the site - each paired with text, and each potentially needing additional colors, or to have the blue color adjusted. It would be a headache to create a new version of each image for any additional colors, and it would be a nightmare if any colors needed to be adjusted. Wouldn't it be nice if you could control the text and image colors in one place with css?

I've done some research, and popular opinion on affecting the color in an image seems to be split between working with CSS3 masks or putting the whole works in SVG. CSS Masks are a very attractive option for their simplicity, but have one glaring support gap - whereas SVG is recommended by W3C but seems to rely on the images being in .svg format (the .png solutions get complicated and spotty support).

So, is anyone aware of a solution that would enable me to effectively change the color of a flat .png image, that provides adequate browser support? Bonus points for simplicity/elegance, but I'll accept anything that only requires a path to a .png image and a hex color. Thanks!


Edit 1: For the record, IE9-10 are out of scope here - we're only looking at 8 and 11. Thanks!


Edit 2: Other (failed) considerations

I've considered simply creating an inverted "stencil" of each icon and overlaying it on a background color. This, however, poses a problem for our users and content editors, since these icons will be used elsewhere and a white stencil on a white background is useless.

Also read the answers on this related question, but filters don't seem to accept a hex value nor are they fully supported. The icon font might work, but would not be easily extensible/modifiable to a non-savvy user - the "upload-to-cms" functionality is very desired here.

Upvotes: 1

Views: 601

Answers (1)

undefined
undefined

Reputation: 4135

Answer & Demo

I've solved the problem, here is a JSFiddle.

###Pros

  • It's very easy to use.
  • You can select any color in any format known to JavaScript.
  • Your icon can have any color except white.

###Cons

  • In order to be compatible with all the color formats, it needs the tinycolor.js library as a dependency. (However: this can be removed if you create your own color convertion functions)

  • It only works in IE9+ as it uses the HTML5 canvas. (Although to be honest coding for IE is always a pain).

  • The source images have to be in the same server, as canvas can't get tainted from cross-origin sources, and you can't get data from them, this is why the source image in the demo is in Data URI. (However: there is a solution if you are interested).

##HTML

<input type="text" value="#669dbb">
<button>Change color</button>
<img src="data:image/png;base64,(..)">

Simple enough, an <input> for the color you want the image to change to, and a <button> to apply the changes. (By the way, the original value is the lightblueish you have on your default icon).

##JavaScript

var img = document.querySelector("img");
var button = document.querySelector("button");
var text = document.querySelector("input");
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");

button.onclick = function () {
    changeColor(text.value);
};

changeColor = function (inputColor) {
    canvas.setAttribute("width", img.width);
    canvas.setAttribute("height", img.height);
    context.drawImage(img, 0, 0);
    var dataObject = context.getImageData(0, 0, canvas.width, canvas.height);
    var data = dataObject.data;
    for (var i = 0; i < data.length; i += 4) {
        var r = data[i],
            g = data[i + 1],
            b = data[i + 2],
            a = data[i + 3];
        var notTransparent = a != 0;
        var nonBackground = ( notTransparent && r != 255 && g != 255 && b != 255 );
        if ( nonBackground ) {
            var input = tinycolor(inputColor);
            var rgb = input.toRgb();
            data[i] = rgb.r;
            data[i + 1] = rgb.g;
            data[i + 2] = rgb.b;
            data[i + 3] = a;
        }
    }
    context.putImageData(dataObject, 0, 0);
    img.src = canvas.toDataURL();
}

##Explanation

Okey, what's going on here, I'm going to center my explanation on the changeColor() function as the other code blocks are just background work.

  • First, we match the <img>'s width & height with the <canvas>.
  • Then, we draw the image to the canvas.
  • Then, we get the pixel array data from the whole canvas. (More on canvas pixel manipulation.
  • Then we read the data (wich is a loooong array) and get each pixel value.
  • Array[0] = Red
  • Array[1] = Green
  • Array[2] = Blue
  • Array[3] = Alpha.
  • Then, we check if the pixel is not a background pixel, by making sure is not transparent nor white.
  • If it isn't, we get the input color and make it RGB.
  • Then, we modify the pixel data and update the canvas.
  • Finally, we get an <img> source from the canvas, by transforming it to Data URI, and we update the <img>.

#Just one more thing

Now, while that may be cool and all, it would take a lot of grunt-work to get going, so I've made it easier for you, here is a library that allows you to do what you want: AmazingIcon.js (Github)

It's not nearly polished, but it will do the job, here is how it works.

##HTML(head)

<script src=tinycolor.js></script>
<script src=amazingicon.js></script>

##HTML(body)

<a class="amazingIcon" href="some-url" data-src="icon-image-url">icon-label</a>

Fairly simple, we use the data-src to create a pseudo-custom attribute.

##JavaScript

AmazingIcon.parseDocument();

AmazingIcon.hover(function(icon,ev){
    icon.setColor("lightblue");
});

The AmazingIcon.parseDocument() function will convert any anchor with the class amazingIcon to an Amazing Icon.

The AmazingIcon.hover(callback) function applies the hover event to all the amazing icons, the hover event function will recieve both (icon,event) arguments, which you can manipulate accordingly. (More on the documentation)

##CSS

.AmazingIconObject{
    /* Display icons in the same row */
      display: inline-block;
    /* Aligning icons vertically inside parent */
      vertical-align: top;
    /* Centering labels */
      text-align: center;
    /* Separation between the icons */
      margin-right: 5px;
    /* Break label to fit icon width */
      word-wrap: break-word;
    /* Sets the width of the anchor (not the icon) */
      width: 100px;
    /* The following properties are design-subjective */
      font-family: Verdana;
      font-weight: bold;
      color: lightcoral;
      margin-right: 5px;
      text-decoration: none;
}

Very simple, if you'd like to know more of how it works of course it's all in the github, feel free to reproduce and modify the code as you and any other readers please.

##Demostration

As it's difficult to use demonstrate with non-crossorigin sources, I'll just show you an image of what that would look like.

Demonstration of the AmazingIcon.js library.

Hope this helps, see ya!

Upvotes: 1

Related Questions