javascriptsvgcanvasthree.jsreact-three-fiber

svg on canvas with dynamic properties


im trying draw selected svg to canvas but it is very slow, inefficient, bad resolution and not all svg's are working

and i need them svgs on canvas as dynamic(changing color, rotation and scaling)

Is there any way to do it better? thank you

export const drawPatternsOnCanvas = (ctx, itemsOnTshirt, uvConditions) => {
  itemsOnTshirt.forEach((item) => {
    if (item.type !== "pattern" || !item.img) return;

    const uv = uvConditions[item.part];
    if (!uv) return;
    const canvasSize = 100;
    const img = new Image();
    img.src = item.pattern; //item.pattern="/pattern/pattern01.svg"

    img.crossOrigin = "anonymous";

    img.onload = () => {
      const minX = uv[0];
      const maxX = uv[1];
      const minY = uv[2];
      const maxY = uv[3];

      const regionWidth = maxX - minX;
      const regionHeight = maxY - minY;

      const coloredPattern = document.createElement("canvas");
      coloredPattern.width = img.width;
      coloredPattern.height = img.height;
      const cpCtx = coloredPattern.getContext("2d");

      cpCtx.drawImage(img, 0, 0);

      cpCtx.globalCompositeOperation = "source-atop";
      cpCtx.fillStyle = `rgb(${Math.floor(item.color[0] * 255)}, ${Math.floor(
        item.color[1] * 255
      )}, ${Math.floor(item.color[2] * 255)})`;
      cpCtx.fillRect(0, 0, img.width, img.height);
      cpCtx.globalCompositeOperation = "source-over";

      const fillPattern = ctx.createPattern(coloredPattern, "repeat");

      if (fillPattern.setTransform) {
        const rotationDegrees = parseFloat(item.rotation) || 0;
        const patternRatioX = img.width / canvasSize;
        const patternRatioY = img.height / canvasSize;

        const center = canvasSize * 0.5;

        const matrix = new DOMMatrix()
          .translateSelf(center, center)
          .rotateSelf(-rotationDegrees)
          .scaleSelf(item.scale / patternRatioX, -item.scale / patternRatioY)
          .translateSelf(item.moveX * canvasSize, item.moveY * canvasSize)
          .translateSelf(-center, -center);

        fillPattern.setTransform(matrix);
      }
      ctx.save();
      ctx.beginPath();
      ctx.rect(minX, minY, regionWidth, regionHeight);
      ctx.clip();

      ctx.fillStyle = fillPattern;
      ctx.fillRect(minX, minY, regionWidth, regionHeight);
      ctx.restore();
    };
  });
};

this is the code which implies pattern svg into canvas

export const createUnifiedCanvasTexture = (
  uvConditions,
  colorsForparts,
  itemsOnTshirt
) => {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");

  const width = (canvas.width = 1000);
  const height = (canvas.height = 1000)
  
  //drawing colors
  drawUVRegions(ctx, uvConditions, colorsForparts);

  // Draw patterns
  drawPatternsOnCanvas(ctx, itemsOnTshirt, uvConditions);
  //inside color
  ctx.fillStyle = "#ffffff";
  ctx.fillRect(1, 180, 124, -179);

  const texture = new THREE.CanvasTexture(canvas);
  texture.flipY = false;
  texture.needsUpdate = true;

  return texture;
};

and this is my main canvas which is connected to shaders and model


Solution

  • I found the solution gusy and i wanted to share
    so currently when the state updated with the new image i am loading all of them as async and using the image i need in temp canvas in that way there is no flickering and all svgs are working thx for the help

    const loadImage = (src) =>
      new Promise((res) => {
        const img = new Image();
        img.src = src;
        img.onload = () => res(img);
      });
    
    
      const [deleteIcon, rotateIcon, duplicateIcon, resizeIcon, ...images] =
        await Promise.all([
          loadImage("/ActionButtonDelete.svg"),
          loadImage("/ActionButtonRotate.svg"),
          loadImage("/ActionButtonDuplicate.svg"),
          loadImage("/ActionButtonScale.svg"),
          ...itemsOnTshirt.map((item) =>
            item.type === "Sticker" || item.type === "Image" || item.texture
              ? loadImage(item.texture)
              : Promise.resolve(null)
          ),
        ]);
    
     itemsOnTshirt.forEach((item, i) => {
        const key = item.type === "Text" ? item.textType : item.type;
        const isClicked = item?.isClicked === true;
    
        switch (key) {
          case "Sticker":
            drawSticker(
              ctx,
              item,
              images[i],
              width,
              height,
              isClicked,
              deleteIcon,
              rotateIcon,
              duplicateIcon,
              resizeIcon,
              uvConditions
            );
            break;