Pixel CSS et pixel physique

Partons d’un code tout bête :

<div></div>  
div {  
    width: 1px;
    height: 1px;
    background: #0F71E7;
}

Voici le rendu sur mon GT680 :

Un pixel bleu, comme on pouvait s’y attendre.

Et voilà le rendu sur ma Nexus 7, première du nom :

Quatre pixels de bleus plus clairs…

Les contours gris délimitent les pixels de ces écrans ; ce sont donc des pixels physiques. Par contre en écrivant width: 1px;, j’ai spécifié une largeur d’un pixel CSS, qui apparaît ici en bleu. Vous voyez ici qu’un pixel CSS n’est pas forcément affiché sur un pixel physique ; il s’agît donc de deux notions distinctes.

La correspondance entre les deux passe par la détermination des dimensions physiques du pixel CSS ; on dispose pour cela de deux approches.

1re approche

1px is equal to 1/96th of 1in

La théorie

Le W3C nous dit qu’un pixel CSS est égal à 1/96 pouce. Si je vous révèle maintenant que l’écran de mon GT680 a une résolution de 96ppp, l’observation semble logique : ses pixels physiques mesurent exactement les dimensions d’un pixel CSS, et un pixel CSS s’affiche donc sur un pixel physique.

La première approche consiste en effet à lier les unités CSS absolues à leur mesure physique.

UnitéDéfinitionÉquivalence
cmcentimètres
mmmillimètres
inpouces2,54cm
pxpixels1/96in
ptpoints1/72in
pcpicas12pt

Si j’écris

div {  
    width: 10cm;
    height: 10cm;
    background: #0F71E7;
}

alors je devrais pouvoir mesurer ces 10 centimètres.

…ce qui n’est pas le cas.

La pratique

En réalité, l’écran n’a pas une résolution de 96ppp mais c’est cette dernière que retourne Windows au navigateur, pour des raisons historiques. J’observe également le problème sous Ubuntu, car X.org a imité ce comportement pour éviter des différences d’affichage avec Windows.

Plutôt que de faire confiance au système d’exploitation, calculons nous-mêmes la vraie résolution. Une diagonale de 15,6" et une définition de 1366×768 nous donnent une résolution de 1366/2838119076/15348625 (merci Pythagore), soit environ 100,5ppp. Par rapport aux 96ppp précédents nous avons donc une erreur relative d’à peu près -4,4% qui confirme la mesure précédente.

Voilà qui vous donne une raison de plus de ne pas utiliser d’unité absolue pour un rendu sur écran !

only use absolute length units when the physical characteristics of the output medium are known.

Au final, cette approche est donc utilisée lorsque :

  • le navigateur a connaissance exacte du dispositif d’affichage ;
  • la cohérence entre unités CSS absolues et mesures physiques est importante.

Ce qui n’est pas le cas sur mon GT680 ; sont plutôt concernés les « médias d’impression et dispositifs haute-résolution similaires ». Si j’imprime le rendu de notre <div> de 10 centimètres, je mesurerai donc 10 centimètres.

Mais pas toujours. Ce serait trop simple.

2e approche

Imaginez maintenant un écran énorme dont les pixels physiques mesurent, disons… un pouce ! Ces derniers sont indivisibles, alors comment pourraient-ils afficher un élément 96 fois plus petit ? Impossible.

Impossible, sauf si le navigateur décide des dimensions du pixel CSS. Ici il lui suffit par exemple de considérer « un pixel CSS mesure un pouce », ce qui est bien arrangeant puisque ses pixels physiques mesurent également un pouce. Un pixel CSS s’affichera donc sur un pixel physique.

Dans ce cas, peut-on toujours considérer qu’un pixel CSS est égal à 1/96 pouce ? Eh bien en fait oui, si on parle de pouces CSS. Ici notre pixel CSS mesure un pouce physique, on a donc un pouce CSS égal à 96 pouces physiques.

