You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Canvas上无损绘制SVG并还原其原生渲染效果?

将SVG绘制到Canvas时能否还原原生渲染外观?

我有一个包含大量小三角形的SVG,已经设置了shape-rendering="crisp-edges"来避免图形间出现可见间隙,原生SVG渲染完全正常,没有瑕疵。但将它绘制到Canvas后,出现了大量间隙,且这个问题和Canvas分辨率无关。

原始转换代码

async function svg_to_canvas(svg_el, canvas) {
  // svg to blob URL
  const serializer = new XMLSerializer();
  const source = serializer.serializeToString(svg_el);
  const svg_blob = new Blob([source], {
    type: "image/svg+xml;charset=utf-8"
  });
  const svg_url = URL.createObjectURL(svg_blob);
  // convert blob URL to image
  const img = new Image();
  await new Promise((resolve, reject) => {
    img.onload = () => resolve(img);
    img.onerror = (err) => reject(err);
    img.src = svg_url;
  })
  // then paint on canvas
  canvas.setAttribute('width', svg_el.getAttribute("width"));
  canvas.setAttribute('height', svg_el.getAttribute("height"));
  const context = canvas.getContext('2d');
  context.imageSmoothingQuality = "high";
  context.drawImage(img, 0, 0, canvas.width, canvas.height);
}

问题表现

  • Windows 11系统下的Firefox浏览器:输出结果出现意外的水平可见间隙
  • Edge浏览器:出现不同的渲染瑕疵,似乎完全忽略了shape-rendering属性

考虑devicePixelRatio的代码示例

以下是适配设备像素比的代码,可用于测试不同缩放级别或DPI比例下的表现:

JavaScript代码

async function svg_to_canvas(svg_el, canvas) {
  // svg to blob URL
  const serializer = new XMLSerializer();
  const source = serializer.serializeToString(svg_el);
  const svg_blob = new Blob([source], {
    type: "image/svg+xml;charset=utf-8"
  });
  const svg_url = URL.createObjectURL(svg_blob);
  // convert blob URL to image
  const img = new Image();
  await new Promise((resolve, reject) => {
    img.onload = () => resolve(img);
    img.onerror = (err) => reject(err);
    img.src = svg_url;
  })
  // then paint on canvas
  const ratio = window.devicePixelRatio;
  canvas.setAttribute('width', svg_el.getAttribute("width") * ratio);
  canvas.setAttribute('height', svg_el.getAttribute("height") * ratio);
  canvas.style.width = svg_el.getAttribute("width") + "px";
  const context = canvas.getContext('2d');
  context.imageSmoothingQuality = "high";
  context.drawImage(img, 0, 0, canvas.width, canvas.height);
}

svg_to_canvas(document.getElementById("test-svg"), document.getElementById('test-canvas'));

HTML代码

