解码图片以便与画布搭配使用非常常见,无论是允许用户自定义头像、剪裁图片,还是仅仅放大图片,都是如此。图片解码的问题在于,图片解码可能需要占用大量 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));
此方法也适用于以 Blob 形式存储在 IndexedDB 中的图片,使 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、样式计算、布局、绘制或合成。
帮助程序库
为了简化操作,我创建了一个帮助程序库,用于处理工作器上的解码,并将解码的图片发回到主线程,并将其绘制到画布中。当然,您可以随意对其进行逆向工程,并将模型应用于您自己的应用。主要优势是控制力更强,但与使用 <img>
元素相比,这意味着需要编写更多代码、进行更多调试,并考虑更多边缘情况。
如果您在图像解码方面需要更多控制权,createImageBitmap()
是您的新最好伙伴。欢迎使用 Chrome 50 立即试用,并告诉我们您的使用效果!