Notez qu’il n’est pas indispensable qu’un pixel CSS s’affiche sur un pixel physique. L’essentiel est que la décision se base sur le…

Pixel de référence

The reference pixel is the visual angle of one pixel on a device with a pixel density of 96dpi and a distance from the reader of [28 inches].

Le pixel de référence est la taille apparente d’un pixel de 1/96 pouce vu à une distance de 28 pouces (longueur nominale d’un bras). Elle est calculée grâce à la formule suivante :

V=2×arctan(S2×D)
  • V est la taille apparente.
  • S est la taille du pixel.
  • D est la distance à laquelle il est vu.

Le pixel de référence est donc égal à 2×arctan(1/5376). Cette valeur nous permet de poser S'=D'/2688 et D'=S'×2688.

Décider que notre pixel CSS mesure un pouce revient à poser S=1, ce qui permet de calculer D=2688, et de déterminer que notre énorme écran est prévu pour être consulté d’une distance de 2688 pouces (environ 68 mètres). Plus précisément, un pixel de cet écran vu à 2688 pouces sera perçu comme un pixel de 1/96 pouce vu à 28 pouces.

A l’inverse, imaginons que cet écran soit installé au Stade de France. Il faut alors prévoir une distance maximale de 270m avec les spectateurs, soit approximativement 10630 pouces. Un pixel CSS devra donc être affiché sur 10630/2688, soit environ 3,96 pouces.

[…] it is recommended that the pixel unit refer to the whole number of device pixels that best approximates the reference pixel.

Il est toutefois recommandé d’afficher un pixel CSS sur un nombre entier de pixels physiques. Les pixels de l’écran mesurant un pouce, le navigateur pourra décider qu’un pixel CSS mesure quatre pouces, ce qui lui permettra d’en afficher un sur 4×4 pixels physiques.
On nomme un tel rapport « (device) pixel ratio » et l’exprime en dppx (dots per pixel). Ici, le navigateur définira donc 4dppx.

Sur mon GT680, le navigateur définissait 1dppx.

Pixels décimaux

Maintenant que nous savons tout du pixel CSS, expliquons le rendu observé sur ma Nexus 7. Nous pouvons trouver les caractéristiques de l’appareil sur le site mydevice.io.

name phys. width phys. height CSS width CSS height pixel ratio phys. ppi CSS ppi
Asus Nexus 7 (v1) 800 1280 604 966 1.325 216 127

Si on peut y voir un pixel ratio, c’est qu’il s’agit pour chaque appareil d’une valeur théorique. Comme dit plus haut, ce ratio est bien choisi par le navigateur. Ce dernier peut ou pas utiliser cette valeur théorique, et peut ou pas vous indiquer la valeur choisie. Que du bonheur.

Toutefois, je vous confirme que la capture montre le rendu de Chrome pour Android qui définit bien un ratio de 1,325dppx, ce qui veut dire qu’un pixel CSS devrait être affiché sur 1,325 pixel physique. Mais on ne peut physiquement pas allumer un nombre décimal de pixels. Par contre, on peut faire semblant.

Arrondi

Le nombre décimal est arrondi à un nombre entier de pixels de la même couleur. Par exemple, le rendu sur mon GT680 aurait théoriquement été identique si j’avais écrit

div {  
    width: 1.325px;
    height: 1.325px;
    background: #0F71E7;
}

« Théoriquement », car le choix de la méthode d’arrondi est laissé au navigateur, ce qui peut poser problème.

By ALexL33 (Own work) CC BY-SA 3.0 or GFDL, via Wikimedia Commons

Anti-aliasing

Le problème de l’arrondi vient du fait qu’on peut se retrouver avec une différence non négligeable entre le rendu voulu et celui obtenu. L’anti-aliasing est une technique visant à réduire cette différence en jouant sur la couleur des pixels correspondant à la partie décimale.

Par exemple 0,325 pixel de couleur #0F71E7 pourrait être rendu par un pixel de couleur #B0D0F7.

