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!