Produire des images de remplacement
Afficher une image provisoire là où la vraie manque encore, dans une maquette ou un catalogue incomplet.
Pas besoin d’IA
GratuitInstantanéVos données ne sortent pas
Le verdict
L’échelle de cette fiche n’a qu’un seul niveau, et c’est son intérêt : le besoin est décoratif et arbitraire, ce qui est précisément le cas où une fonction de hachage donne la réponse exacte plutôt qu’une approximation. Rien ne sort du processus, le coût marginal est nul, et le résultat se teste unitairement au caractère près — le même balisage attendu figure en toutes lettres dans le test Python et dans le test JavaScript, ce qui épingle les deux implémentations l’une à l’autre. La sortie de cette fiche ne se fait pas par le haut : elle se fait en obtenant la vraie image.
SVG déterministe dérivé d’un hachage de l’identifiant
Pas besoin d’IA
Règle et algorithme classiqueN0GratuitInstantané
Python
"""
Une image de remplacement sans modèle : un SVG dérivé d'un hachage de
l'identifiant.
Niveau N0. Aucun fichier écrit et aucun octet téléchargé : la fonction
renvoie une chaîne de balisage qu'un gabarit peut incorporer ou qu'un
gestionnaire de requête peut servir.
Deux propriétés portent toute l'approche.
Elle est déterministe. Le même identifiant donne toujours exactement la même
image, en Python comme en JavaScript, tant que ce code reste inchangé.
Son arithmétique est entière. La teinte, le chroma et les positions des
cellules sont calculés sans une seule division qui pourrait arrondir
autrement d'un environnement d'exécution à l'autre, et c'est ce qui permet à
la version Python et à la version JavaScript de s'accorder au caractère près.
"""
import re
# FNV-1a, the same constants as the rest of the catalogue, so that identifiers
# hash identically wherever they are hashed.
FNV_OFFSET = 2166136261
FNV_PRIME = 16777619
MASK32 = 0xFFFFFFFF
# Characters XML 1.0 does not allow anywhere in a document, escaped or not: C0
# controls other than tab and line breaks, lone surrogates, U+FFFE and U+FFFF.
NOT_XML = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\ud800-\udfff\ufffe\uffff]")
GRID = 5 # cells per side
COLUMNS = 3 # independent columns; the remaining two mirror them
def stable_hash(text: str) -> int:
"""
FNV-1a on 32 bits, fed code points rather than UTF-8 bytes: identical to
the reference on ASCII, different from it on any other character.
Not the built-in hash: that one is salted per process, so it would give a
different image every time the server restarted.
"""
digest = FNV_OFFSET
for char in text:
digest = ((digest ^ ord(char)) * FNV_PRIME) & MASK32
return digest
def _hex_colour(hue: int, saturation: int, lightness: int) -> str:
"""HSL to hexadecimal, in integers only, all three arguments in percent."""
chroma = (255 * saturation * (100 - abs(2 * lightness - 100))) // 10000
edge = (chroma * (60 - abs((hue % 120) - 60))) // 60
floor = (255 * lightness) // 100 - chroma // 2
wheel = ((chroma, edge, 0), (edge, chroma, 0), (0, chroma, edge),
(0, edge, chroma), (edge, 0, chroma), (chroma, 0, edge))
red, green, blue = wheel[(hue // 60) % 6]
return "#%02x%02x%02x" % (red + floor, green + floor, blue + floor)
def _escape(text: str) -> str:
"""
The identifier ends up inside an attribute, so it is markup until escaped.
Escaping is not enough: a control character pasted from a spreadsheet has
no escaped form in XML 1.0 and would make the whole SVG unreadable, so it
is dropped. The ampersand goes first, or it would escape the escapes.
"""
text = NOT_XML.sub("", text)
text = text.replace("&", "&").replace("<", "<").replace(">", ">")
return text.replace('"', """)
def placeholder_svg(identifier: str, size: int = 240) -> str:
"""
Build a symmetric two-tone figure on a tinted ground.
The high bits of the hash choose the hue, the low ones switch cells on and
off. Mirroring the left columns onto the right ones costs one line and is
what makes the result read as a mark rather than as noise.
"""
digest = stable_hash(identifier)
hue = (digest >> 16) % 360
cell = size // GRID
margin = (size - cell * GRID) // 2
parts = [
f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}"'
f' viewBox="0 0 {size} {size}" role="img" aria-label="{_escape(identifier)}">',
f'<rect width="{size}" height="{size}" fill="{_hex_colour(hue, 45, 90)}"/>',
]
ink = _hex_colour(hue, 55, 42)
for row in range(GRID):
for column in range(GRID):
mirrored = min(column, GRID - 1 - column)
if not (digest >> (row * COLUMNS + mirrored)) & 1:
continue
x = margin + column * cell
y = margin + row * cell
parts.append(
f'<rect x="{x}" y="{y}" width="{cell}" height="{cell}" fill="{ink}"/>'
)
parts.append("</svg>")
return "".join(parts)JavaScript
/**
* Une image de remplacement sans modèle : un SVG dérivé d'un hachage de
* l'identifiant.
*
* Niveau N0. Aucun fichier écrit et aucun octet téléchargé : la fonction
* renvoie une chaîne de balisage qu'un gabarit peut incorporer ou qu'un
* gestionnaire de requête peut servir.
*
* Deux propriétés portent toute l'approche.
*
* Elle est déterministe. Le même identifiant donne toujours exactement la même
* image, en Python comme en JavaScript, tant que ce code reste inchangé.
*
* Son arithmétique est entière. La teinte, le chroma et les positions des
* cellules sont calculés sans une seule division qui pourrait arrondir
* autrement d'un environnement d'exécution à l'autre, et c'est ce qui permet à
* la version Python et à la version JavaScript de s'accorder au caractère près.
*/
// FNV-1a, the same constants as the rest of the catalogue, so that identifiers
// hash identically wherever they are hashed.
const FNV_OFFSET = 2166136261;
const FNV_PRIME = 16777619;
// Characters XML 1.0 does not allow anywhere in a document, escaped or not: C0
// controls other than tab and line breaks, lone surrogates, U+FFFE and U+FFFF.
const NOT_XML = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/gu;
const GRID = 5; // cells per side
const COLUMNS = 3; // independent columns; the remaining two mirror them
/**
* FNV-1a on 32 bits, fed code points rather than UTF-8 bytes: identical to
* the reference on ASCII, different from it on any other character.
*
* Math.imul is what keeps it exact: a plain multiplication on numbers this
* large loses precision past 2^53 and would silently drift away from the
* integers Python computes.
*/
export function stableHash(text) {
let digest = FNV_OFFSET;
for (const char of text) {
digest = Math.imul(digest ^ char.codePointAt(0), FNV_PRIME) >>> 0;
}
return digest;
}
/** HSL to hexadecimal, in integers only, all three arguments in percent. */
function hexColour(hue, saturation, lightness) {
const chroma = Math.floor((255 * saturation * (100 - Math.abs(2 * lightness - 100))) / 10000);
const edge = Math.floor((chroma * (60 - Math.abs((hue % 120) - 60))) / 60);
const floor = Math.floor((255 * lightness) / 100) - Math.floor(chroma / 2);
const wheel = [[chroma, edge, 0], [edge, chroma, 0], [0, chroma, edge],
[0, edge, chroma], [edge, 0, chroma], [chroma, 0, edge]];
const channels = wheel[Math.floor(hue / 60) % 6];
return `#${channels.map((v) => (v + floor).toString(16).padStart(2, '0')).join('')}`;
}
/**
* The identifier ends up inside an attribute, so it is markup until escaped.
*
* Escaping is not enough: a control character pasted from a spreadsheet has
* no escaped form in XML 1.0 and would make the whole SVG unreadable, so it
* is dropped. The ampersand goes first, or it would escape the escapes.
*/
function escape(text) {
return text.replace(NOT_XML, '').replaceAll('&', '&').replaceAll('<', '<')
.replaceAll('>', '>').replaceAll('"', '"');
}
/**
* Build a symmetric two-tone figure on a tinted ground.
*
* The high bits of the hash choose the hue, the low ones switch cells on and
* off. Mirroring the left columns onto the right ones costs one line and is
* what makes the result read as a mark rather than as noise.
*/
export function placeholderSvg(identifier, size = 240) {
const digest = stableHash(identifier);
const hue = (digest >>> 16) % 360;
const cell = Math.floor(size / GRID);
const margin = Math.floor((size - cell * GRID) / 2);
const parts = [
`<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}"` +
` viewBox="0 0 ${size} ${size}" role="img" aria-label="${escape(identifier)}">`,
`<rect width="${size}" height="${size}" fill="${hexColour(hue, 45, 90)}"/>`,
];
const ink = hexColour(hue, 55, 42);
for (let row = 0; row < GRID; row += 1) {
for (let column = 0; column < GRID; column += 1) {
const mirrored = Math.min(column, GRID - 1 - column);
if (!((digest >>> (row * COLUMNS + mirrored)) & 1)) continue;
const x = margin + column * cell;
const y = margin + row * cell;
parts.push(`<rect x="${x}" y="${y}" width="${cell}" height="${cell}" fill="${ink}"/>`);
}
}
parts.push('</svg>');
return parts.join('');
}Essayer
interactifLe code ci-dessus tourne dans votre navigateur. Écrivez dans le champ : le résultat se refait sous vos yeux.
Tapez un identifiant : une référence produit, une adresse, un nom de client. La même chaîne donne toujours exactement la même image.
Ou partez d’un exemple
La saisie libre et les autres exemples ont besoin de JavaScript. L’exemple affiché ci-dessous, lui, a été calculé à la construction du site.
Ce qu’il renvoie
Teinte 44° — jaune
24 rectangles pleins en 2 couleurs, et rien à télécharger : le balisage entier tient dans la page.
L’image produite
« Boulangerie Martin » donnera cette image-là, en JavaScript comme en Python, tant que ce code ne change pas.
Là, ça casse
Ce que ça implique
- Vos données ne sortent pas
- Résultat toujours identique
- Se teste unitairement, cas par cas
- Aucun fournisseur dans la boucle
- Empreinte négligeable
Ce que cela ajoute à votre périmètre réglementaire
- Aucun périmètre spécifique ajouté : l’image est calculée dans votre processus, rien n’est téléchargé et rien n’est transmis
- L’identifiant reçu est recopié dans l’attribut aria-label du balisage servi : ce qu’il contient est publié avec la page
Point de rupture
L’image ne dit rien de ce qu’elle remplace : le hachage décide de tout, le sens de l’identifiant de rien. Le test le montre sur trois points — « red velvet sofa » ne tombe pas dans les rouges, sofa-1 et sofa-2 reçoivent des teintes éloignées alors qu’il s’agit du même canapé, et la sortie n’est jamais que des rectangles pleins en deux couleurs.
Quand passer au niveau suivant
Il n’y a pas de niveau au-dessus. Le jour où il vous faut une vraie photographie de l’objet, ce n’est plus un remplaçant qu’on vous demande, et aucune approche de cette fiche n’y répond.
Les quatre options
- RecommandéN0SVG déterministe dérivé d’un hachage de l’identifiantGratuit · Instantané
- N1Niveau sans objet ici
- N2Niveau sans objet ici
- N3Niveau sans objet ici
Les autres options
N1Niveau sans objet ici
Niveau sans objet ici
Modèle classique légerN1
Il n’y a rien à apprendre. Un modèle classique s’entraîne à prédire une bonne réponse à partir d’exemples ; ici aucune image n’est plus juste qu’une autre pour un identifiant donné, la sortie voulue étant décorative et arbitraire. Il n’existe donc pas de jeu étiqueté possible, et un modèle appris n’ajouterait qu’un fichier de poids à transporter là où une fonction de hachage donne déjà une réponse stable.
N2Niveau sans objet ici
Niveau sans objet ici
Petit modèle spécialisé auto-hébergéN2
Un modèle de diffusion auto-hébergé pour produire quelques rectangles colorés est l’exemple même du sur-dimensionnement, et il perd la propriété dont cette fiche a besoin : PyTorch, sur lequel repose la bibliothèque de diffusion publiée par huggingface, ne garantit pas des résultats reproductibles d’une version à l’autre, d’une plateforme à l’autre, ni entre processeur et carte graphique, même à graine identique. Il produit en plus ce qu’un remplaçant ne doit pas être : une image vraisemblable de l’objet absent.
N3Niveau sans objet ici
Niveau sans objet ici
API de LLM généralisteN3
Même disproportion, et deux défauts de plus : chaque vignette manquante devient un appel facturé chez un tiers, et le modèle qui la dessine a une date de fin. Le fournisseur pris en exemple a arrêté DALL·E 2 et DALL·E 3 le 12 mai 2026 : un catalogue dont les remplaçants venaient de ces modèles ne peut plus les redemander.
Pour aller plus loin
- W3C — SVG 2, la spécification du format
- FNV — la page de référence de Landon Curt Noll sur cette famille de hachages
- W3C — SVG Accessibility API Mappings, le rôle img et le nom accessible
- Wikipédia — Identicon, l’image dérivée d’un hachage
Source des chiffres
- PyTorch — Reproducibility
- Hugging Face — Diffusers, Reproducibility
- OpenAI — Deprecations
- OpenAI — Pricing, image generation models
- W3C — Extensible Markup Language (XML) 1.0
Métadonnées
Information générale, tenue à jour au mieux. Ce n’est pas un avis juridique. Faites valider votre situation par un professionnel.