解码图片以便与画布搭配使用非常常见,无论是允许用户自定义头像、剪裁图片,还是仅仅放大图片,都是如此。解码图片的问题在于,它可能需要大量的 CPU 资源,这有时会导致卡顿或棋盘模式。从 Chrome 50 开始(在 Firefox 42 及更高版本中),您现在可以使用另一种方式:createImageBitmap()
。借助它,您可以在后台解码图片,并访问新的 ImageBitmap
基元,您可以将其绘制到画布中,就像绘制 <img>
元素、其他画布或视频一样。
使用 createImageBitmap() 绘制 Blob
假设您使用 fetch()
(或 XHR)下载了一个 Blob 图片,并希望将其绘制到画布中。如果没有 createImageBitmap()
,您必须创建一个图片元素和一个 Blob 网址,才能将图片转换为您可以使用的格式。借助它,您可以更直接地绘制:
fetch(url)
.then(response => response.blob())
.then(blob => createImageBitmap(blob))
.then(imageBitmap => ctx.drawImage(imageBitmap, 0, 0));
这种方法也适用于在 IndexedDB 中存储为 Blob 的图片,这使得 Blob 成为一种方便的中间格式。事实上,Chrome 50 还支持对画布元素使用 .toBlob()
方法,这意味着您可以(例如)从画布元素生成 Blob。
在网页工作器中使用 createImageBitmap()
createImageBitmap()
的一大优势在于,它还可在工作器中使用,这意味着您现在可以在任何位置解码图片。如果您有大量您认为不重要的图片需要解码,则可以将其网址发送到 Web Worker,Web Worker 会在有时间时下载并解码这些图片。然后,它会将这些数据传回主线程,以便绘制到画布中。
用于执行此操作的代码可能如下所示:
// In the worker.
fetch(imageURL)
.then(response => response.blob())
.then(blob => createImageBitmap(blob))
.then(imageBitmap => {
// Transfer the imageBitmap back to main thread.
self.postMessage({ imageBitmap }, [imageBitmap]);
}, err => {
self.postMessage({ err });
});
// In the main thread.
worker.onmessage = (evt) => {
if (evt.data.err)
throw new Error(evt.data.err);
canvasContext.drawImage(evt.data.imageBitmap, 0, 0);
}
目前,如果您在主线程上调用 createImageBitmap()
,解码将在该线程上完成。不过,我们计划让 Chrome 在另一个线程中自动进行解码,以帮助减少主线程的工作负载。不过,在此期间,您应注意在主线程中进行解码,因为这项工作非常繁重,可能会阻塞其他重要任务,例如 JavaScript、样式计算、布局、绘制或合成。
帮助程序库
为了简化操作,我创建了一个辅助库,用于在 worker 上处理解码,并将解码后的图片发回主线程,然后将其绘制到画布中。当然,您可以随意对其进行逆向工程,并将模型应用于您自己的应用。主要优势是控制力更强,但与使用 <img>
元素相比,这意味着需要编写更多代码、进行更多调试,并考虑更多边缘情况。
如果您需要更好地控制图片解码,createImageBitmap()
就是您的新好友。请在 Chrome 50 中试用此功能,并告诉我们您的使用体验!