Javascript将图像加载到离屏画布中,执行Webp转换

时间:2019-04-28 16:05:41

标签: javascript image canvas webp

我最近使用画布通过以下方式将图像转换为webp:

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

但是对于某些图像,这会花费很多时间,例如400ms。

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

我想使用屏幕外画布在后台执行该转换。

但是:

1)我不知道我应该使用哪种屏外画布:    一个]新的OffscreenCanvas()    b] canvas.transferControlToOffscreen()

2)我将本地图像url加载到Image对象中(img.src = url)以获取本地图像的宽度和高度。但是我不明白如何将Image对象传输到屏幕外的Canvas,以便能够在worker中完成:

ctx.drawImage(img,0,0)

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

1 个答案:

答案 0 :(得分:1)

您在这里面临着XY and even -Z problem,但每个人都有一个有用的答案,因此让我们深入探讨。


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

不知道您的用例,很难为您提供完美的建议,但是通常,使与该版本不同的格式最接近原始图像,并且一旦将其绘制在浏览器中,您至少已经三个步骤为时已晚。


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

使用此方法,剩下的唯一繁重的同步操作将是在画布上绘画,并且除非您要缩小一些大图像的大小,否则这不应该占用400ms。

但是,借助createImageBitmap方法,无论如何您都可以在最新的画布上做得更好,该方法允许您异步准备图像,以便图像的解码完成,而实际上需要做的只是一个< em> put pixel 操作:

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()(*)可以接受的唯一 image source 值,该值在Workers中可用(其他均为DOM Elements) )。

该ImageBitmap是transferable,因此您可以从主线程生成它,然后将其发送给您,而无需占用任何内存:

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中生成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一样代表像素数据,并且为了具有此像素数据,您可能已经需要使用其他图像源格式之一在某处绘制图像。