Automattic / node-canvas

Node canvas is a Cairo backed Canvas implementation for NodeJS.
10.2k stars 1.17k forks source link

SVGs with small dimensions are rendered pixelated #1555

Open kayahr opened 4 years ago

kayahr commented 4 years ago

Issue or Feature

node-canvas renders SVGs with small dimensions very pixelated while browsers (Tested with Chrome and Firefox) do not care about the SVG size and renders a high quality image. Maybe because node-canvas rasterizes the image on load (and when width/height properties are changed) while browsers rasterizes the image on draw when the actual output size is known?

This issue is related to https://github.com/Automattic/node-canvas/issues/957 and maybe also to https://github.com/Automattic/node-canvas/issues/1474.

Steps to Reproduce

test.svg

<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
  <path d="M3.4,9.3l1.1,-2.9a1.5,1.5 0,1,1 0.9,0l1.1,2.9a4.5,4.5 0,1,0 -3.1,0z" 
    stroke="#142" stroke-width="0.2" fill="#4a5"/>
</svg>

test.js

const path = require("path");
const fs = require("fs");
const { createCanvas, loadImage } = require("canvas")

const canvas = createCanvas(100, 100);
const context = canvas.getContext("2d");
context.imageSmoothingEnabled = false;

loadImage("test.svg").then(image => {
    // The following line (which isn't needed in browsers) fixes the problem 
    // with node-canvas:
    //image.width = image.height = 100;

    context.drawImage(image, 0, 0, 100, 100);
    const buf = canvas.toBuffer();
    fs.writeFileSync("test.png", buf);
});

test.html

<!DOCTYPE html>
<html>
  <body>
    <canvas width="100" height="100"></canvas>
    <script>

    const canvas = document.querySelector("canvas");
    const context = canvas.getContext("2d");
    context.imageSmoothingEnabled = false;

    const image = new Image();
    image.onload = () => {
        context.drawImage(image, 0, 0, 100, 100);
    };
    image.src = "test.svg";

    </script>
  </body>
</html>

Your Environment

yisibl commented 3 years ago

Please try resvg-js

flohall commented 1 year ago

Alternative solution without using resvg-js.

The following code has two advantages:

It fixes rendering issues in firefox (html canvas) and also it solves the pixelated issue that occurs using librsvg (which is used in node-canvas to render svgs). Important is that the width and height are the final size of your image on the canvas.

 /**
   * Returns SVG data with width and height in the root element.
   * Don't change anything inside this method without intense performance testing.
   */
  private static addSizeToRootSvgElement(svgData: string, width: number, height: number): string {
      return svgData.replace(`<svg `, `<svg width="${width}" height="${height}" `);
  }

By the way librsvg has many different issues with rendering svgs like some elements not being rendered at all. A way to work around those issues it to have a an up to date librsvg (version 2.54.4 solved all the issues I had). To get this version of librsvg you can check here the version delivered by your OS https://repology.org/project/librsvg/versions, so chose the right OS, install canvas dependencies and the run npm i --build-from-source to install canvas from source. (you might have to delete node_modules and or package-lock.json beforehand).

kostia1st commented 3 months ago
 /**
   * Returns SVG data with width and height in the root element.
   * Don't change anything inside this method without intense performance testing.
   */
  private static addSizeToRootSvgElement(svgData: string, width: number, height: number): string {
      return svgData.replace(`<svg `, `<svg width="${width}" height="${height}" `);
  }

From my testing, the above solution (when applied to node-canvas) is basically the same as

  const image = await loadImage(Buffer.from(logoSvg, 'utf-8'));
  image.width = 100;
  image.height = 100;

Same output, same rendering performance. The difference is just that the latter is a bit more straightforward.

Also I noticed that to reduce pixelation it's better to multiply the width/height values by a factor of 10 - then the output is more or less crisp. However the rendering performance degrades significantly.