On peut coller sur une fiche un QR code brut—noir, carré, sans âme—ou en faire un petit objet graphique aux couleurs de la marque. Ce billet raconte comment nous avons fabriqué le second pour nos fiches produit, comment mesurer objectivement sa lisibilité plutôt que de l’espérer, et deux astuces qui changent tout : l’encodage en MAJUSCULES, qui allège le code, et le lien court dynamique, qui le rend modifiable… après impression.
Un QR décoratif, mais qui doit marcher
Sur nos fiches, le QR est décoratif : l’adresse est aussi écrite en clair juste à côté. Le but est de montrer qu’on y a pensé—comme les fiches industrielles du fabricant et de ses concurrents—sans casser l’aspect stylisé. Mais « décoratif » ne veut pas dire « illisible » : un QR qui ne se scanne pas est pire qu’un QR absent, il donne l’impression d’un travail bâclé. D’où une exigence simple : joli ET fonctionnel.
Le moteur : qr-code-styling, sans Node
Le rendu s’appuie sur qr-code-styling—le même moteur que le service en ligne QR Code Monkey. C’est une bibliothèque navigateur ; plutôt que d’installer tout un environnement Node et un moteur de rendu graphique natif (pénible sous Windows), on la fait tourner dans Chromium headless piloté par Playwright, déjà présent sur la machine. Le reste—pilotage, mesures, sortie—est en Python.
Les choix de design retenus :
- corps en points ronds ;
- dégradé radial
#0277BD(bleu, au centre) vers#991E7E(magenta, sur les bords) ; - yeux (les trois coins) en carré arrondi, centre rond, magenta plein ;
- logo central : le « G » du favicon, dans son cadre jaune
#FFED00; - correction d’erreur au niveau H (~30 %), indispensable dès qu’un logo masque une partie du code.
Le contraste avant l’esthétique
Point clé : un scanner ne lit pas la teinte, il lit le contraste de luminance entre les modules sombres et le fond clair. Cela éclaire un détail du dégradé radial qui n’est pas qu’esthétique : le magenta (fort contraste) tombe sur les coins, c’est-à-dire sur les trois yeux—les repères que l’appareil cherche en premier ; le bleu (contraste plus faible) reste au centre, là où le logo couvre déjà les modules. Le plus lisible est placé là où c’est le plus utile.
#FFED00. Or le jaune sur blanc, c’est un contraste d’environ 1 : 1—quasi invisible pour un scanner. Joli à l’œil, catastrophique pour la lecture. Retour au magenta plein.
Mesurer, ne pas deviner
Comment savoir si un QR stylisé se scanne vraiment ? En le décodant par programme. Un petit lecteur (OpenCV + zxing-cpp) rejoue le PNG en simulant les conditions réelles : réduction de résolution et flou gaussien, puis vérifie que le contenu décodé est bien celui attendu.
python tester_lisibilite.py "qr_TO_fr.png" --attendu "HTTPS://WWW.GRANULOSHOP.COM/T/AB12"
Deux enseignements. D’abord, zxing-cpp se comporte à peu près comme un téléphone, tandis qu’OpenCV est bien plus sévère sur les QR décorés—son verdict est donc conservateur, ce qui rassure. Ensuite, et c’est contre-intuitif : le vrai levier de robustesse n’est pas la couleur, c’est la résolution (viser ≥ 1 024 px) accompagnée d’une quiet zone (la marge blanche autour).
L’astuce des MAJUSCULES
La norme QR propose plusieurs modes d’encodage. Le mode Alphanumeric ne connaît que 45 caractères : les chiffres 0-9, les lettres A-Z en MAJUSCULES, et neuf symboles (espace $ % * + - . / :). Il ignore les minuscules—mais en échange, il est bien plus compact : il code deux caractères sur 11 bits, là où le mode Byte (celui qui accepte tout) en dépense 16.
Conséquence directe : une URL écrite tout en majuscules tient en mode Alphanumeric → une version de QR plus basse → des modules plus gros → une lecture plus robuste, à taille de papier égale. Sur l’image ci-dessus, le passage de l’un à l’autre ne change que le mode, et le code « respire » visiblement.
HTTPS) et le domaine sont insensibles à la casse—toujours sûrs. Mais le chemin dépend du serveur : beaucoup sont sensibles à la casse, et /Fiche.html n’y est pas /fiche.html. Sous IIS/Windows (notre cas) le chemin est généralement insensible à la casse—mais à vérifier. Dans le doute, on reste en Byte.
Côté générateur, un mode auto tranche tout seul : si l’URL est entièrement en majuscules et sans accents, il choisit Alphanumeric ; sinon il retombe sur Byte, sans jamais produire un code invalide.
https://www.granuloshop.com/…, pas un HTTPS:// hurlant. Les majuscules ne vivent que dans les octets du code, jamais sous les yeux du client.
Le lien court /T/, ou comment rendre un QR dynamique
Reste un obstacle : mettre en majuscules un vrai chemin (souvent plein de minuscules) n’est pas toujours possible. La solution est de ne pas encoder l’adresse longue, mais un lien court qu’on maîtrise. Chaque page reçoit un code court—Crockford base32 en majuscules, soit 0-9 A-Z privé de I, L, O et U pour éviter les confusions à l’œil—de 1 à 4 caractères. L’adresse encodée devient : HTTPS://WWW.GRANULOSHOP.COM/T/AB12. Courte, tout en majuscules : idéale pour l’Alphanumeric.
Un code imprimé se fige : on l’attribue une fois, séquentiellement, on le stocke, et on ne le renumérote jamais. Une simple table (tiny_code, url) tient l’association. Et surtout, /T/<code> est dynamique : on peut le faire pointer ailleurs sans réimprimer le QR. Deux façons de le réaliser.
Version statique : une page de transfert
La plus simple, pour un site majoritairement statique : on écrit, pour chaque code, une petite page HTML de redirection. Aucun code serveur, cachable à l’infini. Elle combine trois déclencheurs—ceinture et bretelles :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta http-equiv="refresh" content="0; url=https://www.granuloshop.com/TO/fr/products/product_ldp-190.html">
<link rel="canonical" href="https://www.granuloshop.com/TO/fr/products/product_ldp-190.html">
<title>Redirection</title>
<script>location.replace("https://www.granuloshop.com/TO/fr/products/product_ldp-190.html");</script>
</head>
<body>
<p><a href="https://www.granuloshop.com/TO/fr/products/product_ldp-190.html">Continuer</a></p>
</body>
</html>
meta refresh fonctionne sans JavaScript. Le location.replace() ne laisse pas d’entrée dans l’historique : le bouton « Retour » ne revient pas piteusement sur la page de transfert. Le lien visible sauve le cas extrême où tout serait désactivé. Et le canonical + noindex évitent que ces pages-relais polluent le référencement.
Version dynamique : un point d’entrée ASP
Si l’on veut un seul endroit qui interroge la base à la volée—et pouvoir changer une destination sans regénérer le moindre fichier—un mini-script ASP classique suffit :
<%@ Language="VBScript" %>
<%
Dim code, url
code = Request.QueryString("c")
' ... lire dbo.topas_liens_courts, reconstruire l'adresse ...
' url = "https://www.granuloshop.com/TO/" & ligne("url")
If Len(url) > 0 Then
Response.Status = "301 Moved Permanently"
Response.AddHeader "Location", url
Else
Response.Status = "404 Not Found"
End If
Response.End
%>
Le statique coûte zéro serveur et se prête parfaitement à un site de pages fixes ; l’ASP tient sur une seule page et permet un reciblage instantané via la base. Rien n’interdit de mélanger : pages statiques pour le gros du catalogue, endpoint dynamique pour les liens qu’on veut pouvoir rerouter à tout moment.
Router /T/ vers l’ASP : web.config et une regexp
Ce script ne sert à rien tant que le serveur ne l’appelle pas. Or l’adresse imprimée sur la fiche, /T/AB12, ne désigne aucun fichier : c’est à IIS de faire le lien, par une règle de réécriture d’URL posée dans un web.config. Deux décisions comptent plus que le reste.
Le fichier va dans le dossier T\, pas à la racine. Une règle posée dans un sous-dossier ne voit que les URL de ce sous-dossier : le web.config de la racine—avec ses 301 historiques, sa CSP et sa page 404—reste intact. Si le lien court disparaît un jour, on supprime le fichier, et c’est tout. Corollaire pratique : le motif s’applique à l’URL relative au dossier. Pour /T/AB12, la règle reçoit AB12—le T/ est déjà consommé. D’où un motif sans préfixe.
Réécriture, pas redirection. Un Rewrite est interne : le navigateur garde www.granuloshop.com/T/AB12 du premier au dernier instant. Un Redirect exposerait le chemin réel de l’ASP et ferait passer le code par une étape supplémentaire dans l’historique.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<!-- Le dossier T\ ne contient aucun fichier a servir :
il n'existe que pour porter ce web.config. -->
<directoryBrowse enabled="false" />
<rewrite>
<rules>
<!-- Crockford base32 : 0-9 et A-Z prive de I, L, O et U -->
<rule name="LienCourt_T" stopProcessing="true">
<match url="^([0-9A-HJKMNP-TV-Z]{1,4})/?$" />
<action type="Rewrite" url="/TO/ASP/lien_court_relais.asp?c={R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
La regexp dit exactement ce que le générateur produit : l’alphabet Crockford—les quatre lettres ambiguës en moins—et {1,4} pour la longueur. Le /? final tolère la barre oblique de trop : quelqu’un qui recopie l’adresse à la main ne doit pas tomber en 404. Et comme ignoreCase vaut true par défaut, la casse est indifférente—le QR encode AB12 en majuscules pour l’Alphanumeric, mais un lecteur qui tape ab12 arrive au même endroit. Tout ce que la règle ne reconnaît pas retombe sur la 404 du site, sans jamais atteindre l’ASP : un filtre gratuit contre le balayage d’adresses au hasard.
<clear />. La première version de ce fichier en portait un, pour écarter proprement les règles héritées de la racine. Il écartait aussi la redirection canonique granuloshop.com → www.granuloshop.com. Résultat : la page était servie depuis le domaine sans www, tandis que chacune de ses ressources était redirigée vers celui avec—deux origines différentes, donc une CSP default-src 'self' qui bloquait tout : la feuille de style, les données, l’appel AJAX. La page s’affichait nue, sans la moindre erreur serveur. En laissant l’héritage, l’ordre redevient naturel : canonisation d’abord, réécriture interne ensuite.
T\AB12\index.html. Le document par défaut d’IIS fait le travail, et /T/AB12 répond sans une ligne de configuration.
Jusqu’où peut-on s’éloigner ? La distance de lecture
Le même lecteur, poussé plus loin : on simule l’éloignement—moins de pixels par module—en ajoutant ce qu’un téléphone apporte vraiment (léger flou, bruit du capteur, compression JPEG), puis on cherche la taille à laquelle le décodage lâche. Traduite en distance pour un QR imprimé à 25 mm, lu par un téléphone 12 MP, cela donne :

