Javascript 将图像加载到 Offscreen Canvas 中,执行 webp 转换

Posted

技术标签:

【中文标题】Javascript 将图像加载到 Offscreen Canvas 中,执行 webp 转换【英文标题】:Javascript load Image into Offscreen Canvas, perform webp conversion 【发布时间】:2019-09-17 09:53:51 【问题描述】:

我最近使用 canvas 将图像转换为 webp,使用:

const dataUrl = canvas.toDataURL('image/webp');

但这对于某些图像来说需要很长时间,例如 400 毫秒。

我收到了来自 Chrome 的警告,因为它阻止了 UI。

我想使用 Offscreen Canvas 在后台执行该转换。

但是:

1) 我不知道我应该使用哪个 Offscreen Canvas: a] 新的 OffscreenCanvas() b] canvas.transferControlToOffscreen()

2) 我在 Image 对象 (img.src = url) 中加载本地图像 url 以获取本地图像的宽度和高度。但我不明白如何将图像对象转移到屏幕外的画布上,以便能够在工作人员中完成:

ctx.drawImage(img, 0, 0)

因为如果我不传输图像,工人不知道 img。

【问题讨论】:

之前使用占位符img,然后使用javascript设置img.src = url,还在img上附加一个onload函数并在加载img时渲染 【参考方案1】:

你在这里面对XY and even -Z problem,但每个人都可能有一个有用的答案,所以让我们深入研究。


X请勿使用 canvas API 进行图片格式转换。 canvas API 是有损的,无论你做什么,你都会从原始图像中丢失信息,即使你传递了无损图像,画布上绘制的图像也不会与原始图像相同。 如果您传递已经有损的格式(例如 JPEG),它甚至会添加原始图像中没有的信息:压缩伪影现在是原始位图的一部分,导出算法会将这些视为应保留的信息,从而使您的文件可能比您输入的 JPEG 文件大。

不知道你的用例,给你完美的建议有点困难,但一般来说,使版本中的不同格式最接近原始图像,一旦在浏览器中绘制,你至少已经太迟了三步。


现在,如果您对此图像进行一些处理,您可能确实想要导出结果。 但是您可能在这里不需要这个 Web Worker。。在您的描述中占用最大阻塞时间的应该是同步 toDataURL() 调用。 您应该始终使用异步且性能更高的 toBlob() 方法,而不是 API 中的这个历史错误。在 99% 的情况下,无论如何您都不需要数据 URL,几乎所有您想要对数据 URL 执行的操作都应该直接使用 Blob 完成。

使用这种方法,剩下的唯一繁重的同步操作就是在画布上绘画,除非你要缩小一些巨大的图像,否则这不应该占用 400 毫秒。

但无论如何,由于createImageBitmap 方法,您可以在最新的画布上使其变得更好,该方法允许您异步准备图像,以便图像的解码完成,所有需要做的只是一个 放像素 操作:

large.onclick = e => process('https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg');
medium.onclick = e => process('https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Black_hole_-_Messier_87.jpg/1280px-Black_hole_-_Messier_87.jpg');

function process(url) 
  convertToWebp(url)
    .then(prepareDownload)
    .catch(console.error);


async function convertToWebp(url) 
  if(!supportWebpExport())
  console.warn("your browser doesn't support webp export, will default to png");

  let img = await loadImage(url);
  if(typeof window.createImageBitmap === 'function') 
    img = await createImageBitmap(img);
  
  const ctx = get2DContext(img.width, img.height);

  console.time('only sync part');
  ctx.drawImage(img, 0,0);
  console.timeEnd('only sync part');
  
  return new Promise((res, rej) => 
    ctx.canvas.toBlob( blob => 
      if(!blob) rej(ctx.canvas);
      res(blob);
    , 'image/webp');
  );


// some helpers

function loadImage(url) 
  return new Promise((res, rej) => 
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.src = url;
    img.onload = e => res(img);
    img.onerror = rej;
  );


function get2DContext(width = 300, height=150) 
  return Object.assign(
    document.createElement('canvas'),
    width, height
  ).getContext('2d');


function prepareDownload(blob) 
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = 'image.' + blob.type.replace('image/', '');
  a.textContent = 'download';
  document.body.append(a);


function supportWebpExport() 
  return get2DContext(1,1).canvas
    .toDataURL('image/webp')
    .indexOf('image/webp') > -1;
<button id="large">convert large image (7,416 × 4,320 pixels)</button>
<button id="medium">convert medium image (1,280 × 746 pixels)</button>

Z。要从 Web Worker 在 OffscreenCanvas 上绘制图像,您需要上面提到的 createImageBitmap。实际上,此方法生成的 ImageBitmap 对象是 drawImage() 和 texImage2D()(*) 可以接受的唯一 图像源 值,该值在 Workers 中可用(所有其他都是 DOM 元素)。

这个 ImageBitmap 是transferable,所以你可以从主线程生成它,然后将它发送给你的 Worker,没有内存成本:

ma​​in.js

const img = new Image();
img.onload = e => 
  createImageBitmap(img).then(bmp => 
    // transfer it to your worker
    worker.postMessage(
      image: bmp // the key to retrieve it in `event.data`
    ,
   [bmp] // transfer it
  );
;
img.src = url;

另一种解决方案是直接从 Worker 中获取图像数据,并从获取的 Blob 中生成 ImageBitmap 对象:

worker.js

const blob = await fetch(url).then(r => r.blob());
const img = await createImageBitmap(blob);
ctx.drawImage(img,0,0);

请注意,如果您将主页面中的原始图像作为 Blob(例如来自 ),那么甚至不要直接使用 htmlImageElement 的方式,也不要直接获取发送此 Blob 并从中生成 ImageBitmap。

*texImage2D 实际上接受更多的源图像格式,例如 TypedArrays 和 ImageData 对象,但是这些 TypedArrays 应该代表像素数据,就像 ImageData 一样,并且为了具有这个像素数据,您可能需要已经使用其他 图像源 格式之一在某处绘制了图像。

【讨论】:

凭借您的洞察力,我能够提出我所加速的解决方案。谢谢!

以上是关于Javascript 将图像加载到 Offscreen Canvas 中,执行 webp 转换的主要内容,如果未能解决你的问题,请参考以下文章

使用 JavaScript 延迟加载图像的工作原理是啥?

如何使用 Jquery/Javascript 将我的一个文件夹中的所有图像加载到我的网页中

JQuery - 预加载图像(使用 JQuery / 本机 JavaScript / CSS)

从 JavaScript 加载 WKWebView 中 iOS tmp/ 文件夹中的图像文件

Javascript 画布 - 从加载的 4 波段 RGB 图像中删除 alpha 到 3 波段 JPEG 图像?

当我提出新请求时,Ajax http 请求将以前的图像加载到轮播中