Jacksonkr
Jacksonkr

Reputation: 32217

canvas isPointInPath does not work with ctx.drawImage()

  1. I suppose this doesn't work because canvas is drawing a bitmap of a vector (and a bitmap is not a path).
  2. Even if it did work, the bitmap is likely always has a rectangular permitter.

Is there any way to leverage something like isPointInPath when using drawImage?

example:

a link to my proof

** EDIT **

I draw a circle on one canvas, and use isPointInPath to see if the mouse pointer is inside the circle (bottom canvas in my example). I also "copy" the bottom canvas to the top canvas using drawImage. Notice that isPointInPath will not work on the top canvas (most likely due to reasons I mentioned above). Is there a work-around I can use for this that will work for ANY kind of path (or bitmap)?

Upvotes: 2

Views: 2981

Answers (4)

Mark
Mark

Reputation: 429

I had some more challenges, because my canvas element could be rescaled. So first when I draw the figures, in my case arc, I save them in an array together with a name and draw them:

      if (this.coInit == false)
      {
        let co = new TempCO ();
        co.name= sensor.Name;
        co.path = new Path2D();
        co.path.arc(c.X, c.Y, this.radius,  0, 2 * Math.PI);
        this.coWithPath.push(co);
      }
      let coWP = this.coWithPath.find(c=>c.name == sensor.Name);
      this.ctx.fillStyle = color;
      this.ctx.fill(coWP.path);

Then in the mouse event, I loop over the items and check if the click event is in a path. But I also need to rescale the mouse coordinates according to the resized canvas:

getCursorPosition(event) {
  const rect = this.ctx.canvas.getBoundingClientRect();
  const x = ((event.clientX - rect.left ) / rect.width) * this.canvasWidth;
  const y = ((event.clientY - rect.top) / rect.height) * this.canvasHeight;
  this.coWithPath.forEach(c=>{

    if (this.ctx.isPointInPath(c.path, x, y))
    {
      console.log("arc is hit", c);
      //Switch light
    }
  });
}

So I get the current size of the canvas and rescale the point to the original size. Now it works!

This is how the TempCO looks like:

export class TempCO
{
  path : Path2D;
  name : string;
}

Upvotes: 0

Gilberto Alexandre
Gilberto Alexandre

Reputation: 2367

To me, isPointInPath failed after canvas was moved. So, I used:

mouseClientX -= gCanvasElement.offsetLeft; mouseclientY -= gCanvasElement.offsetTop;

Upvotes: 0

puk
puk

Reputation: 16782

You could try reimplementing ctx.drawImage() to always draw a box behind the image itself, like so (JSFiddle example):

ctx.customDrawImage = function(image, x, y){
    this.drawImage(image, x, y);
    this.rect(x, y, image.width, image.height);
}
var img1 = new Image();
img1.onload = function(){
var x = y = 0;
ctx.drawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));

x = 1.25 * img1.width;
ctx.customDrawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));

Note: you might get side effects like the rectangle appearing over the image, or bleeding through from behind if you are not careful.

Upvotes: 1

Simon Sarris
Simon Sarris

Reputation: 63812

A canvas context has this hidden thing called the current path. ctx.beginPath, ctx.lineTo etc create this path.

When you call ctx.stroke() or ctx.fill() the canvas strokes or fills that path.

Even after it is stroked or filled, the path is still present in the context.

This path is the only thing that isPointInPath tests.

If you want to test if something is in an image you have drawn or a rectangle that was drawn with ctx.fillRect(), that is not possible using built in methods.

Typically you'd want to use a is-point-in-rectangle function that you write yourself (or get from someone else).

If you're looking for how to do pixel-perfect (instead of just the image rectangle) hit detection for an image there are various methods of doing that discussed here: Pixel perfect 2D mouse picking with Canvas

Upvotes: 4

Related Questions