By ALexL33 (Own work) CC BY-SA 3.0 or GFDL, via Wikimedia Commons

Subpixel rendering

C’est cette technique qui est utilisée sur la Nexus 7 ; malheureusement elle ne peut pas être visible sur une capture d’écran puisqu’au lieu de jouer sur la couleur des pixels, on jouera sur l’affichage de leurs composantes rouge, verte et bleue, ce qui permet un anti-aliasing bien plus précis.

By ALexL33 (Own work) CC BY-SA 3.0 or GFDL, via Wikimedia Commons

Au final, il n’y a donc aucun problème à utiliser des dimensions ne correspondant pas à des pixels entiers dans votre CSS. Prenez juste garde à ce que l’implémentation d’un navigateur ne casse pas votre layout !

Viewport

Même si nous savons maintenant que la capture d’écran de la Nexus 7 ne reflète pas exactement la réalité, elle reste éloignée de ce à quoi on pouvait s’attendre, les pixels bleus étant bien plus clairs. La faute au viewport.

User agents for continuous media generally offer users a viewport (a window or other viewing area on the screen) through which users consult a document.

Le W3C le définit comme la surface d’affichage sur laquelle un document peut être consulté. Il donne ses dimensions au « bloc conteneur initial » qui contient l’élément racine <html>, ce qui explique que la largeur de ce dernier est celle de la zone d’affichage du navigateur.

Lorsque les dimensions du document dépassent celles du viewport, celui-ci doit fournir un mécanisme de défilement.

Le soucis, c’est que même avec la popularisation du responsive design, énormément de sites ont une largeur fixe prévue pour un écran d’ordinateur. En conséquence, un tel site sur un écran moins large nécessiterait un mécanisme de défilement horizontal.

Cependant, les navigateurs mobiles ont préféré une alternative : ils définissent leur propre viewport (pas le même pour tous évidemment) indépendamment des dimensions de l’écran, de façon à ce qu’il soit aussi large que celui d’un écran d’ordinateur.

Une fois une page web rendue sur un tel viewport, le navigateur va dézoomer jusqu’à ce qu’elle s’affiche entièrement sur l’écran. Sur ma Nexus 7, Chrome définit un viewport de 980 pixels CSS de large alors que l’écran n'en compte que 604 ; un niveau de zoom d’à peu près 62% est donc appliqué. Au final, notre pixel CSS s’affiche en réalité sur environ 0,82 pixels physiques, ce qui explique qu’on ne retrouve plus notre bleu original.

Bonne nouvelle cependant, nous pouvons contrôler (à quelques exceptions) les dimensions du viewport. Ainsi, ajouter au HTML la ligne

<meta name="viewport" content="width=device-width, initial-scale=1.0" />  

permet d’obtenir un résultat plus satisfaisant.

Zoom

Revenons sur le zoom avec le rendu sur mon GT680. Si je l’augmente avec le navigateur jusqu’à 200%, c’est quatre pixels physiques de l’écran qui seront colorés.

Cela semble normal, après tout le zoom a doublé donc les dimensions ont également doublé. Mais quelles dimensions exactement ? Notre écran a toujours la même définition, la même résolution, et le CSS n’a pas changé. Ce que nous observons est donc toujours un pixel CSS. Mais nous savons maintenant que la taille du pixel CSS est choisie par le navigateur, et rien ne l’empêche de la changer.

Un zoom est en effet un simple facteur de redimensionnement du pixel CSS. En conséquence, le pixel ratio est également modifié ; il passe ici de 1 à 2 dppx. Vous pouvez d’ailleurs retourner sur mydevice.io pour le constater par vous-mêmes (un rechargement peut être nécessaire pour mettre à jour les valeurs).

Et… voilà ! Nous avons fait le tour des péripéties entreprises par un pixel CSS pour être rendu sur un écran. Tenez, vous l’avez bien mérité :

Achievement unlocked: master CSS pixel