QR classique
N&B carré · sans logo
- Mode
- Byte (URL brute)
- Correction d’erreur
- L — 7 %
- Modules
- 33 × 33

QR design
points, dégradé, logo · lien court en MAJUSCULES
- Mode
- Alphanumeric (MAJUSCULES)
- Correction d’erreur
- H — 30 %
- Modules
- 29 × 29
Distances approximatives ; elles varient avec le téléphone et l’éclairage. Elles sont proportionnelles à la taille d’impression : ×2 à 50 mm, ×4 à 100 mm (une affiche).
Une variante métier : le QR « granulométrique »
Puisque le lien court laisse beaucoup de place libre dans le code, on peut se permettre un rendu plus signifiant. Le style granulométrique garde nos points ronds, mais fait croître leur rayon le long de la diagonale (fins en haut-gauche, gros en bas-droite), à la manière d’une courbe granulométrique—l’objet même de nos instruments. Le code devient une petite signature métier, sans cesser d’être un vrai QR. Côté outil, c’est une simple option, à côté du style classique.
La recette est verrouillée et mesurée : correction H, rayon borné (0,30 → 0,50 module) pour ne jamais empiéter sur le module voisin, yeux magenta, quiet zone de 4 modules. Au banc OpenCV/zxing : 96 %—BON, avec ou sans le logo central (même profil que le QR design). Pousser le curseur plus loin—logo plus gros, gradient plus fort—retombe à 88 % : l’équilibre est ici.
Vectoriel pour l’impression
Dernier détail, mais qui compte pour un PDF : on sort le QR en SVG. C’est net à n’importe quelle résolution d’imprimante, léger, et ça évite toute question de DPI. Une option remplace même le logo raster embarqué par ses formes vectorielles natives, pour un fichier 100 % propre. Le PNG en haute définition reste comme secours pour les usages écran.
Bilan
Un QR « joli » n’est pas incompatible avec un QR qui marche—à condition de mesurer plutôt que de deviner, et de se rappeler que le scanner lit le contraste, pas la teinte. Deux idées à emporter : les MAJUSCULES font basculer l’encodage en mode Alphanumeric et allègent le code ; et un lien court /T/ le rend à la fois compact et modifiable après impression—par une simple page de transfert statique, ou par un point d’entrée ASP quand on veut piloter la destination depuis la base.