html canvas - multiple svg images which all have different fillStyle colors

144 Views Asked by At

For a digital artwork I'm generating a canvas element in Vue which draws from an array of multiple images. The images can be split in two categories:

  • SVG (comes with a fill-color)
  • PNG (just needs to be drawn as a regular image)

I came up with this:

    const depict = (options) => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      const myOptions = Object.assign({}, options);
      if (myOptions.ext == "svg") {
        return loadImage(myOptions.uri).then((img) => {
          ctx.drawImage(img, 0, 0, 100, 100);
          ctx.globalCompositeOperation = "source-in";
          ctx.fillStyle = myOptions.clr;
          ctx.fillRect(0, 0, 100, 100);
          ctx.globalCompositeOperation = "source-over";
        });
      } else {
        return loadImage(myOptions.uri).then((img) => {
          ctx.fillStyle = myOptions.clr;
          ctx.drawImage(img, 0, 0, 100, 100);
        });
      }
    };
    this.inputs.forEach(depict);

for context:
myOptions.clr = the color
myOptions.uri = the url of the image
myOptions.ext = the extension of the image

While all images are drawn correctly I can't figure out why the last fillStyle overlays the whole image. I just want all the svg's to have the fillStyle which is attached to them.

I tried multiple globalCompositeOperation in different orders. I also tried drawing the svg between ctx.save and ctx.restore. No succes… I might be missing some logic here.

1

There are 1 best solutions below

0
On

So! I figured it out myself in the meantime :)

I created an async loop with a promise. Inside this I created a temporary canvas per image which I then drew to one canvas. I took inspiration from this solution: https://stackoverflow.com/a/6687218/15289586

Here is the final code:

    // create the parent canvas
    let parentCanv = document.createElement("canvas");
    const getContext = () => parentCanv.getContext("2d");
    const parentCtx = getContext();
    parentCanv.classList.add("grid");

    // det the wrapper from the DOM
    let wrapper = document.getElementById("wrapper");

    // this function loops through the array
    async function drawShapes(files) {
      for (const file of files) {
        await depict(file);
      }
      // if looped > append parent canvas to to wrapper
      wrapper.appendChild(parentCanv);
    }

    // async image loading worked best
    const loadImage = (url) => {
      return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject(new Error(`load ${url} fail`));
        img.src = url;
      });
    };

    // depict the file
    const depict = (options) => {
      // make a promise
      return new Promise((accept, reject) => {
        const myOptions = Object.assign({}, options);
        var childCanv = document.createElement("canvas");
        const getContext = () => childCanv.getContext("2d");
        const childCtx = getContext();
        if (myOptions.ext == "svg") {
          loadImage(myOptions.uri).then((img) => {
            childCtx.drawImage(img, 0, 0, 100, parentCanv.height);
            childCtx.globalCompositeOperation = "source-in";
            childCtx.fillStyle = myOptions.clr;
            childCtx.fillRect(0, 0, parentCanv.width, parentCanv.height);
            parentCtx.drawImage(childCanv, 0, 0);
            accept();
          });
        } else {
          loadImage(myOptions.uri).then((img) => {
            // ctx.fillStyle = myOptions.clr;
            childCtx.drawImage(img, 0, 0, 100, parentCanv.height);
            parentCtx.drawImage(childCanv, 0, 0);
            accept();
          });
        }
      });
    };

    drawShapes(this.inputs);