【问题标题】:Rendering svg images on a canvas在画布上渲染 svg 图像
【发布时间】:2016-07-13 14:58:08
【问题描述】:

我正在尝试在画布上渲染 SVG 图像,一次绘制一个图像以填充给定的行,下面是相同的代码 sn-p:

  function createSVGUrl(svg) {
    var svgBlob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'});
    return DOMURL.createObjectURL(svgBlob);
  };

/**
   * Renders svg tile on the given context.
   * @param {CanvasRenderingContext2D} ctx
   * @param {SVGElement} svg The svg tile.
   * @param {{x: number, y:number}} pos The position to draw the svg tile on.
   * @throws Error
   */
  function renderSVGTile(ctx, svg, pos) {
    var img = new Image();
    var url = createSVGUrl(svg);
    img.onload = function() {
      try {
        ctx.drawImage(img, pos.x, pos.y);
        ctx.imageSmoothingEnabled = false;
        ctx.mozImageSmoothingEnabled = false;
        DOMURL.revokeObjectURL(url);
      } catch (e) {
        throw new Error('Could not render image' + e);
      }
    };
    img.src = url;
  };

问题是我可以看到我不想要的部分填充的行,有没有办法一次填充整行?

【问题讨论】:

  • 等待所有图像都已预加载,然后绘制它。顺便说一句,调用drawImage之前应该设置imageSmoothingEnabled,避免循环设置。
  • @Kaiido 不完全是我尝试了建议的解决方案,但在我的情况下它不起作用,可能是由于每行的图像数量非常多。理论上它应该可以工作,但它没有,here 你可以看到这个例子。
  • o_o 为什么要通过 svg ?你知道canvas有一些绘图方法吗?你的代码会更简单。
  • @Kaiido 因为我们开发人员遇到的同样问题是的,这是要求。

标签: javascript html canvas svg web


【解决方案1】:

是的。首先将整行图块绘制到屏幕外的画布上。完成后,您可以将该屏幕外画布绘制到主画布上。

类似:

var offscreenCanvas = document.createElement('canvas');
offscreenCanvas .width = <whatever>;
offscreenCanvas .height = <whatever>;
var offscreenContext = offscreenCanvas.getContext('2d');

// Draw all your tiles
renderSVGTile(offscreenContext, svg, pos);
//... loop through all tiles etc

// When finished...
mainCanvasContext.drawImage(offscreenCanvas, 0, 0);

演示:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var image = document.getElementById("source");

var offscreenCanvas = document.createElement("canvas");
offscreenCanvas.width = 300;
offscreenCanvas.height = 150;
var offscreenContext = offscreenCanvas.getContext("2d");

offscreenContext.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);
offscreenContext.drawImage(image, 33, 71, 104, 124, 108, 20, 87, 104);

ctx.drawImage(offscreenCanvas, 0, 0);
<canvas id="canvas"></canvas>
<div style="display:none;">
  <img id="source" src="http://placekitten.com/300/227"
       width="300" height="227">
</div>

【讨论】:

  • 你试过这个吗,因为我以前试过这个但不成功。
  • 添加了工作演示(尽管是普通的位图图像)。在 Chrome 和 Firefox 中测试。
  • LGTM!我自己试试看。
  • 由于图像的缓存(以及您使用单个图像的事实)OP的方法是异步的,这将不起作用,并且仅在您的测试中起作用。他必须预先加载他的图像。
  • 是的,凯多是对的。我假设图像已经被缓存。我的示例更多地与演示如何一次渲染一行有关。这是 OP 的原始请求。
【解决方案2】:

我就是这样做的,虽然我不确定它是否符合您的目的,因为我在渲染时在文档中有图像,它只是被隐藏了。

var ctx = document.getElementById("canvasID").getContext("2d");
ctx.drawImage(document.getElementById("imageID"), x,y,w,h);

【讨论】:

  • 不,svg 是动态生成的。
  • 那恐怕不行,除非你愿意将动态生成的 SVG 文件添加到文档中的图像中,这样就可以了。我不确定 ctx.drawImage 是否会接受非元素图像,您可以尝试,因为您使用的是相同的方法。此外,您实际上不必将元素附加到页面,只要它被创建和引用即可。
【解决方案3】:

下面的解决方案对我来说很好用:

 /**
   * @param {CanvasRenderingContext2D} ctx
   * @param {!Array<!SVGTile>} tiles
   */
  function drawTiles(ctx, tiles) {
    var canvas  = document.createElement('canvas');
    var width   = tiles.length * TILE_WIDTH;
    var height  = TILE_HEIGHT;
    var y = tiles[0].y;
    canvas.width  = tiles.length * TILE_WIDTH;
    canvas.height = TILE_HEIGHT;
    var context = canvas.getContext("2d");
    tiles.forEach(function(tile, index) {
      renderTile(context, tile, function() {
        if (tiles.length === index + 1) {
          ctx.drawImage(canvas, 0, y);
        }
      });
    });
    // TODO: Below code is for testing purpose.
    var temp = document.createElement('div');
    temp.appendChild(canvas);
    document.body.appendChild(temp);
  };


  /**
   * Renders svg tile on the given context.
   * @param {CanvasRenderingContext2D} ctx
   * @param {!Tile} tile The tile to render.
   * @param {function()} callback To be called after image is loaded.
   * @throws Error
   */
  function renderTile(ctx, tile, callback) {
    var img = new Image();
    img.onload = function() {
      try {
        ctx.drawImage(this, tile.x, 0, tile.width, tile.height);
        ctx.imageSmoothingEnabled = false;
        ctx.mozImageSmoothingEnabled = false;
        DOMURL.revokeObjectURL(tile.svgURL);
        callback();
      } catch (e) {
        throw new Error('Could not render image' + e);
      }
    };
    img.src = tile.svgURL;
  };

【讨论】: