James Dawson
James Dawson

Reputation: 5409

Getting image pixel data from a canvas sprite

I'm trying to implement pixel perfect collision detection in my canvas game, however I can't seem to get the pixel information from my sprites.

I need the x and y values for each pixel of the sprite, and from what I've read I use the getImageData() method to do that.

However, this doesn't work:

this.sprite = new Image();
this.sprite.src = 'img/player.png';
console.log(this.sprite.getImageData());

Am I maybe using the wrong type of sprite? Because I get this error in the console:

Uncaught TypeError: Object # has no method 'getImageData'

Upvotes: 1

Views: 2072

Answers (1)

markE
markE

Reputation: 105035

Here’s how to use a sprite’s pixel data to do pixel-perfect hit-testing

First, draw your sprite normally on your visible canvas.

enter image description here

Create a red-masked copy of the sprite on a hidden canvas. This copy is the exact size as the sprite but contains only transparent or red pixels.

enter image description here

Track the visible sprite’s bounding box. When the bounding box is clicked calculate the X/Y of the mouseclick in relation to the sprite’s bounding box (not in relation to the canvas).

Then, refer to the red-masked sprite and see if the corresponding pixel at that X/Y is red or transparent. If the pixel is red, you have a pixel-perfect hit. If the pixel is transparent, no hit.

In this illustration, assume the blue dots are the X/Y click position. Since the corresponding X/Y pixel in the red-masked canvas is “red”, this is a HIT.

enter image description here

Here is code to create a red-masked sprite. I don’t show the code for hit-testing here, but if you try and can’t code the hit-test, I’ll take the time to code up the hit-testing too.

Important note: to get this code to run, you must avoid the cross-domain security restriction. Be sure your image source is on your local domain, otherwise you will run into a cross domain security violation and the masking image will not be drawn…So you can't do this for your sprite source: http://otherDomain.com/picture.jpg!

<!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:10px; }
    canvas{border:1px solid blue;}
</style>

<script>
    $(function(){

        var c=document.getElementById("canvas");
        var ctx=c.getContext("2d");

        var img=new Image();
        img.onload=function(){
          ctx.drawImage(this,100,25);

          // make a red-masked copy of just the sprite
          // on a separate canvas
          var canvasCopy=document.getElementById("canvasCopy");
          var ctxCopy=canvasCopy.getContext("2d");
          canvasCopy.width=this.width;
          canvasCopy.height=this.height;
          ctxCopy.drawImage(img,0,0);

          // make a red-masked copy of the sprite on a separate canvas
          var imgData=ctxCopy.getImageData(0,0,c.width,c.height);
          for (var i=0;i<imgData.data.length;i+=4)
            {
                if(imgData.data[i+3]>0){
                    imgData.data[i]=255;
                    imgData.data[i+1]=0;
                    imgData.data[i+2]=0;
                    imgData.data[i+3]=255;
                }
            }
          ctxCopy.putImageData(imgData,0,0);         

        }
        img.src = "houseIcon.png";

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

</head>

<body>
    <p>Original sprite drawn on canvas at XY:100/25</p>
    <canvas id="canvas" width="400" height="300"></canvas>
    <p>Red-masked on canvas used for hit-testing</p>
    <canvas id="canvasCopy" width="300" height="300"></canvas>

</body>
</html>

To do pixel-perfect collision testing between 2 sprites, you would:

Create a red-masked canvas for both sprite#1 and sprite#2.

First test if the bounding boxes of the 2 sprites are colliding. If the bounding boxes are not colliding, the 2 sprites are not colliding, so you can stop the hit-test here.

If the 2 sprites are possibly colliding using the bounding boxes test, create a third canvas for the collision test.

You’re going to use canvas’s compositing method to test for a collision between sprite#1 and sprite#2 by drawing both sprite#1 and sprite#2 onto the third canvas. Using compositing, only the COLLIDING pixels of the 2 sprites will be drawn on the third canvas.

Here’s how Compositing with “destination-in” works: The existing canvas content is kept where both the new shape (sprite#2) and existing shape (sprite#1) content overlap. Everything else is made transparent.

So…

Draw sprite#1 in its transformed state into the third canvas (transforms could be move, rotate, scale, skew--anything!).

Set the globalCompositeOperation to destination-in.

context.globalCompositeOperation = 'destination-over';

Draw sprite#2 in its transformed state into the third canvas.

After this draw, the third canvas contains only the colliding portions of sprite#1 and sprite#2

Test each pixel in the third canvas for non-transparent pixels. If you find any non-transparent pixels, the 2 sprites ARE COLLIDING.

Depending on what action you want to take upon collision, you might just bail out when you find the first colliding pixel.

Upvotes: 5

Related Questions