<p>SVG:</p>
<!-- 包含大量三角形的SVG,填充为一个大三角形区域 -->
<svg id="test-svg" width="400" height="200" style="display: block; outline: 1px solid silver;">
<rect x="5.5" y="5.5" width="99" height="99" fill="#ddd" stroke="black"/>
<g shape-rendering="crispEdges"><path d="M 56.0 18.0 L 47.2 33.2 L 64.8 33.2 Z" fill="rgb(237.41034097107058, 65.8077882928761, 65.8077882928761)" stroke-width="none" stroke="none"></path><path d="M 47.2 33.2 L 64.8 33.2 L 56.0 48.4 Z" fill="rgb(218.40072063744432, 93.07557473646084, 93.07557473646084)" stroke-width="none" stroke="none"></path><path d="M 64.8 33.2 L 56.0 48.4 L 73.6 48.4 Z" fill="rgb(208.22744775845476, 65.8077882928761, 131.664972562941)" stroke-width="none" stroke="none"></path><path d="M 56.0 48.4 L 73.6 48.4 L 64.8 63.6 Z" fill="rgb(186.25593481725875, 93.07557473646082, 147.20959252992503)" stroke-width="none" stroke="none"></path><path d="M 73.6 48.4 L 64.8 63.6 L 82.3 63.6 Z" fill="rgb(174.22304669589496, 65.8077882928761, 174.18571985096827)" stroke-width="none" stroke="none"></path><path d="M 64.8 63.6 L 82.3 63.6 L 73.6 78.8 Z" fill="rgb(147.25376645215857, 93.07557473646084, 186.2210129204828)" stroke-width="none" stroke="none"></path><path d="M 82.3 63.6 L 73.6 78.8 L 91.1 78.8 Z" fill="rgb(131.71435001547857, 65.8077882928761, 208.19621754489202)" stroke-width="none" stroke="none"></path><path d="M 73.6 78.8 L 91.1 78.8 L 82.3 94.0 Z" fill="rgb(93.14542508380538, 93.07557473646084, 218.37093939770165)" stroke-width="none" stroke="none"></path><path d="M 91.1 78.8 L 82.3 94.0 L 99.9 94.0 Z" fill="rgb(65.9065247149324, 65.8077882928761, 237.3829501038354)" stroke-width="none" stroke="none"></path><path d="M 47.2 33.2 L 38.4 48.4 L 56.0 48.4 Z" fill="rgb(208.22744775845476, 131.664972562941, 65.8077882928761)" stroke-width="none" stroke="none"></path><path d="M 38.4 48.4 L 56.0 48.4 L 47.2 63.6 Z" fill="rgb(186.25593481725875, 147.20959252992503, 93.07557473646082)" stroke-width="none" stroke="none"></path><path d="M 56.0 48.4 L 47.2 63.6 L 64.8 63.6 Z" fill="rgb(174.223046695895, 131.664972562941, 131.664972562941)" stroke-width="none" stroke="none"></path><path d="M 47.2 63.6 L 64.8 63.6 L 56.0 78.8 Z" fill="rgb(147.2537664521586, 147.20959252992503, 147.20959252992503)" stroke-width="none" stroke="none"></path><path d="M 64.8 63.6 L 56.0 78.8 L 73.6 78.8 Z" fill="rgb(131.7143500154786, 131.664972562941, 174.18571985096827)" stroke-width="none" stroke="none"></path><path d="M 56.0 78.8 L 73.6 78.8 L 64.8 94.0 Z" fill="rgb(93.14542508380542, 147.20959252992506, 186.2210129204828)" stroke-width="none" stroke="none"></path><path d="M 73.6 78.8 L 64.8 94.0 L 82.3 94.0 Z" fill="rgb(65.9065247149324, 131.664972562941, 208.19621754489202)" stroke-width="none" stroke="none"></path><path d="M 38.4 48.4 L 29.7 63.6 L 47.2 63.6 Z" fill="rgb(174.22304669589496, 174.18571985096827, 65.8077882928761)" stroke-width="none" stroke="none"></path><path d="M 29.7 63.6 L 47.2 63.6 L 38.4 78.8 Z" fill="rgb(147.25376645215857, 186.2210129204828, 93.07557473646084)" stroke-width="none" stroke="none"></path><path d="M 47.2 63.6 L 38.4 78.8 L 56.0 78.8 Z" fill="rgb(131.71435001547857, 174.18571985096827, 131.664972562941)" stroke-width="none" stroke="none"></path><path d="M 38.4 78.8 L 56.0 78.8 L 47.2 94.0 Z" fill="rgb(93.1454250838054, 186.2210129204828, 147.20959252992506)" stroke-width="none" stroke="none"></path><path d="M 56.0 78.8 L 47.2 94.0 L 64.8 94.0 Z" fill="rgb(65.9065247149324, 174.18571985096827, 174.18571985096827)" stroke-width="none" stroke="none"></path><path d="M 29.7 63.6 L 20.9 78.8 L 38.4 78.8 Z" fill="rgb(131.71435001547857, 208.19621754489202, 65.8077882928761)" stroke-width="none" stroke="none"></path><path d="M 20.9 78.8 L 38.4 78.8 L 29.7 94.0 Z" fill="rgb(93.14542508380538, 218.37093939770165, 93.07557473646084)" stroke-width="none" stroke="none"></path><path d="M 38.4 78.8 L 29.7 94.0 L 47.2 94.0 Z" fill="rgb(65.90652471493237, 208.19621754489202, 131.664972562941)" stroke-width="none" stroke="none"></path><path d="M 20.9 78.8 L 12.1 94.0 L 29.7 94.0 Z" fill="rgb(65.9065247149324, 237.3829501038354, 65.8077882928761)" stroke-width="none" stroke="none"></path></g>
</svg>

<p>Canvas:</p>
<canvas id="test-canvas" style="display: block; margin-bottom: 1em; outline: 1px solid silver;"></canvas>

内容的提问来源于stack exchange,提问作者roeland

火山引擎 最新活动