【问题标题】:Resize & Compress Image in Web Worker. Can I use a canvas?在 Web Worker 中调整图像大小和压缩图像。我可以使用画布吗?
【发布时间】:2016-03-02 09:56:41
【问题描述】:

背景

我现在正在处理客户端选择图像。

我想对该图像执行两个操作,并输出 base64 编码的字符串。

  1. 如果图像尺寸的宽度或高度大于 1000,请调整其大小。
  2. 使用质量为 0.5 的 jpeg 压缩图像。

所以现在我将在主脚本中执行以下操作:

$(function() {

  $('#upload').change(function() {

    var URL = window.URL || window.webkitURL;

    var imgURL = URL.createObjectURL(this.files[0]);

    var img = new Image();

    img.onload = function() {

      var canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');
      var w0 = img.width;
      var h0 = img.height;
      var w1 = Math.min(w0, 1000);
      var h1 = h0 / w0 * w1;

      canvas.width = w1;
      canvas.height = h1;

      ctx.drawImage(img, 0, 0, w0, h0,
        0, 0, canvas.width, canvas.height);

      // Here, the result is ready, 
      var data_src = canvas.toDataURL('image/jpeg', 0.5);
      $('#img').attr('src', data_src);

      URL.revokeObjectURL(imgURL);
    };

    img.src = imgURL;

  });

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="upload" type="file" accept="image/*" />
<img id="img" />

问题

但是,我的代码仍然可以在移动设备上运行,而上述过程(调整大小和压缩)无法很快完成。它会导致 GUI 停止响应片刻。

我希望该过程在另一个线程中工作,使用 web worker。所以不会阻塞UI,用户体验会更好。

现在问题来了,网络工作者似乎无法处理画布,我该如何解决这个问题?

【问题讨论】:

  • 你的图片有多大,所以加载会阻塞 UI?我认为使用 webworker 完成此类任务的唯一方法是提取画布的 imageData 并将其发送给 webworker,在那里你必须自己重写缩放/压缩算法...... getImageData/putImageData 是至少与您正在做的事情一样消耗CPU。你确定问题出在这个函数上吗?
  • 使用 webworkers 将减少 UI 线程的延迟。只要设备对网络工作者有良好的支持,这将减少 UI 延迟。但是使用 webworkers 会增加整体负担,因为图像数据必须同时编组给 webworkers,然后在他们的工作完成时编组回来。

标签: javascript html image-processing canvas web-worker


【解决方案1】:

一些事件驱动的编码

遗憾的是,Web 工作者还没有为浏览器支持做好准备。

网络工作者对toDataURL 的有限支持意味着需要另一种解决方案。请参阅页面中间的MDN web worker APIs (ImageData),目前仅适用于 Firefox。

查看您的onload,您可以在一个阻塞呼叫onload 中完成所有繁重的工作。您在创建新画布、获取其上下文、缩放和toDataURL(不知道revokeObjectURL 做了什么)的过程中阻塞了 UI。在这种情况发生时,您需要让 UI 接到几个电话。所以一点点事件驱动的处理将有助于减少故障,如果不是让它不明显的话。

尝试如下重写onload函数。

// have added some debugging code that would be useful to know if
// this does not solve the problem. Uncomment it and use it to see where
// the big delay is.
img.onload = function () {
    var canvas, ctx, w, h, dataSrc, delay; // hosit vars just for readability as the following functions will close over them
                                    // Just for the uninitiated in closure.
    // var now, CPUProfile = [];  // debug code 
    delay = 10; // 0 could work just as well and save you 20-30ms
    function revokeObject() { // not sure what this does but playing it safe
        // as an event.
        // now = performance.now(); // debug code
        URL.revokeObjectURL(imgURL);
        //CPUProfile.push(performance.now()-now); // debug code
        // setTimeout( function () { CPUProfile.forEach ( time => console.log(time)), 0);
    }
    function decodeImage() {
        // now = performance.now(); // debug code
        $('#img').attr('src', dataSrc);
        setTimeout(revokeObject, delay); // gives the UI a second chance to get something done.
        //CPUProfile.push(performance.now()-now); // debug code
    }
    function encodeImage() {
        // now = performance.now(); // debug code
        dataSrc = canvas.toDataURL('image/jpeg', 0.5);
        setTimeout(decodeImage, delay); // gives the UI a second chance to get something done.
        //CPUProfile.push(performance.now()-now); // debug code
    }
    function scaleImage() {
        // now = performance.now(); // debug code
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        setTimeout(encodeImage, delay); // gives the UI a second chance to get something done.
        //CPUProfile.push(performance.now()-now); // debug code
    }
    // now = performance.now(); // debug code
    canvas = document.createElement('canvas');
    ctx = canvas.getContext('2d');
    w = Math.min(img.width, 1000);
    h = img.height / img.width * w;
    canvas.width = w;
    canvas.height = h;
    setTimeout(scaleImage, delay); // gives the UI a chance to get something done.
    //CPUProfile.push(performance.now()-now); // debug code
};

setTimeout 允许当前调用退出,释放调用堆栈并允许 UI 在 DOM 上获得它的手套。我给了 10 毫秒,我个人会从 0 毫秒开始,因为调用堆栈访问没有被阻止,但我玩得很安全

如果运气好,问题会大大减少。如果它仍然是不可接受的延迟,那么我可以查看 CPU 配置文件,看看是否无法通过针对瓶颈找到解决方案。我的猜测是toDataURL 是负载所在的位置。如果是,一个可能的解决方案是找到一个用 javascript 编写的 JPG 编码器,该编码器可以转换为事件驱动的编码器。

问题不在于处理数据需要多长时间,而在于阻塞 UI 需要多长时间。

【讨论】:

  • 写的很详细,指出合理的解决方案,谢谢。
  • 但是有没有办法打破压缩?这可能需要几秒钟。
  • @Blindman67,您可以将URL.revokeObjectURL() 视为free cachedelete 用于由createObjectURL 方法生成的blob。顺便说一句,@fish_ball 你也可以尝试使用FileReader.readAsDataURL 进行此计算,这将异步加载文件而不是同步createObjectURL()
  • Fish_ball 压缩?图像到 JPG 或调整大小?调整大小将由 GPU(如果可用)完成。只有当图像大于 GPU 可用的 RAM 时,才会注意到这一点。对于由本机函数完成的 JPG 压缩,除非您使用较低级别的语言,否则无法更快地完成。 @Kaiido 谢谢。然后撤销是微不足道的,直到 GC 启动,这在大多数浏览器上都做得很好。
  • @Blindman67 revokeObjectURL 并不是很简单:即使您关闭创建 ObjectURL 的选项卡,blob 也会缓存在浏览器中,实际上我认为它只有在您退出浏览器时才会被清除,我不要认为GC会收集它。所以revokeObjectURL不应该被遗忘。但我同意这与 OP 的问题无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-30
  • 2012-08-27
  • 2018-02-25
  • 1970-01-01
  • 2013-03-24
相关资源
最近更新 更多