【问题标题】:canvas.toBlob giving null sometimescanvas.toBlob 有时会给出 null
【发布时间】:2021-05-27 23:14:48
【问题描述】:

我正在设置一个画布,然后在该画布中绘制一个图像,然后调用canvas.toBlob(function(blob)...),但我发现toBlob 中的blob 参数有时是null

为什么会这样?我应该在drawImage 之后等待什么吗(即使在 sn-p 中 - 你可以看到我在等待图像加载后再继续)?

//--------------------------------------------------
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'))。

【问题讨论】:

  • How do I ask a good question?:在问题本身中包含minimal reproducible example,而不仅仅是摘录和指向外部资源的链接(可能无法访问或更改其内容)无论出于何种原因)。
  • 根据specification,在这种情况下只有一个步骤可能会出现问题:“如果result是_non-null,则将结果设置为@ 987654325@ with type and quality if given"_ + "如果在创建图像文件期间发生错误(例如内部编码器错误),则序列化的结果是null。”
  • @Andreas 您错过了第 3 步,这是在 OP 的情况下设置结果的地方(因为他们的画布是 0x0px)。
  • @Kaiido 不,我没有错过那一步,但我错过了 OP 只等待 FileReader 而不是 Image :/
  • @Andreas 那么,为什么说“只有一步……”呢?显然至少有两个。

标签: javascript canvas


【解决方案1】:

好的,看起来问题在于画布工作的调用者 - 实际上,在 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/看到工作版本

【讨论】:

    【解决方案2】:

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

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

    由于您没有等待图像加载,它的widthheight 属性仍然是0,并且由于您确实将画布的大小设置为这些,因此您属于上述第三个项目符号。


    因此,要修复您的错误,请等待图片已加载,然后再对其进行任何操作。
    另外,请注意您应该almost never useFileReader.readAsDataURL(),当然不要显示来自 Blob 的媒体文件,而是使用 URL.createObjectURL() 从这些 Blob 生成一个blob:// URI。

    但在您的情况下,您甚至可以使用更好的 createImageBitmap API,它会负责将图像加载到您的 Blob 中,并生成一个内存友好的 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/*">

    【讨论】:

      【解决方案3】:

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

      /**
       * @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​​(仅被解码回&lt;img&gt; 再次使用二进制文件)这是浪费时间、处理和 RAM。

      【讨论】:

      • 从 Blob 中更好地使用 createImageBitmapHere is a polyfill 用于 Safari(我写的),尽管他们应该尽快添加支持。此外,它会更好地回答解释为什么响应是null
      猜你喜欢
      • 2017-11-16
      • 1970-01-01
      • 1970-01-01
      • 2021-06-03
      • 1970-01-01
      • 2023-03-18
      • 2018-11-02
      • 2019-04-17
      • 2020-05-18
      相关资源
      最近更新 更多