Das Decodieren von Bildern für die Verwendung mit einem Canvas ist ziemlich üblich, beispielsweise damit Nutzende einen Avatar anpassen, ein Bild zuschneiden oder einfach ein Bild heranzoomen können. Das Problem bei der Dekodierung von Bildern ist, dass sie CPU-intensiv sein kann. Das kann manchmal zu Rucklern oder Schachbrettmustern führen. Ab Chrome 50 (und in Firefox 42 und höher) haben Sie jetzt eine weitere Option: createImageBitmap()
. Damit können Sie ein Bild im Hintergrund decodieren und auf ein neues ImageBitmap
-Primitive zugreifen, das Sie auf einem Canvas genauso zeichnen können wie ein <img>
-Element, ein anderes Canvas oder ein Video.
Blobs mit createImageBitmap() zeichnen
Angenommen, Sie laden ein Blob-Bild mit fetch()
(oder XHR) herunter und möchten es in ein Canvas zeichnen. Ohne createImageBitmap()
müssten Sie ein Bildelement und eine Blob-URL erstellen, um das Bild in ein verwendbares Format zu bringen. Damit erhalten Sie einen viel direkteren Weg zum Malen:
fetch(url)
.then(response => response.blob())
.then(blob => createImageBitmap(blob))
.then(imageBitmap => ctx.drawImage(imageBitmap, 0, 0));
Dieser Ansatz funktioniert auch mit Bildern, die als Blobs in IndexedDB gespeichert sind, was Blobs zu einem praktischen Zwischenformat macht. Demnächst unterstützt Chrome 50 auch die .toBlob()
-Methode für Canvas-Elemente, sodass Sie beispielsweise Blobs aus Canvas-Elementen generieren können.
createImageBitmap() in Webworkern verwenden
Eine der besten Funktionen von createImageBitmap()
ist, dass sie auch in Workern verfügbar ist. Das bedeutet, dass Sie jetzt Bilder beliebig decodieren können. Wenn Sie viele Bilder decodieren müssen, die Sie für nicht wesentlich halten, senden Sie ihre URLs an einen Webworker, der sie nach und nach herunterlädt und decodiert. Sie werden dann zurück in den Hauptthread übertragen, um sie auf einem Canvas zu zeichnen.
Der Code dazu könnte so aussehen:
// 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);
}
Wenn Sie heute createImageBitmap()
im Hauptthread aufrufen, wird dort die Decodierung durchgeführt. Künftig soll die Dekodierung jedoch automatisch in einem anderen Thread erfolgen, um die Arbeitslast des Hauptthreads niedrig zu halten. In der Zwischenzeit sollten Sie jedoch darauf achten, die Dekodierung im Haupt-Thread auszuführen, da es sich um eine intensive Arbeit handelt, die andere wichtige Aufgaben wie JavaScript, Stilberechnungen, Layout, Malen oder Compositing blockieren könnte.
Eine Hilfsbibliothek
Um das Leben ein wenig einfacher zu machen, habe ich eine Hilfsbibliothek erstellt, die die Decodierung in einem Worker übernimmt, das decodierte Bild an den Hauptthread zurücksendet und in einen Canvas zieht. Natürlich können Sie ein Reverse-Engineering durchführen und das Modell auf Ihre eigenen Apps anwenden. Der große Vorteil ist mehr Kontrolle, aber das bedeutet wie gewohnt mehr Code, mehr Debugging-Aufwand und mehr Grenzfälle, die berücksichtigt werden müssen als die Verwendung eines <img>
-Elements.
Wenn du mehr Kontrolle bei der Bilddecodierung benötigst, ist createImageBitmap()
genau das Richtige für dich. Probieren Sie es in Chrome 50 aus und teilen Sie uns Ihre Meinung mit!