17

Does anybody have a good solution to change the color of a non-transparent area, with the help of HTML5 or Canvas (or any other HTML5 technology).

I googled a lot and found some solution (with Canvas) which can change the color of an image,like,

How do i tint an image with HTML5 Canvas?

Canvas - Change colors of an image using HTML5/CSS/JS?

But these solution also change the transparent background to the given color (which i don't want).

Stackoverflow image with transparent pixel(background)

Its a stackoverflow png image with transparent background. Now, i want to change image color without affecting its transparent pixels(background), so that i am able to save an image again with the help of canvas toDataURL method with new color.

I am not sure whether is it possible with HTML5 or not. Any help is highly appreciated.

Thanks in Advance.

*******************EDITED*********************

Added new Image of Pill (capsule).

enter image description here

Community
  • 1
  • 1
Mohit Pandey
  • 3,679
  • 7
  • 26
  • 38
  • This may be exactly what you're not looking for, but what I typically do to change image color is just make it a bit transparent, and set the background image color to the desired color. – dcclassics May 23 '14 at 13:34
  • @dcclassics: I think you misinterpret me. I have an png image with some transparent pixels,let say, a mario(cartoon character) png image. Since mario is a character and have polynomial side. Our image must contain some pixel which is transparent. So now, i want to change the color of a character(mario) without changing the transparent pixels. – Mohit Pandey May 23 '14 at 13:41
  • You probably want `canvas.getImageData` and `putImageData`, which allow you to edit a canvas pixel-by-pixel, using RGBA values. See [this tutorial](http://beej.us/blog/data/html5s-canvas-2-pixel/) for a start. – apsillers May 23 '14 at 13:59

2 Answers2

32

A Demo (see note about this demo below): http://jsfiddle.net/m1erickson/2d7ZN/

enter image description here

You can get an array containing the image's RGBA pixel data using context.getImageData

var imageData=context.getImageData(0,0,canvas.width,canvas.height);

var data=imageData.data;

From there you can read (and optionally change) the color of each pixel.

The rgba array is laid out like this:

// top-left pixel (x:0,y:0)

data[0]   red of pixel#0 (0-255)
data[1]   green of pixel#0 (0-255)
data[2]   blue of pixel#0 (0-255)
data[3]   alpha of pixel#0 (opacity: 0-255)

// next pixel (x:1,y:0)

data[4]   red of pixel#1
data[5]   green of pixel#1
data[6]   blue of pixel#1
data[7]   alpha of pixel#1

You can change the R,G,B or A component of any pixel by changing the data array

For example, this changes the top left pixel to solid red

data[0]=255;   // red
data[1]=0;     // green
data[2]=0;     // blue
data[3]=255;   // alpha (opacity)

About transparency:

The alpha component of each pixel determines that pixel's transparency. If you only want to modify mostly opaque pixels then you can easily ignore all the semi-transparent pixels like this:

// alpha is 0-255
// ignore pixels with alpha < 200 (ignore all pixels that are mostly semi-transparent)

if( data[3]<200 ) { // don't process this pixel }

Finally, you can push your modified pixels back to the canvas with .putImageData

context.putImageData(imageData,0,0);

Note about the Demo

It's difficult to "recolor" an image using the canvas's default RGBA color scheme.

So this demo converts the RGB data to the HSL color scheme. This allows the "color" (hue) to be changed without affecting the other components of the pixel (S=saturation of the color, L=lightness of the color).

After the pixel is recolored, that modified HSL data is converted back to RGBA and saved to the data array.

markE
  • 102,905
  • 11
  • 164
  • 176
  • 1
    Thanks markE. Its a pretty good solution. Although, i am still struggling with the issue. I don't know the color which i want to change in image. I just want to add a color which is passed by user to the image ( which is a mixed of original image color + color passed by user). – Mohit Pandey May 25 '14 at 06:23
  • I have added a new image of pill (capsule). I want to change its color which is passed by user in hexadecimal form. Any guidance on that will be great. Thanks in Adv. – Mohit Pandey May 25 '14 at 06:34
  • Well, transparent pixels have an alpha value near zero (maybe 0-10) so don't change pixels with low alpha values. Your pill image has lighter and darker colors. If you want to change the darker part of the pill then target pixels with lower rgb values (lower rgb values == darker colors). If you want to change the lighter part of the pill then target pixels with higher rgb values. As in my answer, you should convert the selected pixels to HSL and shift the color of the selected pixels towards the color passed by the user. That way the saturation & lightness of the image remain. – markE May 25 '14 at 06:43
  • Thanks. I don't know whether this would solve my particular problem but i definitely give a try to that. Also its a valuable suggestion and this answer also provide a good solution to tackle such problem. So, accepting your anwser. – Mohit Pandey May 25 '14 at 06:53
  • Is it possible to changed image data before image itself is painted on canvas? – Michal Vician May 15 '16 at 09:18
  • @miso. Not really -- you need the image data from the drawn image. But you can create a memory-only canvas with `document.createElement('canvas')` if you don't want to use the displayed canvas for processing. ;-) – markE May 15 '16 at 17:57
  • @markE - Can you explain how you came about the value of 'colorshift' used in your demo? – N.P Aug 24 '20 at 00:38
0

I was having similar issue today so I have created Node.js script which does exactly what is expected.

const { chunk } = require('lodash');
const { createCanvas, loadImage, createImageData } = require('canvas');

const hexToRgb = (hex) => {

    // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
    const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
    hex = hex.replace(shorthandRegex, (m, r, g, b) => {

        return r + r + g + g + b + b;

    });

    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16),
    } : null;

};

const convert = async(url, hex) => {

    const { r, g, b } = hexToRgb(hex);
    const baseImage = await loadImage(url);
    const canvas = createCanvas(baseImage.width, baseImage.height);
    const ctx = canvas.getContext('2d');
    ctx.drawImage(baseImage, 0, 0);
    const { data } = ctx.getImageData(0, 0, baseImage.width, baseImage.height);
    const pixels = chunk(data, 4);
    /**
     * @type {unknown[]}
     */
    const convertedPixel = pixels.map((pixel) => {

        pixel[0] = r;
        pixel[1] = g;
        pixel[2] = b;

        return pixel;

    }).reduce((p, n) => [...p, ...n], []);

    const imageData = createImageData(
        new Uint8ClampedArray(convertedPixel),
        baseImage.width, baseImage.height,
    );
    ctx.putImageData(imageData, 0, 0);
    return canvas.toBuffer();

};