无论是要允许用户自定义头像、剪裁图片,还是仅放大图片,解码图片以在画布中使用都是很常见的。解码图像的问题是会占用大量 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。
在 Web Worker 中使用 createImageBitmap()
createImageBitmap()
最棒的功能之一是它在 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、样式计算、布局、绘制或合成)。
帮助程序库
为了简化工作,我创建了一个辅助程序库,用于处理工作器的解码,并将解码后的图片发回到主线程,并将其绘制到画布中。当然,您可以随意对其进行逆向工程,并将模型应用于您自己的应用。主要好处是具有更大的控制权,但与使用 <img>
元素相比,这(和往常一样)需要更多代码、更多需要调试的代码,以及更多需要考虑的极端情况。
如果您需要通过图像解码实现更多控制,createImageBitmap()
将成为您的新伙伴。欢迎在 Chrome 50 中体验此功能,并告知我们您的进展!