html canvas clip but with an image

946 Views Asked by At

I have been working with html canvas compositing trying to clip a pattern with a mask.

The main issue that I have is that the mask I have comes from an svg with transparencies within the outer most border. I want the entire inside from the outer most border to be filled with the pattern.

Take this SVG for example you can see that there is a single pixel border, then some transparency, and then an opaque red inner blob. The compositing I have done works as the documentation says it should, the single pixel border and the red inner portion pick up the pattern that I want to mask into this shape. The problem is that I want to mask the entire innards starting from the single pixel border.

This is where I think clip might help. But it seems clip only works with manually drawn paths, not paths from an svg (at least that I am aware of).

Is there a way to accomplish what I am trying to do?

Regards, James

1

There are 1 best solutions below

0
On

The Path2D constructor accepts an SVG path data argument, that it will parse as the d attribute of an SVG <path> element.

You can then use this Path2D object with the clip() method:

(async () => {
// fetch the svg's path-data
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");
// build our Path2D object and use it
const path = new Path2D(pathData);
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 30;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
  for(let x = 0; x < canvas.width; x += rad * 2 ) {
    ctx.moveTo(x+rad, y);
    ctx.arc(x, y, rad, 0, Math.PI*2);
  }
}
ctx.fillStyle = "red";
ctx.fill();

})().catch(console.error);
<canvas width="792" height="612"></canvas>

If you need to transform this path-data (e.g scale, or rotate), then you can create a second Path2D object, and use its .addPath(path, matrix) method to do so:

// same as above, but smaller
(async () => {
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");

const originalPath = new Path2D(pathData);
const path = new Path2D();
// scale by 0.5
path.addPath(originalPath, { a: 0.5, d: 0.5 });
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 15;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
  for(let x = 0; x < canvas.width; x += rad * 2 ) {
    ctx.moveTo(x+rad, y);
    ctx.arc(x, y, rad, 0, Math.PI*2);
  }
}
ctx.fillStyle = "red";
ctx.fill();

})().catch(console.error);
<canvas width="396" height="306"></canvas>