【问题标题】:Drawing images to canvas returns InvalidStateError for every new image drawn, then succeeds将图像绘制到画布上为每个绘制的新图像返回 InvalidStateError,然后成功
【发布时间】:2013-11-07 08:54:45
【问题描述】:

情况

我有一个网格状的无序列表,其中包含 144 个(90 像素 x 90 像素)图像 (12x12),可以旋转。我的最终目标是获取 144 个图像网格并将其保存为 1 个图像。

当前解决方案

我目前的解决方案让我遵循以下步骤:

  1. 创建一个图像宽 x 12 宽和图像高 x 12 高的画布。这是为了代表最终产品的形象。
  2. 遍历列表项(图像),从项中提取图像 src 并将其绘制到自己的画布上,即图像大小。
  3. 旋转新的小画布,但其图像已在网格上旋转。
  4. 在当前指针的 x 和 y 处将新的小画布绘制到最终结果画布上。

注意事项

当我循环浏览图像时,我会跟踪一个指针(我当前在画布上的位置)。我通过维护一行和一个列号来做到这一点。它们代表我正在绘制的图像的当前行和列。我使用它们乘以单个图像的宽度和高度,以获得画布上的确切 x 和 y 坐标来绘制下一张图像。

当前问题

当我调用函数来创建、绘制和生成画布的 base64 时,我收到以下错误消息:“InvalidStateError:尝试使用不可用或不再可用的对象。” .如果此错误在 100% 的时间内发生,我会假设它是因为我正在绘制到画布上的图像尚未加载或根本没有加载,但是,我只收到一次此错误我加载的每一个新图像。例如,如果我有一个 144 个图像网格,即 2 个不同的图像,每个图像绘制 72 次,我会收到两次 InvalidStateError,然后第三次调用该函数,它会成功。

当前代码

请记住,这只是测试保存图像的尖峰代码,我知道需要进行一些重构。

generateThumbnail: function(){
  var builder = this,
      canvas = document.createElement('canvas'),
      content,
      row = 0,
      col = 0;

  // width is single image width (90) x number of tiles wide (usually 12)
  canvas.width = 90 * builder.grid[0];
  // height is single image height (90) x number of tiles high (usually 12)
  canvas.height = 90 * builder.grid[1];
  // get 2d context of new canvas
  context = canvas.getContext("2d");

  // loop through all of the images on the grid
  $.each($(".pattern-grid li"), function(i, tile) {
    var $tile = $(tile),
        image = new Image(),
        src = $tile.find("img").attr("src"),
        width,
        height,
        buffer,
        bufferctx,
        x,
        y;

    // set crossOrigin of image to anonymous as these images are loaded via CORS
    image.crossOrigin = "Anonymous";
  
    // increase row number by 1 if it has reached the end of the row and its not the first image being drawn
    if(i % builder.grid[0] == 0 && i != 0){
      row++;
    }
    // Set Column to 0 if it is a new row, otherwise increase column by 1 (unless it is the first image being drawn)
    if(col == builder.grid[0]-1){
      col = 0;
    }else if(i != 0){
      col++;
    }

    // determine if there was no image drawn at this location
    if(src != undefined){
      image.src = src;
      // get the width and height the image, to be used for the small canvas and where to draw it
      width = image.width;
      height = image.height;
      // create a new buffer canvas to draw the image to, this will be used to apply any rotations that may exist
      buffer = document.createElement("canvas");
      //set width and height of the buffer to the current images width and height
      buffer.width = width;
      buffer.height = height;
      bufferctx = buffer.getContext("2d");
      //Determine x and y coordinates to draw the small canvas using row and column numbers
      x = col*width;
      y = row*height;
      //Save current state of buffer canvas
      bufferctx.save();
      //translate and then rotate the buffer canvas by the image's rotation
      bufferctx.translate(width/2, height/2);
      bufferctx.rotate($tile.find("img").data("rotation")*Math.PI/180);
      bufferctx.translate(width/2*-1, height/2*-1);
      //draw image to buffer canvas and restore its context
      bufferctx.drawImage(image, 0, 0);
      bufferctx.restore();
      //draw the buffer canvas to the main canvas at predetermined x and y
      context.drawImage(buffer, x, y, width, height);
    }
  });
  return canvas.toDataURL();
}

【问题讨论】:

  • 您能否使用控制台打印语句来缩小错误发生的范围,或者您是否已经知道错误发生在哪一行?
  • 错误输出不包含行号。我可以创建一个 jsfiddle 来重现该问题以进行测试。
  • 我知道,这是让我对很多游戏机感到沮丧的事情之一。但我的意思是在整个代码中添加一些console.print 语句,尤其是在某些“关键”部分之前和之后,以查看是否可以缩小错误范围。是否有可能在页面完成加载之前调用此代码?
  • 页面加载完成后,我一直在使用控制台调用这个函数。我可以缩小失败的范围。调用"context.drawImage(buffer, x, y, width, height);"时失败。
  • 经过一番研究,我建议使用image.onload 回调。

