In this part we will solve the last problem: how to interact with objects with irregular shapes.
The simple answer is that you don't have to. You can wrap any image inside a rectangular box and detect where the image is transparent.
Introducing offscreen canvas
An offscreen canvas is a canvas outside the DOM. It's very useful for 2 reasons:- you can do complex drawing once and reuse the result with just an instruction
- you can use it to check where an image is transparent
Let's draw
First of all I start with an images array (I used inline images, but you can use images urls):
var objs = [
{name:'firefox',
src:'data:image/png;base64,iVBORw0KGgoAAAA.....',
x:30,
y:50,
angle:Math.PI/2,
},
{name:'opera',
src:'data:image/png;base64,iVBORw0KGgoAAAA...',
x:40,
y:30,
angle:Math.PI/4,
},
{name:'chrome',
src:'data:image/png;base64,iVBORw0...',
x:60,
y:100,
angle:-Math.PI/4,
}
];
Then I draw each image inside an offscreen canvas and, then the offscreen canvas in the main canvas:var i, obj;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0;i < objs.length;i++){
obj = objs[i];
(function (obj){
var img = new Image(); // Creating a new img element
img.onload = function(){
// It's very important to wait until the image loads!!!
// otherwise I have no clue of the real size of the image
ctx.save();
ctx.translate(obj.x, obj.y);
ctx.rotate(obj.angle);
obj.width = img.width;
obj.height = img.height;
//I attach the offscreen canvas to the object
obj.canvas = document.createElement("canvas");
obj.canvas.width = img.width;
obj.canvas.height = img.height;
obj.ctx = obj.canvas.getContext('2d');
// I draw the image on a off-screen canvas
obj.ctx.drawImage(img, 0 , 0);
// I draw the off-screen canvas on the main canvas
ctx.drawImage(obj.canvas, -img.width/2 , -img.height/2);
ctx.restore();
};
img.src = obj.src; // Set source path
}(objs[i]));
}
Pay attention! I have applied transformations on the main canvas and not on each off-screen canvases.
The values are Red, Green, Blue and Alpha (transparency) and they have values between 0 and 255.
I set a threeshold of 50 on alpha.
This is the whole code:
And this is the results:
Detecting mouse clicks
Let's finish detecting on which shape a user is pointing. As usual I check each object in reverse order and transform pointer coordinates. Then I must retrieve the image from the offscreen canvas and test the color on the resulting coordinates. If the alpha value is opaque the pointer is on the image. // I get a single pixel
imageData = obj.ctx.getImageData(p.x+(obj.width/2), p.y+(obj.height/2), 1, 1);
// imageData contains r,g,b,a
if(imageData.data[3] > 50){ // 50 is the threeshold
log.innerHTML = 'clicked on ' + obj.name;
return;
}
I have taken a pixel from the canvas using getImageData. This function returns an array that include 4 values for each pixel (just one in my case).The values are Red, Green, Blue and Alpha (transparency) and they have values between 0 and 255.
I set a threeshold of 50 on alpha.
This is the whole code:
canvas.addEventListener('click', function (evt){
var rect = canvas.getBoundingClientRect(),
posx = evt.clientX - rect.left,
posy = evt.clientY - rect.top,
i = objs.length,
obj,p;
for (; i > 0; i--){
obj = objs[i-1];
// translate coordinates
p = new Point(posx, posy);
p.translate(-obj.x, -obj.y);
p.rotate(-obj.angle);
imageData = obj.ctx.getImageData(p.x+(obj.width/2), p.y+(obj.height/2), 1, 1);
if(imageData.data[3] > 50){
log.innerHTML = 'clicked on ' + obj.name;
return;
}
}
log.innerHTML = '';
}, false);
And this is the results:
I hope this is helpful!