14

I am trying to make a game with HTML5's canvas. I have some sprites, I am able to load them well, and they work fine, but some of the parts of the image are specifically #ff0000 and I would like to be able to replace it by some other color, a custom user defined color.

I don't really have a lead on this, I sort of saw image filters, but I didn't really find any examples suited for my usage, and don't have the brains to think it up on my own, trust me, I've tried.

Any help, lead, or whatsoever will be greatly appreciated.

Alexandre Hitchcox
  • 2,704
  • 5
  • 22
  • 33

2 Answers2

17

You can use canvas’ getImageData to replace any color with any other color.

// pull the entire image into an array of pixel data
var imageData = context.getImageData(0, 0, w, h);

// examine every pixel, 
// change any old rgb to the new-rgb
for (var i=0;i<imageData.data.length;i+=4)
  {
      // is this pixel the old rgb?
      if(imageData.data[i]==oldRed &&
         imageData.data[i+1]==oldGreen &&
         imageData.data[i+2]==oldBlue
      ){
          // change to your new rgb
          imageData.data[i]=newRed;
          imageData.data[i+1]=newGreen;
          imageData.data[i+2]=newBlue;
      }
  }
// put the altered data back on the canvas  
context.putImageData(imageData,0,0);

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/4apAS/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; padding:20px; }
    img{border:1px solid red;}
</style>

<script>
$(function(){


    // this just makes an image we can test with
    // it's just an image of a red rectangle
    var temp=document.createElement("canvas");
    var tempctx=temp.getContext("2d");
    tempctx.fillStyle="red";
    tempctx.strokeStyle="blue";
    tempctx.lineWidth=5;
    tempctx.rect(20,20,75,50);
    tempctx.fill();
    var image0=document.getElementById("image0");
    image0.src=temp.toDataURL();

    var image=new Image();
    image.onload=function(){
        // call function to replace red with green
        recolorImage(image,255,0,0,0,255,0);
    }
    image.src= image0.src;


    function recolorImage(img,oldRed,oldGreen,oldBlue,newRed,newGreen,newBlue){

        var c = document.createElement('canvas');
        var ctx=c.getContext("2d");
        var w = img.width;
        var h = img.height;

        c.width = w;
        c.height = h;

        // draw the image on the temporary canvas
        ctx.drawImage(img, 0, 0, w, h);

        // pull the entire image into an array of pixel data
        var imageData = ctx.getImageData(0, 0, w, h);

        // examine every pixel, 
        // change any old rgb to the new-rgb
        for (var i=0;i<imageData.data.length;i+=4)
          {
              // is this pixel the old rgb?
              if(imageData.data[i]==oldRed &&
                 imageData.data[i+1]==oldGreen &&
                 imageData.data[i+2]==oldBlue
              ){
                  // change to your new rgb
                  imageData.data[i]=newRed;
                  imageData.data[i+1]=newGreen;
                  imageData.data[i+2]=newBlue;
              }
          }
        // put the altered data back on the canvas  
        ctx.putImageData(imageData,0,0);
        // put the re-colored image back on the image
        var img1=document.getElementById("image1");
        img1.src = c.toDataURL('image/png');

    }

}); // end $(function(){});
</script>

</head>

<body>
    <p>Original Image</p>
    <img id="image0" width=200 height=200><br/>
    <p>Red recolored Green Image</p>
    <img id="image1" width=200 height=200>
</body>
</html>
markE
  • 102,905
  • 11
  • 164
  • 176
  • I am having issues with this. It replaces most of the colors but not all of them. I am shrinking down the canvas before changing the color if this has any bearing on the result. Any ideas? – Dave M Jul 21 '16 at 19:33
  • This should work, but it visits every pixel in the image, which seems inefficient. If it's a color mapped image, there should be a better way. –  May 22 '19 at 03:30
14

You can use globalCompositeOperation = "source-in"

For example, the following draws the image to a (new) canvas, and sets the colour.

    ctx.save();

    // Draw mask to buffer
    ctx.clearRect(0, 0, this.width, this.height);
    ctx.drawImage(your_image, 0, 0, this.width, this.height, 0, 0, this.width, this.height);

    // Draw the color only where the mask exists (using source-in)
    ctx.fillStyle = [your color]; // 
    ctx.globalCompositeOperation = "source-in";
    ctx.fillRect(0, 0, this.width, this.height);

    ctx.restore();

I use this exact technique to set the text colour of my bitmap fonts.

Jarrod
  • 9,349
  • 5
  • 58
  • 73
  • OP wants to replace 1 color on multi-colored image so they also need you to show them how to create a color-mask to use with your source-in. :) – markE Apr 26 '13 at 03:52
  • @markE I'm just pointing YOU in the right direction. That's your job now ;) – Jarrod Apr 26 '13 at 03:55
  • 1
    Chuckle -- ok but it's after midnight here an I'm getting sleepy so I'll show getImageData instead. I **do like** your idea of compositing a masked color in production--seems cleaner somehow. G'nite, mate! – markE Apr 26 '13 at 04:32
  • @Jarrod -- so in your `drawText` you `fillRect` the off-screen canvas, using "source-in" with the needed color, and then `drawImage` parts (chars) of the off-screen canvas into the visible one? Or is there a more efficient way to do this using a single `drawImage` ? – BitWhistler May 04 '16 at 22:00
  • 1
    What I love about vanilla js is that this answer has just helped me in the end of 2021. Thank you – Miraage Dec 06 '21 at 13:01