标签: javascript html canvas


【解决方案1】:

我能够将@abiessu 的建议与 onload 结合使用,并与闭包配对以保存函数的状态。我的解决方案是:

generateThumbnail: function(){
  var builder = this,
      canvas = document.createElement('canvas'),
      content,
      row = 0,
      col = 0;
  // width is single image width (90) x number of tiles wide (usually 12)
  canvas.width = 90 * builder.grid[0];
  // height is single image height (90) x number of tiles high (usually 12)
  canvas.height = 90 * builder.grid[1];
  context = canvas.getContext("2d");
  // loop through all of the images on the grid
  $.each($(".pattern-grid li"), function(i, tile) {
    var $tile = $(tile),
        image = new Image(),
        src = $tile.find("img").attr("src");
     // set crossOrigin of image to anonymous as these images are loaded via CORS
    image.crossOrigin = "Anonymous";
    // increase row number by 1 if it has reached the end of the row and its not the first image being drawn
    if(i % builder.grid[0] == 0 && i != 0){
      row++;
    }
    // increase row number by 1 if it has reached the end of the row and its not the first image being drawn
    if(col == builder.grid[0]-1){
      col = 0;
    }else if(i != 0){
      col++;
    }
    image.onload = function(row, col){
      return function(){
        // determine if there was no image drawn at this location
        if(src != undefined){
          var width = image.width,
              height = image.height,
              buffer = document.createElement("canvas"),
              bufferctx,
              x,
              y;
          buffer.width = width;
          buffer.height = height;
          bufferctx = buffer.getContext("2d");
          x = col*width;
          y = row*height;
          bufferctx.save();
          bufferctx.translate(width/2, height/2);
          bufferctx.rotate($tile.find("img").data("rotation")*Math.PI/180);
          bufferctx.translate(width/2*-1, height/2*-1);
          bufferctx.drawImage(image, 0, 0);
          bufferctx.restore();
          context.drawImage(buffer, x, y, width, height);
        }
      }
    }(row, col);
    image.src = $tile.find("img").attr("src");
  });
  window.canvas = canvas;
}

【讨论】:

  • 它可以工作,但您可能需要重构代码,以免为每个新图像创建缓冲区画布。
  • 我需要为每个新图像创建一个缓冲画布。如前所述,每个图像都放置在缓冲区画布上,以便我可以应用该特定图像存在的任何旋转,然后将该画布放在大画布上。
  • 您可以在主画布上应用您的旋转,而不必为每个图像使用 1 个缓冲画布所需的额外资源,并且还可以重绘每个图像两次。
  • 嗯,我本来是这么想的,但是做不到。因为每个图像可以有不同的旋转。您能否就如何实际做到这一点提供更多反馈?如果我旋转主画布,那不会弄乱已经绘制的图像吗?
  • 你可以使用一个js对象来维护每个图像的状态:x/y,rotation,image-src。当图像完全加载后,您可以使用状态对象以适当的位置和角度绘制图像。您可以使用 drawImage 的扩展调整大小功能以缩略图大小绘制图像。您可以在主画布上使用此编码模式在主上下文中完成所有这些操作:保存、翻译、旋转、drawImage、恢复。这是一个例子:jsfiddle.net/m1erickson/MxRCP
【解决方案2】:

使用image.onload 回调:

image.onload = function(){
  bufferctx.drawImage(image,0,0);
}

更多信息可以从 Mozilla 页面找到,我使用了 this onethis search

【讨论】:

  • 因为我觉得解决方案中肯定会使用 onload ,所以这特别不起作用,因为循环的其余部分将在图像完成其加载之前继续,导致它绘制图像坐标错误。我认为配合闭包来保存函数的状态,我可以使用 onload 来绘制图像。
  • 你是对的,也许onload 机制可以用来暂停主进程,直到该部分完成?或者也许所有的绘图操作都可以放入onload回调中,以便它们都与正确的坐标一起被调用......我想也可以使用image.attachEventListener('load',...),其中...是函数,以便您可以根据需要调用多个函数。
  • 我能够使用这个带有闭包的 onload 建议来创建一个解决方案 - 它可以在上面看到。感谢您的帮助!
猜你喜欢
  • 2023-04-02
  • 1970-01-01
  • 2021-11-23
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
  • 2016-10-18
  • 2014-09-03
  • 2013-11-06
相关资源
最近更新 更多