L'art du tramage : des images magnifiques en un seul bit
Comment les algorithmes de tramage créent des nuances avec seulement des pixels noirs et blancs, de Floyd-Steinberg au bruit bleu, avec des implémentations concrètes.

Le Return of the Obra Dinn de Lucas Pope est l'un des jeux les plus frappants visuellement jamais créés, et il n'utilise que deux couleurs : le noir et le blanc. Un bit par pixel. Pas de gris, pas de dégradés, pas d'anticrénelage. Chaque pixel est soit allumé, soit éteint. Pourtant, le jeu parvient à afficher un monde 3D entièrement rendu, avec des personnages reconnaissables, une perception de la profondeur et un éclairage atmosphérique, le tout grâce à un tramage astucieux.
Le tramage est la technique qui consiste à disposer des pixels noirs et blancs selon des motifs qui trompent l'œil et lui font voir des teintes intermédiaires. Il existe depuis les débuts de l'infographie et connaît un regain d'intérêt, que ce soit dans le développement de jeux, le web design ou le problème de plus en plus pertinent de l'affichage d'images sur les écrans e-ink. Les algorithmes qui le sous-tendent sont élégants, étonnamment variés et bien plus utiles qu'on ne le pense.
Pourquoi le tramage fonctionne
Votre système visuel ne traite pas les pixels individuellement, il intègre des zones. Quand vous regardez un motif de pixels noirs et blancs alternés, vous ne voyez pas de points distincts : vous voyez du gris. Une zone où 25 % des pixels sont blancs ressemble à un gris à 25 %. Une zone où 75 % sont blancs ressemble à un gris à 75 %. La densité de pixels blancs dans une zone contrôle la luminosité perçue.
C'est le même principe que la trame utilisée en impression de journaux. Ces points de tailles et d'espacements variables créent l'illusion d'un ton continu à partir d'une simple encre noire sur papier blanc. Le tramage numérique en est l'équivalent : il utilise des motifs de pixels au lieu de tailles de points.
Le défi n'est pas de créer des niveaux de gris, ce qui est simple. Le défi est de le faire sans artefacts visibles : pas de bandes (transitions brusques entre motifs), pas de biais directionnels (motifs qui font apparaître des lignes) et pas d'effets de moiré (figures d'interférence entre la trame et la grille de l'écran).
Le seuillage : l'approche naïve
Le tramage le plus simple : on compare la luminosité de chaque pixel à un seuil (généralement 50 %). Plus clair que le seuil, c'est blanc. Plus sombre, c'est noir. On obtient une silhouette très contrastée, sans aucune gradation. C'est utile pour le texte et les dessins au trait, et catastrophique pour les photos ou le rendu 3D.
import numpy as np
from PIL import Image
def threshold_dither(image, threshold=128):
"""Binary threshold — no dithering at all, really."""
gray = np.array(image.convert('L'), dtype=float)
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
Tramage ordonné : des motifs sur une grille
Le tramage ordonné utilise une matrice de seuils (aussi appelée matrice de Bayer) qui fait varier le seuil selon un motif de tuile répétitif. Au lieu de comparer chaque pixel au même seuil, on le compare à une valeur différente de la matrice, en fonction de sa position.
def ordered_dither(image, matrix_size=4):
"""Ordered dithering with Bayer matrix."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
# 4×4 Bayer matrix (values 0-15, normalized to 0-255)
bayer_4x4 = np.array([
[ 0, 8, 2, 10],
[12, 4, 14, 6],
[ 3, 11, 1, 9],
[15, 7, 13, 5]
]) * (255 / 16)
# Tile the matrix across the image
threshold = np.tile(bayer_4x4, (h // 4 + 1, w // 4 + 1))[:h, :w]
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
Le tramage ordonné produit un motif en hachures croisées immédiatement reconnaissable. À basse résolution, la structure régulière de la grille est visible, ce qui donne à l'image un aspect mécanique et rétro. C'est d'ailleurs un choix esthétique assumé dans de nombreux contextes : le motif ordonné d'une matrice de Bayer 8×8 a un rendu distinctif que beaucoup de jeux et d'artistes utilisent délibérément.
La matrice de Bayer est construite avec soin : chaque niveau de seuil ajoute de nouveaux pixels à des positions aussi éloignées que possible des existants. Cela limite les amas et donne la répartition la plus régulière des points à chaque niveau de luminosité. C'est aussi extrêmement rapide, avec une seule comparaison par pixel et aucune dépendance aux voisins, ce qui le rend adapté au rendu temps réel.
La diffusion d'erreur : Floyd-Steinberg et compagnie
La diffusion d'erreur adopte une approche totalement différente. Au lieu d'utiliser une matrice de seuils fixe, elle traite les pixels séquentiellement et répartit l'« erreur » (la différence entre la valeur d'origine du pixel et le résultat quantifié, noir ou blanc) vers les pixels voisins qui n'ont pas encore été traités.
def floyd_steinberg_dither(image):
"""Floyd-Steinberg error diffusion dithering."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
for y in range(h):
for x in range(w):
old_pixel = gray[y, x]
new_pixel = 255.0 if old_pixel > 128 else 0.0
gray[y, x] = new_pixel
error = old_pixel - new_pixel
# Distribute error to neighbors
# Floyd-Steinberg diffusion kernel:
# * 7/16
# 3/16 5/16 1/16
if x + 1 < w:
gray[y, x + 1] += error * 7 / 16
if y + 1 < h:
if x - 1 >= 0:
gray[y + 1, x - 1] += error * 3 / 16
gray[y + 1, x] += error * 5 / 16
if x + 1 < w:
gray[y + 1, x + 1] += error * 1 / 16
return Image.fromarray(gray.astype(np.uint8))
Le tramage de Floyd-Steinberg donne des résultats bien plus naturels que le tramage ordonné. La répartition des pixels ne présente aucune trame visible et gère magnifiquement les dégradés doux. Les points se regroupent de façon organique, pour un rendu plus proche de la trame photographique en impression.
Le revers de la médaille : c'est séquentiel. Chaque pixel dépend de l'erreur des pixels précédemment traités, il est donc difficile à paralléliser. C'est plus lent que le tramage ordonné et plus compliqué à implémenter dans les pipelines de rendu à base de shaders. Pour des images prétraitées, ça n'a pas d'importance. Pour du rendu 3D temps réel à 60 ips, c'est un problème.
Tramage au bruit bleu : le meilleur des deux mondes
Le tramage au bruit bleu est l'état de l'art actuel pour de nombreuses applications. Il utilise une matrice de seuils comme le tramage ordonné (donc il est rapide et parallélisable), mais ses valeurs sont disposées pour produire une distribution de bruit bleu : les points sont placés aléatoirement, avec une contrainte d'espacement minimal, ce qui évite à la fois les amas du bruit blanc et la grille du tramage ordonné.
L'expression « bruit bleu » vient du traitement du signal. Le bruit bleu concentre l'essentiel de son énergie dans les hautes fréquences, comme la lumière bleue. En termes spatiaux, cela signifie que le motif n'a pas de structure à grande échelle (ni grille visible, ni biais directionnel), mais que les points sont régulièrement espacés localement (pas d'amas, pas de trous). C'est le motif que l'on obtient en lançant des fléchettes sur une cible, avec la contrainte que chaque fléchette soit à une distance minimale de toutes les autres.
Générer une bonne matrice de seuils en bruit bleu est étonnamment difficile : c'est un problème d'optimisation qui exige d'itérer jusqu'à ce que le spectre de fréquences corresponde au profil voulu. Mais une fois la matrice obtenue, son utilisation est aussi rapide que le tramage de Bayer : une comparaison par pixel.
L'approche d'Obra Dinn : le tramage sphérique
Le tramage de Return of the Obra Dinn est particulièrement astucieux, car il résout un problème propre au rendu 3D : la stabilité temporelle. Quand un motif de tramage standard est appliqué dans l'espace écran, déplacer la caméra fait « glisser » le motif : il reste fixé à l'écran alors que la géométrie 3D se déplace en dessous. Cela crée un scintillement extrêmement gênant.
La solution de Pope : cartographier le motif de tramage dans l'espace 3D plutôt que dans l'espace écran. Le motif est projeté sur une sphère entourant la caméra, puis échantillonné en fonction de la direction de vue de chaque pixel. Quand la caméra tourne, le motif tourne avec elle. Quand des objets se déplacent dans le monde, leur tramage reste stable, car il dérive de leur position dans le monde et non de leur position à l'écran.
Cette technique est adaptable à toute application 3D qui utilise du tramage ou des effets de trame. L'idée clé : si vous voulez de la stabilité temporelle, vos valeurs de seuil doivent dépendre de quelque chose de stable (position dans le monde, direction de vue) plutôt que de quelque chose qui change à chaque image (coordonnées écran).
Applications pratiques au-delà de l'esthétique rétro
Le tramage ne sert pas qu'à l'esthétique rétro des jeux. Il a de réelles applications pratiques dans le développement moderne.
- Écrans e-ink. Les liseuses et les panneaux e-ink ont une capacité de gris limitée (généralement 16 niveaux). Le tramage est indispensable pour afficher des photos et des dégradés sur ces appareils. Le choix de l'algorithme influe directement sur la lisibilité et la qualité de l'image.
- Réduction de la taille des fichiers. Une image tramée sur 1 bit pèse 1/8 de la taille brute d'une image en niveaux de gris sur 8 bits. Pour la diffusion web d'images décoratives, les PNG tramés peuvent être nettement plus légers que leurs équivalents en niveaux de gris. Certains web designers utilisent délibérément des images tramées, pour des raisons esthétiques et de performance.
- Quantification des couleurs. Quand on réduit une image en couleurs à une palette limitée (GIF, PNG indexé, ou écrans à palette restreinte), le tramage évite les bandes qui apparaissent lorsque des dégradés doux sont convertis en valeurs de couleur discrètes. Le Floyd-Steinberg appliqué dans l'espace des couleurs est précisément la façon dont les encodeurs GIF gèrent ce problème.
- Bandes de dégradé en rendu. Les cibles de rendu à faible précision (8 bits par canal) peuvent produire des bandes visibles dans les dégradés doux. Ajouter un tramage dans l'espace écran (même de ±0,5 d'une valeur) brise ces bandes sans bruit visible. La plupart des moteurs de jeu modernes le font automatiquement.
- Impression thermique et tickets. Les imprimantes thermiques sont intrinsèquement en 1 bit : chaque point est soit chauffé (noir), soit non. Le tramage convertit photos et graphiques en format imprimable. Le choix de l'algorithme détermine si l'image de votre ticket est reconnaissable ou n'est qu'une tache.
Implémenter le tramage dans un shader
Pour les applications temps réel, le tramage ordonné et le tramage au bruit bleu peuvent être implémentés efficacement sous forme de fragment shaders.
// GLSL fragment shader for Bayer 8x8 ordered dithering
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
// Bayer 8x8 matrix lookup via bit manipulation (no texture needed)
float bayer8x8(vec2 pos) {
ivec2 p = ivec2(mod(pos, 8.0));
int index = p.x + p.y * 8;
// Bit-reversal trick for Bayer matrix values
int value = 0;
int x = p.x ^ p.y;
int y = p.y;
for (int i = 0; i < 3; i++) {
value = value * 4 + (x & 1) * 2 + (y & 1);
x >>= 1;
y >>= 1;
}
return float(value) / 64.0;
}
void main() {
vec4 color = texture2D(uTexture, vTexCoord);
float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));
float threshold = bayer8x8(gl_FragCoord.xy);
float dithered = step(threshold, luminance);
gl_FragColor = vec4(vec3(dithered), 1.0);
}
Pour le bruit bleu, on précalcule généralement la texture de bruit et on l'échantillonne dans le shader. La texture peut être répétée en mosaïque : une texture de bruit bleu de 64×64 ou 128×128 couvre tout l'écran par répétition. Le coût par pixel se limite à un échantillonnage de texture et une comparaison, pratiquement gratuit sur les GPU modernes.
Choisir le bon algorithme
Chaque approche de tramage a un cas d'usage où elle est le meilleur choix.
- Seuillage : texte, dessins au trait, graphismes très contrastés où le détail compte plus que la gamme tonale.
- Ordonné (Bayer) : rendu temps réel où l'on veut un rendu rétro ou de trame délibéré. Rapide, parallélisable, adapté aux GPU.
- Floyd-Steinberg : traitement d'images statiques où la qualité prime. Idéal pour les photos et les images à dégradés doux.
- Bruit bleu : rendu temps réel où l'on veut des résultats naturels sans motifs visibles. Le meilleur choix polyvalent pour les applications modernes.
- Sphérique/espace monde : rendu 3D où la stabilité temporelle est critique. Indispensable pour le rendu 3D en 1 bit comme celui d'Obra Dinn.
Le tramage est un de ces sujets où la simplicité du concept (« disposer des points pour ressembler à des ombrages ») masque la profondeur des choix d'implémentation. Chaque algorithme représente un compromis différent entre qualité, vitesse et caractère visuel. Et à une époque où les écrans e-ink, les connexions à faible débit et les esthétiques rétro sont tous pertinents, comprendre ces compromis est plus utile que jamais.


