kit2d2
kit2d2

Reputation: 1

Converting SVG's to be rendered with ctx commands in JS

Lets say I have an svg,

<svg viewBox="188.1601 295.929 33.6399 36" width="33.6399" height="36" xmlns="http://www.w3.org/2000/svg">
  <path fill="#744EAA" d="M 192.8 299.929 C 195.8 299.929 197.8 301.929 200.8 305.929 C 203.8 309.929 208.757 313.12 212.8 313.929 C 217.8 314.929 221.8 318.929 221.8 324.929 C 221.8 329.826 217.954 331.929 212.8 331.929 C 207.8 331.929 203.8 328.929 198.8 323.929 C 193.8 318.929 188.8 309.929 188.8 305.929 C 188.8 301.929 189.8 299.929 192.8 299.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
  <path fill="#77B255" d="M 190.315 295.929 C 191.563 295.929 191.563 297.177 191.563 298.424 C 191.563 300.188 192.811 299.553 194.059 299.553 C 195.305 299.553 197.8 301.929 197.8 301.929 L 194.058 301.929 C 192.81 301.929 194.058 304.543 192.81 304.543 C 191.562 304.543 191.562 303.355 190.315 303.355 C 189.068 303.355 188.8 306.929 188.8 306.929 C 188.8 306.929 187.196 302.776 189.067 300.905 C 190.315 299.657 187.82 295.929 190.315 295.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
</svg>

and I want to convert it to ctx commands so it can be rendered on a 2D Canvas game. Here is an example of what I have:

// body
    ctx.fillStyle = '#ffe763';
    ctx.strokeStyle = '#a29442';
    ctx.beginPath();
    const pos = offset(this.renderX, this.renderY);
    ctx.arc(pos.x, pos.y, this.r, 0, Math.PI * 2);
    ctx.stroke();
    ctx.fill();
    ctx.closePath();

I tried to find an online converter but they just rendered something at the top left of my screen in low quality.

Upvotes: -1

Views: 59

Answers (1)

user3297291
user3297291

Reputation: 23397

To implement a full-blow SVG renderer in canvas is quite some work... But if you're in control of the SVG exports you might be able to get away with a very limited implementation like this one:

  • Use ctx.scale and ctx.translate to implement viewBox. This ensures we can render our canvas at any resolution without having to worry about the unit system of the SVG drawing
  • Use Path2D to construct a path based on the SVG d attribute

I've put some of the limitations of this approach in comments, but just to be sure I'll list them here as well:

  • Only loops over path elements. If you need stuff like <circle />, <rect />, etc. you'll have to implement those yourself.
  • Only accepts fill attributes directly set to path elements.
  • Skips transform on path (al though this one is not hard to add)

All in all I think I would recommend an approach where you render svg images to image data and put that in your canvas instead...

Using image data

const W = 400;
const H = 400;

const svgString = `<svg viewBox="188.1601 295.929 33.6399 36" width="33.6399" height="36" xmlns="http://www.w3.org/2000/svg">
  <path fill="#744EAA" d="M 192.8 299.929 C 195.8 299.929 197.8 301.929 200.8 305.929 C 203.8 309.929 208.757 313.12 212.8 313.929 C 217.8 314.929 221.8 318.929 221.8 324.929 C 221.8 329.826 217.954 331.929 212.8 331.929 C 207.8 331.929 203.8 328.929 198.8 323.929 C 193.8 318.929 188.8 309.929 188.8 305.929 C 188.8 301.929 189.8 299.929 192.8 299.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
  <path fill="#77B255" d="M 190.315 295.929 C 191.563 295.929 191.563 297.177 191.563 298.424 C 191.563 300.188 192.811 299.553 194.059 299.553 C 195.305 299.553 197.8 301.929 197.8 301.929 L 194.058 301.929 C 192.81 301.929 194.058 304.543 192.81 304.543 C 191.562 304.543 191.562 303.355 190.315 303.355 C 189.068 303.355 188.8 306.929 188.8 306.929 C 188.8 306.929 187.196 302.776 189.067 300.905 C 190.315 299.657 187.82 295.929 190.315 295.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
</svg>`;

const img = document.createElement("img");
img.addEventListener("load", e => {
  draw(W, H);
});

img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
document.body.appendChild(img);

const cvs = document.querySelector("canvas");
const ctx = cvs.getContext("2d");


const draw = (width, height) => {
  cvs.width = width;
  cvs.height = height;
  ctx.drawImage(img, 0, 0, width, height);
}
img,
canvas {
  background: #efefef;
}

h2 {
  margin: 0;
}
<h2>Canvas</h2>
<canvas></canvas>
<br/>
<button onclick="draw(100, 100)">100⨯100px</button>
<button onclick="draw(200, 200)">200⨯200px</button>
<button onclick="draw(400, 400)">400⨯400px</button>
<h2>Svg source</h2>

Recreating paths using drawing commands

const W = 400;
const H = 400;

const svg = document.querySelector("svg");
const cvs = document.querySelector("canvas");
const ctx = cvs.getContext("2d");


const draw = (width, height) => {
  // Viewbox
  const { width: vbw, height: vbh, x, y } = svg.viewBox.baseVal;
  cvs.width = width;
  cvs.height = height;

  ctx.scale(width / vbw, height / vbh);
  ctx.translate(-x, -y);


  // Shortcut: only implement <path />
  for (const path of svg.querySelectorAll("path")) {
    // Shortcut: only support direct fill attributes
    // (e.g. no inheritance from parent <g>)
    const fill = path.getAttribute("fill");
    const d = path.getAttribute("d");

      // Shortcut: skip transform attribute
    const p = new Path2D(d);

    ctx.fillStyle = fill;
    ctx.fill(p);
  }
}

draw(W, H);
svg,
canvas {
  background: #efefef;
}

h2 {
  margin: 0;
}
<h2>SVG</h2>
<svg viewBox="188.1601 295.929 33.6399 36" width="33.6399" height="36" xmlns="http://www.w3.org/2000/svg">
  <path fill="#744EAA" d="M 192.8 299.929 C 195.8 299.929 197.8 301.929 200.8 305.929 C 203.8 309.929 208.757 313.12 212.8 313.929 C 217.8 314.929 221.8 318.929 221.8 324.929 C 221.8 329.826 217.954 331.929 212.8 331.929 C 207.8 331.929 203.8 328.929 198.8 323.929 C 193.8 318.929 188.8 309.929 188.8 305.929 C 188.8 301.929 189.8 299.929 192.8 299.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
  <path fill="#77B255" d="M 190.315 295.929 C 191.563 295.929 191.563 297.177 191.563 298.424 C 191.563 300.188 192.811 299.553 194.059 299.553 C 195.305 299.553 197.8 301.929 197.8 301.929 L 194.058 301.929 C 192.81 301.929 194.058 304.543 192.81 304.543 C 191.562 304.543 191.562 303.355 190.315 303.355 C 189.068 303.355 188.8 306.929 188.8 306.929 C 188.8 306.929 187.196 302.776 189.067 300.905 C 190.315 299.657 187.82 295.929 190.315 295.929 Z" transform="matrix(0.9999999999999999, 0, 0, 0.9999999999999999, 0, 0)"/>
</svg>

<h2>Canvas</h2>
<canvas></canvas>
<br/>
<button onclick="draw(100, 100)">100⨯100px</button>
<button onclick="draw(200, 200)">200⨯200px</button>
<button onclick="draw(400, 400)">400⨯400px</button>

Upvotes: 0

Related Questions