javascriptmathcanvascolorscolor-palette

Apply pseudo-color palette to canvas image


How to apply a custom palette to a canvas image?

for example, turn a black-white image to a color image:

enter image description here enter image description here

const colorstops = ["white", "#ffd700", "#cc0077", "#20008c", "black"]; // palette

const imageData = ctx.getImageData(x, y, width, height);
for(let i = 0; i < imageData.data.length; i +=4){
  imageData.data[i] = ?
  imageData.data[i + 1] = ?
  imageData.data[i + 2] = ?
}

ctx.putImageData(imageData, x, y);

so the imageData.data is an array of 0-255 numbers. Each 4 elements from the array correspond to a rgba value. I have no clue how to map these to colors from the custom palette.

I guess that one solution would be to convert the colorstops hex colors array to an array that fully maps colors to the entire range from rgb(0,0,0) until rgb(255,255,255) but that sounds terribly inefficient. Is there some math formula I can use instead?


Solution

  • You could calculate the average of the red, green and blue values of every pixel, and then use this value to mapp it to neares color in your custom palette.To find the values for each color channel based on the distance between the average value and the closest color in the palette you can use linear interpolation.

    for (let i = 0; i < imageData.data.length; i += 4) {
        let avg = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3;
    
        // find the closest color in the custom palette
        let closestColorIndex = 0;
        for (let j = 0; j < colorstops.length - 1; j++) {
            if (avg >= (j * 255 / (colorstops.length - 1)) && avg <= ((j + 1) * 255 / (colorstops.length - 1))) {
                closestColorIndex = j;
                break;
            }
        }
    
        // determine the values for each color channel based on linear interpolation
        let color1 = colorstops[closestColorIndex];
        let color2 = colorstops[closestColorIndex + 1];
        let t = (avg - (closestColorIndex * 255 / (colorstops.length - 1))) / (255 / (colorstops.length - 1));
    
        imageData.data[i] = color1[0] + (color2[0] - color1[0]) * t;
        imageData.data[i + 1] = color1[1] + (color2[1] - color1[1]) * t;
        imageData.data[i + 2] = color1[2] + (color2[2] - color1[2]) * t;
    }