0

我试图通过仅使用图像 URL、宽度和高度向其发布消息来在 Worker 内创建 ImageData。为了使它工作,我需要获取图像并获取 ArrayBuffer。有了它,我应该能够创建新的 ImageData:

//worker.ts
fetch(e.data.url)
  .then( r => r.arrayBuffer() )
  .then( buffer => {
      console.log(buffer.byteLength); // 4022
      new ImageData((new Uint8ClampedArray(buffer)), e.data.width, e.data.height);
  });

我收到以下错误:输入数据字节长度不是(4 * 宽度)的倍数。

对于 Chrome,我不仅可以将 URL 发布到 Worker,还可以将之前在主线程中使用画布创建的整个 ArrayBuffer 发布,并且可以正常工作:

//main thread
const canvas = typeof OffscreenCanvas !== 'undefined' ?
   new OffscreenCanvas(img.width, img.height) :
   document.createElement('canvas'),
   ctx = canvas.getContext('2d');

ctx.drawImage(img, 0, 0, img.width, img.height);
const imageData: ImageData = ctx.getImageData(0, 0, img.width, img.height);
console.log(imageData.data.buffer.byteLength); // 106288

问题在于 Firefox worker.postMessage() 只能处理较小的消息,例如 URL,而不是整个 ArrayBuffer(需要很长时间来序列化和反序列化大消息)。这就是为什么我尝试只发送 URL、高度、宽度并在 Worker 本身中获取图像的方法。问题在于 Worker 中获取的 ArrayBuffer - 它比在主线程中使用画布创建的要小得多(相同的 URL,不同的方法,不同的 ArrayBuffer)。如果我在 Worker 中有相同的 ArrayBuffer,则会创建 ImageData(不会引发错误)。

我的问题是我做错了什么,如何在使用 fetch/axios 等获取图像后创建 ImageData。

4

1 回答 1

0

ImageData 表示绘制时将成为像素的红绿蓝和 Alpha 值。

另一方面,图像文件必须具有元数据。即使图像是您可以发明的最基本的位图格式,它仍然至少需要一个 width或一个height信息。

因此,您要获取的文件不仅仅是像素的 RGBA 通道的值,它是一个二进制数据,需要对其进行解析、可能未压缩、解码,甚至可能进行一些色彩空间处理,然后才能访问这些 RGBA 通道。


但是,您认为 Firefox 无法做到这一点的前提是有缺陷的。

不要克隆那个 ArrayBuffer,转移它,它不需要时间。

const worker_script = document.querySelector( '[type="worker-script"]' );
const worker_content = worker_script.textContent;
const worker_blob = new Blob( [ worker_content ], { type: 'text/javascript' } );
const worker_url = URL.createObjectURL( worker_blob );
const worker = new Worker( worker_url );

let start;
worker.onmessage = e => {
  console.log( e.data );
  console.log( 'it took ' + (performance.now() - start) + 'ms to go from main thread to worker and back to main' );
};

const img = new Image();
img.onload = e => {
  const canvas = document.createElement( 'canvas' );
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext( '2d' );
  ctx.drawImage( img, 0, 0, img.width, img.height );
  const imageData= ctx.getImageData( 0, 0, img.width, img.height );
  start = performance.now();
  worker.postMessage(
    imageData,
    [ imageData.data.buffer ] // transfer the ArrayBuffer
  );
};
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
<script type="worker-script">
  onmessage = e => {
    const imageData = e.data;
    postMessage(
`worker received a ${ imageData.width } x ${ imageData.height } ImageData,
whose buffer's size is ${ imageData.data.buffer.byteLength }bytes`
    );
  };
</script>

于 2020-05-13T05:10:50.820 回答