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

问题描述 投票:0回答:1

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

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

但这需要花费大量时间来处理某些图像,例如400毫秒。

我收到了Chrome的警告,因为它阻止了用户界面。

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

但是:

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

2)我在Image对象(img.src = url)中加载局部图像URL以获取本地图像的宽度和高度。但我不明白如何将Image对象转移到屏幕外的Canvas,以便能够在worker中执行:

ctx.drawImage(img,0,0)

因为如果我没有转移图像,工人就不会知道img。

javascript image canvas webp
1个回答
1
投票

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


X.不要使用canvas API执行图像格式转换。画布API是有损的,无论你做什么,你都会从原始图像中丢失信息,即使你确实传递了无损图像,画布上绘制的图像也不会与原始图像相同。 如果你传递一个像JPEG这样已经有损的格式,它甚至会添加原始图像中没有的信息:压缩工件现在是原始位图的一部分,导出算法将这些视为应该保留的信息,使你的文件成为可能大于你喂它的JPEG文件。

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


现在,如果您对此图像进行一些处理,您可能确实想要导出结果。 但是你可能不需要这个Web Worker。 Y.你的描述中最大的阻塞时间应该是同步toDataURL()调用。 而不是API中的这个历史错误,你应该始终使用异步和更高性能的toBlob()方法。在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:

main.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生成Image Bitmap对象:

worker.js

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

请注意,如果您将主页面中的原始图像作为Blob(例如来自<input type =“file”>),那么甚至不采用HTMLImageElement的方式,也不采取提取方式,直接发送此Blob并从中生成ImageBitmap。

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

© www.soinside.com 2019 - 2024. All rights reserved.