Skip to content Skip to sidebar Skip to footer

Canvas Drawimage Slow First Time Another Canvas Is Used As The Source Argument

I am seeing slow canvas drawing the first time I use another canvas as the drawing source. Subsequent canvas to canvas .drawImage calls are fine until I swap images (and then I see

Solution 1:

I think you just hit a weird optimization quirk here and it might be quite hard to have a definitive answer as to what happens, but I'll try to make an educated guess anyway.


It seems the browser comes back to the CPU thread before the GPU actually has executed the job it was assigned, taking full advantage of tasks parallelism.

So in the first loop, the GPU will start a job asking to draw the <img> to the <canvas>, this image's data which even though is decoded in Chrome, still has to be transfered to the GPU and converted to an actual bitmap. But as we said, this is done in parallel and thus the js script can continue and proceed with the second loop directly while doing this job.

However when it comes at drawing the first target canvas on the second, it will see that there is one running GPU job that will modify it, and will thus block the CPU thread until the first drawing has completed.

The next iterations though will only deal with <canvas> sources which bitmap buffers are already on the GPU, so they won't take any relevant time.

We can somehow confirm this by simply waiting a few ms between each iterations. Doing so, all canvas-to-canvas operations will take about 0ms.

var url1 = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?bar" + Math.random();
var url2 = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?foo" + Math.random();

var sourceImage = newImage(); // Original image
sourceImage.crossOrigin = true;
var myImages = []; // Array of image and canvases references
myImages[0] = sourceImage; // Set first myImage to image source// Image onload 
sourceImage.onload = asyncfunction() {

  console.log("Imageload", newDate() - t0);
  myImages[0] = sourceImage;
  // create canvases before hand to be sure it's not part of the issuefor (var i = 1; i <= 4; i += 1) {
    // Create canvas
    myImages[i] = document.createElement("canvas");

    // Set canvas dimensions to same as original image
    myImages[i].width = myImages[0].width;
    myImages[i].height = myImages[0].height;
    myImages[i].getContext("2d");
  }

  // Loop to create and draw on canvasesfor (var i = 1; i <= 4; i += 1) {
    // Draw last canvas / image onto this canvas
    t0 = newDate();
    myImages[i].getContext("2d").drawImage(
      myImages[i - 1],
      0,
      0,
      myImages[i].width,
      myImages[i].height
    );
    console.log("drawImage", i, newDate() - t0);
    awaitnewPromise(r =>setTimeout(r, 500));

  }

  // Finished with black.jpg so load white.jpg           if (sourceImage.getAttribute("src") == url1) {
    sourceImage.src = url2
  }

};

// Load image
t0 = newDate();
sourceImage.src = url1;

Similarly, if we do generate ImageBitmaps of each source, we can see that the one taking the most time is as expected the <img>:

var url1 = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?bar" + Math.random();
var url2 = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?foo" + Math.random();

var sourceImage = newImage(); // Original image
sourceImage.crossOrigin = true;
var myImages = []; // Array of image and canvases references
myImages[0] = sourceImage; // Set first myImage to image source// Image onload 
sourceImage.onload = asyncfunction() {

  console.log("Imageload", newDate() - t0);
  myImages[0] = sourceImage;
  // create canvases beforehand to be sure it's not part of the issuefor (var i = 1; i <= 4; i += 1) {
    // Create canvas
    myImages[i] = document.createElement("canvas");

    // Set canvas dimensions to same as original image
    myImages[i].width = myImages[0].width;
    myImages[i].height = myImages[0].height;
    myImages[i].getContext("2d");
  }

  // Loop to create and draw on canvasesfor (var i = 1; i <= 4; i += 1) {
    // wait for create ImageBitmap to be created
    t0 = newDate();
    const img = awaitcreateImageBitmap(myImages[i - 1]);
    console.log("createImageBitmap", i, newDate() - t0);

    t0 = newDate();
    myImages[i].getContext("2d").drawImage(
      img,
      0,
      0,
      myImages[i].width,
      myImages[i].height
    );
    console.log("drawImage", i, newDate() - t0);

  }

  // Finished with black.jpg so load white.jpg           if (sourceImage.getAttribute("src") == url1) {
    sourceImage.src = url2
  }

};

// Load image
t0 = newDate();
sourceImage.src = url1;

Ps: one might be tempted to call getImageData to force coming back to the CPU thread synchronously, but doing so we also transfer all the canvases bitmaps back and forth between CPU and GPU, creating actually the same slowness at every loops.

Post a Comment for "Canvas Drawimage Slow First Time Another Canvas Is Used As The Source Argument"