javascript - Drawing images to canvas returns InvalidStateError for every new image drawn, then succeeds - Stack Overflow

SituationI have a grid-like unordered list with 144 (90px x 90px) images (12x12) that may be rotated.

Situation

I have a grid-like unordered list with 144 (90px x 90px) images (12x12) that may be rotated. My end goal is to take the 144 image grid and save it as 1 image.

Current Solution

My current solution has me following these steps:

  1. Create a canvas that is one image width x 12 wide and one image height x 12 high. This is to represent the end product image.
  2. Loop through the list items(images), extracting the image src from the item and drawing it onto its own canvas that is the size of the image.
  3. Rotate the new small canvas however it's image has been rotated on the grid.
  4. Draw the new small canvas onto the end-result canvas at the x and y of the current pointer.

Things to note

As I loop through the images, I keep track of a pointer (where I am currently on the canvas). I do this by maintaining a row and a col number. They represent the current row and column of images I am drawing. I use them, multiplied by the width and height of a single image, to get the exact x and y coordinates on the canvas to draw the next image.

Current Problem

When I call the function to create, draw and generate the base64 of the canvas, I receive the following error message: "InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable.". If this error was occurring 100% of the time, I'd assume its because the image I'm drawing to the canvas, either isn't loaded yet or is not being loaded at all, but, I only receive this error once for every new image I load. For example, if I have a 144 image grid, that is 2 different images each drawn 72 times, I will receive InvalidStateError twice and then the third time I call the function, it will succeed.

Current Code

Please keep in mind this is simply spike code to test saving the image, I am aware some refactoring is required.

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();
}

Situation

I have a grid-like unordered list with 144 (90px x 90px) images (12x12) that may be rotated. My end goal is to take the 144 image grid and save it as 1 image.

Current Solution

My current solution has me following these steps:

  1. Create a canvas that is one image width x 12 wide and one image height x 12 high. This is to represent the end product image.
  2. Loop through the list items(images), extracting the image src from the item and drawing it onto its own canvas that is the size of the image.
  3. Rotate the new small canvas however it's image has been rotated on the grid.
  4. Draw the new small canvas onto the end-result canvas at the x and y of the current pointer.

Things to note

As I loop through the images, I keep track of a pointer (where I am currently on the canvas). I do this by maintaining a row and a col number. They represent the current row and column of images I am drawing. I use them, multiplied by the width and height of a single image, to get the exact x and y coordinates on the canvas to draw the next image.

Current Problem

When I call the function to create, draw and generate the base64 of the canvas, I receive the following error message: "InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable.". If this error was occurring 100% of the time, I'd assume its because the image I'm drawing to the canvas, either isn't loaded yet or is not being loaded at all, but, I only receive this error once for every new image I load. For example, if I have a 144 image grid, that is 2 different images each drawn 72 times, I will receive InvalidStateError twice and then the third time I call the function, it will succeed.

Current Code

Please keep in mind this is simply spike code to test saving the image, I am aware some refactoring is required.

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();
}
Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Oct 28, 2013 at 14:21 Bryan AshleyBryan Ashley 9717 silver badges22 bronze badges 6
  • Can you use console print statements to narrow down where the error occurs, or do you already know which line it occurs at? – abiessu Commented Oct 28, 2013 at 14:32
  • The error output does not include a line number. I can create a jsfiddle to reproduce the issue for testing purposes. – Bryan Ashley Commented Oct 28, 2013 at 14:38
  • I know, it's one of the things that frustrates me with a lot of consoles. But I meant to put some console.print statements sprinkled throughout the code especially before and after certain "critical" sections to see if the error can be narrowed down. Is it possible that this code is invoked before the page has finished loading? – abiessu Commented Oct 28, 2013 at 14:40
  • I have been using the console to call this function well after the page has loaded. I can narrow down where it fails. It fails when calling "context.drawImage(buffer, x, y, width, height);". – Bryan Ashley Commented Oct 28, 2013 at 14:42
  • After some research, I'm going to suggest that an image.onload callback be used. – abiessu Commented Oct 28, 2013 at 14:48
 |  Show 1 more ment

2 Answers 2

Reset to default 4

I was able to use @abiessu's suggestion with an onload, paired with a closure to save state of the function. My solution, that works, is:

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;
}

Use an image.onload callback:

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

More information can be found from the Mozilla pages, I used this one and this search.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744789748a4593841.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信