(HTML5 Canvas-)Bilder hochauflösend (Retina) darstellen

Vor guten zehn Monaten ist der letzte Artikel in der Kategorie “Leicht und klar erklärt” erschienen und so gab mir heute (mal wieder) ein Thema in der Firma eine perfekte Vorlage für einen Artikel.

Wie stelle ich HTML5 Canvas Grafiken auf einem iPad mit Retina-Display scharf dar? Folgende Erklärung gilt aber nicht nur für das Canvas-Element, auf Bilder/Fotos trifft dies ebenso zu. Fast alle Tablets haben mittlerweile hochauflösende Displays (High DPI – HiDPI) und so wird es immer wichtiger, dass du das folgende beachtest – zumindest wenn du deinen Besuchern Qualität bieten möchtest. Die vernünftige Darstellung von Bildern auf allen Geräten kann man als Teildisziplin von Responsive Webdesign bezeichnen. Auf zur Theorie:

Die Dreipixeligkeit – Grafikpixel, Software-Pixel und Bildschirm-Pixeldichte

Software-Pixel / CSS-Pixel: 100px in CSS bedeutet nicht, dass 100 Lämpchen am Bildschirm angesteuert werden. Die Browser legen fest, wieviel mm ein px auf dem Bildschirm misst.

Pixeldichte: Wieviel Pixel pro Inch (ppi) sind physikalisch auf dem Display vorhanden. Daraus ergibt sich, wieviel Bildschirm-Pixel einen Software-Pixel darstellen.

Pixelauflösung einer Grafik: Hast du eine 300×300 Pixel große Grafik, ist dessen tatsächlich Abmessung abhängig davon, wo sie verwendet wird. Druckst du sie (mit typischen 300dpi), ist die Grafik 1inch x 1inch groß. Wird die gleiche Grafik in eine HTML-Seite eingebunden und auf einem standard Monitor, mit einem standard Betriebssystem dargestellt (96dpi) ist diese schon ~ 3inch x 3inch groß.

Microsoft hatte auf den damaligen 72dpi Bildschirmen schon 96dpi verwendet. Daraus folgte, dass sie 1/3 mehr Bildschirm-Pixel zur Darstellung hatten, damit also feiner in der Darstellung waren, aber auch 1/3 weniger Platz auf dem Bildschirm hatten.

Aus diesen ganzen Unterschieden ergibt sich auch die Problematik der nicht scharfen Darstellung von Grafiken auf HiDPI-Bildschirmen (Retina und andere Geräte). Die 300 x 300 Pixel große Grafik wird auch auf einem 180dpi Desktop-Monitor 3inch x 3inch einnehmen, es sind aber nur Informationen für jeden zweiten (Hardware-)Pixel vorhanden, weshalb gedoppelt wird. Das gleiche Prinzip, als würdest du in Photoshop deine 300 x 300 Pixel Grafik auf 600 x 600 Pixel vergrößern. Was passiert: Die Grafik ist nicht mehr scharf.
Wenn wir jetzt aber eine Grafik mit der Auflösung 200×200 Pixel nehmen, diese auf 100 x 100 CSS-Pixel setzen, kann das Retina-Display weiterhin die vollen 200×200 Pixel der Grafik im 100×100 Pixel Raum darstellen und stellt das Bild somit superscharf dar.

Nun zur Praxis mit dem eigentlichen Auslöser des Artikels – dem HTML5 Canvas-Element:

HTML5 Canvas-Element scharf auf allen Endgeräten

Canvas-Grafiken leiden unter dem eben beschriebenen Prinzip wie alle anderen Grafiken und Fotos. Wie dort aber bereits erwähnt; es ist leicht zu beheben:

So kannst du automatisch deine Canvas-Grafik Retina tauglich machen; folgenden Code benutzen, bevor du anfängst auf dem Canvas zu zeichnen:


var canvas = document.getElementById('myCanvas');

// Die alten Maße auslesen
var oldWidth = canvas.width;
var oldHeight = canvas.height;

// Die Zeichenfläche des Canvas-Elements verdoppeln
// aka. mehr Bildinformationen erschaffen.
canvas.width = oldWidth * 2;
canvas.height = oldHeight * 2;

/* Die CSS-Pixel auf die alten Werte setzen, sodass
 * das Canvas weiterhin die gewünschten Ausmaße auf der
 * Website einnimmt.
 */
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';

Nun werden Schriften und Grafiken auf deinem Canvas wieder scharf dargestellt, auch auf einem HiDPI-Bildschirm. Das Problem ist, dass die Grafiken und Schriften nun nur noch halb so groß sind, wir haben die Zeichenfläche verdoppelt, während die Angaben zum Zeichnen aber noch auf der alten Fläche beruhen. Deine x und y Angaben sind Pixelangaben. Dies können wir aber mit scale() beheben:


context = canvas.getContext('2d');
context.scale(2, 2);

Dadurch wird automatisch jede folgende x und y Angabe verdoppelt. Deine Grafik ist weiterhin superscharf und nun auch mit den richtigen Maßen.

Theoretisch könnten wir es nun dabei belassen, obiges würde dazu führen, dass das Canvas auch auf normalen Bildschirmen wunderbar dargestellt wird, da das Downscaling in modernen Browsern sehr sehr gut funktioniert. Das Problem ist, dass durch die Verdopplung der Zeichenfläche vier mal soviel Speicher benötigt wird – das müssen wir unseren Besuchern aber nicht zumuten. Die meisten Browser bieten eine API zum Auslesen, ob es sich um ein HiDPI Bildschirm handelt oder nicht:


// Device-Pixel-Ratio auslesen - Verhältnis von Software-DPI zu Bildschirm-PPI
var dpr = window.devicePixelRatio || 2;

/* Während auf einem Desktop automatisch alles hochskaliert wird, wird
 * die automatische Skalierung auf mobilen Geräten aufgrund des höheren
 * Speicherbedarfs ausgestellt. Ob eine automatische Skalierung stattfand,
 * kann folgend ausgelesen werden.
 */
var bsr = context.webkitBackingStorePixelRatio ||
          context.mozBackingStorePixelRatio ||
          context.msBackingStorePixelRatio ||
          context.oBackingStorePixelRatio ||
          context.backingStorePixelRatio || 1;

// Unsere tatsächliche Pixel-Ration berechnen.
var ratio = dpr / bsr;

Nun wissen wir, um welchen Faktor wir hochskalieren müssen. Woraus sich folgender Code ergibt.


function hiDPICanvas(canvas) {
    context = canvas.getContext('2d');

    /*
     * dpr Default auf "2" für immer hohe Qualität, aber
     * auch immer hohen Speicherbedarf. Könnte nützlich
     * sein, wenn window.devicePixelRatio einen Bug hat.
     */
    var dpr = window.devicePixelRatio || 1;
    var bsr = context.webkitBackingStorePixelRatio ||
              context.mozBackingStorePixelRatio ||
              context.msBackingStorePixelRatio ||
              context.oBackingStorePixelRatio ||
              context.backingStorePixelRatio || 1;

    var ratio = dpr / bsr;

    var oldWidth = canvas.width;
    var oldHeight = canvas.height;

    canvas.width = oldWidth * ratio;
    canvas.height = oldHeight * ratio;

    canvas.style.width = oldWidth + 'px';
    canvas.style.height = oldHeight + 'px';

    context.scale(ratio, ratio);
}

Diese Funktion ausführen, bevor dein Code zum Zeichnen auf dem Canvas beginnt und alles sollte wunderbärchen sein.

Weiterführende Links zum Thema HiDPI

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>