tvoirand
tvoirand

Reputation: 440

Downloading canvas image using toBlob

I am attempting to download a large canvas image (several thousand pixels height and width) on the click of a button using toBlob in the following code, which doesn't seem to work:

document.getElementById("download_button").onclick = function() {

  var link = document.createElement("a");
  link.download = "image.png";

  canvas.toBlob(function(blob){
    link.href = URL.createObjectURL(blob);
    console.log(blob);
  },'image/png');

  console.log(link.href);
  link.click();

}

console.log(blob) in the callback function returns: Blob {size: 64452, type: "image/png"}

But console.log(link.href) returns nothing.

Am I not using .createObjectURL correctly?

I used to work with toDataURL, but it stopped working above a certain canvas size. And this post canvas.toDataURL() download size limit suggested to try toBlob.

Upvotes: 26

Views: 53916

Answers (3)

herrstrietzel
herrstrietzel

Reputation: 17334

You might also create a custom prototype to get an equivalent to canvas.toDataURL()

HTMLCanvasElement.prototype.toObjectURL = async function(
  mimeType = "image/jpeg",
  quality = 0.85
) {
  return new Promise((resolve, reject) => {
    this.toBlob(
      (blob) => {
        if (!blob) {
          reject("Error creating blob");
          return;
        }

        const blobUrl = URL.createObjectURL(blob);
        resolve(blobUrl);
      },
      mimeType,
      quality
    );
  });
};
<canvas id="canvas" width="200" height="200"></canvas>
<img id="imgBlob" src="" alt="">
<img id="imgUrl" src="" alt="">

<p><a id="linkObjectUrl" href="" download="image.jpg">Download img (object URL)</a> | <a id="linkDataUrl" href="" download="image.webp">Download img (data URL)</a></p>

<script>
  // example canvas
  window.addEventListener('DOMContentLoaded', async(e) => {

    // draw example on canvas
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.src = "https://picsum.photos/id/237/200/200";
    await img.decode();
    ctx.drawImage(img, 0, 0);

    // get object URL
    let quality = 0.5;
    let objectUrl = await canvas.toObjectURL("image/jpeg", quality);
    let dataUrl = canvas.toDataURL("image/webp", quality);

    // set object URLs
    linkObjectUrl.href = objectUrl;
    linkDataUrl.href = dataUrl;

    // apply to image elements
    imgBlob.src = objectUrl;
    imgUrl.src = dataUrl;

  })
</script>

Usage

The method needs to be called in a async function.

let objectUrl = await canvas.toObjectURL("image/jpeg", quality);

Download links don't work in SO snippets.
See also codepen example

Upvotes: 1

s.meijer
s.meijer

Reputation: 3908

My solution to the problem:

async function getImage({
  canvas,
  width,
  height,
  mime = 'image/jpeg',
  quality = 0.8,
}) {
  return new Promise(resolve => {
    const tmpCanvas = document.createElement('canvas');
    tmpCanvas.width = width;
    tmpCanvas.height = height;

    const ctx = tmpCanvas.getContext('2d');
    ctx.drawImage(
      canvas,
      0,
      0,
      canvas.width,
      canvas.height,
      0,
      0,
      width,
      height,
    );

    tmpCanvas.toBlob(resolve, mime, quality);
  });
}

const photo = await getImage({ canvas, width: 500, height: 500 });

Upvotes: 13

ymz
ymz

Reputation: 6924

Your code is fine.. just use it at the right time :)

 canvas.toBlob(function(blob){
    link.href = URL.createObjectURL(blob);
    console.log(blob);
    console.log(link.href); // this line should be here
  },'image/png');

Upvotes: 31

Related Questions