第一次使用另一个画布作为源参数时,画布绘制图像速度较慢

Sti*_*cky 5 javascript canvas image drawimage

第一次使用另一个画布作为绘图源时,我看到画布绘制速度很慢。随后的 canvas 到 canvas .drawImage 调用都很好,直到我交换图像(然后我再次看到相同的问题)。

下面的示例代码 - 加载图像,然后创建 4 个画布,第一个画布从图像本身绘制,第二个画布从第一个画布绘制,依此类推。创建画布后,交换源图像并运行代码再次。

        var sourceImage = new Image();  // Original image
        var myImages = []; // Array of image and canvases references
        myImages[0] = sourceImage; // Set first myImage to image source

        // Image onload 
        sourceImage.onload = function () {
     
            console.log("Imageload", new Date() - t0);
            myImages[0] = sourceImage;

            // Loop to create and draw on canvases
            for (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;

                // Draw last canvas / image onto this canvas
                t0 = new Date();
                myImages[i].getContext("2d").drawImage(
                    myImages[i - 1],
                    0,
                    0,
                    myImages[i].width,
                    myImages[i].height
                );
                console.log("drawImage", i,  new Date() - t0); 
                
            }

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

        }

        // Load image
        t0 = new Date();
        sourceImage.src = "images/black.jpg"

Run Code Online (Sandbox Code Playgroud)

控制台输出是...

Imageload 36
drawImage 1 0
drawImage 2 255
drawImage 3 0
drawImage 4 0

Imageload 35
drawImage 1 0
drawImage 2 388
drawImage 3 1
drawImage 4 1
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么第二个画布绘制速度很慢?我尝试过各种图像文件和不同的画布尺寸,但总是看到相同的结果。我已经在 Chrome 和 Safari 上进行了测试。

如果缓慢绘制是在第一个画布上,我可以接受虽然 .onload 已触发,但图像仍然存在一些问题。但速度慢的是第二个画布,即第一个画布是从图像中毫无问题地绘制的。

Kai*_*ido 3

我认为你在这里遇到了一个奇怪的优化怪癖,可能很难对发生的事情有一个明确的答案,但无论如何我会尝试做出一个有根据的猜测。


浏览器似乎在 GPU 实际执行分配给它的作业之前就回到了 CPU 线程,充分利用了任务并行性。

因此,在第一个循环中,GPU 将开始一项工作,要求绘制<img><canvas>图像的数据,即使在 Chrome 中解码,仍然需要传输到 GPU 并转换为实际的位图。
但正如我们所说,这是并行完成的,因此 js 脚本可以在执行此工作时继续并直接继续第二个循环。

然而,当在第二个目标画布上绘制第一个目标画布时,它会发现有一个正在运行的 GPU 作业将修改它,因此会阻塞 CPU 线程,直到第一个绘图完成。

不过,下一次迭代将仅处理<canvas>位图缓冲区已位于 GPU 上的源,因此它们不会花费任何相关时间。

我们可以通过在每次迭代之间等待几毫秒来以某种方式确认这一点。这样做,所有画布到画布的操作将花费大约 0 毫秒。

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 = new Image(); // 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 = async function() {

  console.log("Imageload", new Date() - t0);
  myImages[0] = sourceImage;
  // create canvases before hand to be sure it's not part of the issue
  for (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 canvases
  for (var i = 1; i <= 4; i += 1) {
    // Draw last canvas / image onto this canvas
    t0 = new Date();
    myImages[i].getContext("2d").drawImage(
      myImages[i - 1],
      0,
      0,
      myImages[i].width,
      myImages[i].height
    );
    console.log("drawImage", i, new Date() - t0);
    await new Promise(r => setTimeout(r, 500));

  }

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

};

// Load image
t0 = new Date();
sourceImage.src = url1;
Run Code Online (Sandbox Code Playgroud)

类似地,如果我们确实生成每个源的 ImageBitmap,我们可以看到花费最多时间的一个如预期的那样<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 = new Image(); // 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 = async function() {

  console.log("Imageload", new Date() - t0);
  myImages[0] = sourceImage;
  // create canvases beforehand to be sure it's not part of the issue
  for (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 canvases
  for (var i = 1; i <= 4; i += 1) {
    // wait for create ImageBitmap to be created
    t0 = new Date();
    const img = await createImageBitmap(myImages[i - 1]);
    console.log("createImageBitmap", i, new Date() - t0);

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

  }

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

};

// Load image
t0 = new Date();
sourceImage.src = url1;
Run Code Online (Sandbox Code Playgroud)


PS:人们可能会想要强制getImageData同步返回 CPU 线程,但这样做时我们还会在 CPU 和 GPU 之间来回传输所有画布位图,从而在每个循环中实际上产生相同的缓慢速度。