canvas.toBlob 有时给出 null

canvas.toBlob giving null sometimes

我正在设置一个 canvas,然后将图像绘制到 canvas 中,然后调用 canvas.toBlob(function(blob)...),但我在 [=15= 中找到了 blob 参数] 有时是 null.

为什么会这样?我应该在 drawImage 之后等待什么(即使在代码片段中 - 你可以看到我在继续之前等待图像加载)?

//--------------------------------------------------
function doImageFileInsert(fileinput) {
  var newImg = document.createElement('img');

  var img = fileinput.files[0];
  let reader = new FileReader();
  reader.onload = (e) => {
    let base64 = e.target.result;
    newImg.src = base64;
    doTest(newImg);
  };
  reader.readAsDataURL(img);

  fileinput.value = ''; // reset ready for another file
}
//--------------------------------------------------
function doTest(imgElem) {
  console.log('doTest');
  var canvas = document.createElement("canvas");
  var w = imgElem.width;
  var h = imgElem.height;
  canvas.width = w;
  canvas.height = h;
  var ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(imgElem, 0, 0, w, h);

  canvas.toBlob(function(blob) {
    if (blob) {
      console.log('blob is good');
    } else {
      console.log('blob is null');
      alert('blob is null');
    }
  }, 'image/jpeg', 0.9);
}
canvas, div {
    border:solid 1px grey;
    padding:10px;
    margin:5px;
    border-radius:9px;
}
img {
    width:100%;
    height:auto;
}
<input type='file' value='Add' onChange='doImageFileInsert(this)'>

也可在 https://jsfiddle.net/Abeeee/rtwcge5h/24/ 获得。

如果您通过“选择文件”按钮添加图像的次数足够多,就会遇到问题 (alert('blob is null'))。

好吧,问题似乎出在 canvas 工作的调用者身上 - 事实上,在 drawImage(可能)为 运行 时图像还没有完全加载.

最初对 doTest() 的调用是

function doImageFileInsert(fileinput) {
    var contenteditable = document.getElementById('mydiv');
    var newImg = document.createElement('img');
    //contenteditable.appendChild(newImg);


    var img = fileinput.files[0];
    let reader = new FileReader();
    reader.onload = (e) => {
      let base64 = e.target.result;
      newImg.src = base64;
      doTest(newImg);  <-----
    };
    reader.readAsDataURL(img);
 }

但错误的是调用

改为

function doImageFileInsert(fileinput) {
    var contenteditable = document.getElementById('mydiv');
    var newImg = document.createElement('img');
    //contenteditable.appendChild(newImg);


    var img = fileinput.files[0];
    let reader = new FileReader();
    reader.onload = (e) => {
      let base64 = e.target.result;
      newImg.src = base64;
      //doTest(newImg);  
      newImg.onload = (e) => { doTest(newImg); };   <-----
    };
    reader.readAsDataURL(img);
 }

似乎已解决。可在 https://jsfiddle.net/Abeeee/rtwcge5h/25/

中查看工作版本

如果您想使用,这里有一个更现代的方法

/**
 * @param {HTMLInputElement} fileInput 
 */
async function doImageFileInsert (fileInput) {
  const img = new Image()
  img.src = URL.createObjectURL(fileInput.files[0])
  await img.decode()
  doTest(img)
}

或者这个不适用于所有浏览器(需要 polyfill)

/**
 * @param {HTMLInputElement} fileInput 
 */
function doImageFileInsert (fileInput) {
  createImageBitmap(fileInput.files[0]).then(doTest)
}

FileReader 现在是一种遗留模式,blob 本身有新的基于 promise 的读取方法,您也可以使用 URL.createObjectURL(file) 而不是浪费时间将文件编码为 base64 url(只是被 <img> 再次解码回二进制)这是浪费时间、处理和 RAM。

toBlob 会产生 null 的原因只有几个:

  • 浏览器编码器中的错误(我自己从未见过)。
  • 面积大于the maximum supported by the UA的canvas。
  • 宽度或高度为0的canvas。

由于您没有等待图像加载,它的 widthheight 属性仍然是 0,并且您属于上面的第三个项目符号,因为您确实设置了canvas 的尺码。


因此,要修复您的错误,请等待图像加载后再对其进行任何操作。
另请注意,您应该 FileReader.readAsDataURL(), and certainly not to display media files from a Blob, instead generate a blob:// URI from these Blobs using URL.createObjectURL().

但在您的情况下,您甚至可以使用更好的 createImageBitmap API which will take care of loading the image in your Blob and will generate a memory friendly ImageBitmap 对象,它已准备好由 GPU 绘制,无需任何更多计算。
只有 Safari 还没有实现这个 API 的基础,但他们应该很快(它暴露在标志后面,在 TP 版本中没有标记),并且 I wrote a polyfill 你可以用来修复各种实现中的漏洞.

const input = document.querySelector("input");
input.oninput = async (evt) => {
  const img = await createImageBitmap(input.files[0]);
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  // I hope you'll do something more here,
  // reencoding an iamge to JPEG through the Canvas API is a bad idea
  // Canvas encoders aim at speed, not quality
  canvas.toBlob( (blob) => {
    console.log( blob );
  }, "image/jpeg", 0.9 );
};
<!-- createImageBitmap polyfill for Safari -->
<script src="https://cdn.jsdelivr.net/gh/Kaiido/createImageBitmap/dist/createImageBitmap.js"></script>
<input type="file" accept="image/*">