Basculer le décodage des codes-barres de ZXing.Net vers zbar

L'ISBN 9782846391009 n'était pas décodé par l'application, alors qu'il
l'était par une application d'essai utilisant zbar, sur le même livre et
le même appareil.

⚠️ Le raisonnement qui avait écarté cette piste était faux : CLAUDE.md
concluait que « le décodeur n'y est pour rien » à partir de mesures de
VITESSE (4-6 ms/frame). La vitesse ne dit rien du taux de réussite.

Le décodage quitte donc le C# pour zbar compilé en WebAssembly, dont les
assets viennent du paquet ZBar.Blazor.

Ce que la bascule apporte, au-delà du code lu :
- plus aucun pixel ne traverse le pont JS→C# (un byte[] par frame avant),
  seule la valeur décodée le fait ;
- l'image ENTIÈRE est analysée, à 960 px, là où l'ancienne version
  recadrait sur la bande centrale à 640 px pour alléger ce transfert —
  c'était la seconde cause possible des codes non lus, elle disparaît ;
- zbar.wasm est chargé À LA DEMANDE, à la première ouverture du scanner.

⚠️ Le composant ZBarCamera du paquet n'est PAS utilisé : il ouvre la
caméra lui-même et avale les erreurs. On y perdrait les messages qui
distinguent permission refusée, absence de caméra, caméra occupée et
contexte non sécurisé, ainsi que facingMode environment (caméra arrière)
et l'indication de résolution. Seuls zbar.js et zbar.wasm sont empruntés.

Poids, comparaison de deux publish Release complets (somme brotli) :
  _framework  3 281 558 → 2 978 700 o   (−296 Kio au démarrage)
  total       3 281 558 → 3 125 541 o   (−152 Kio)
L'écart vient de la traîne que ZXing imposait au trimmer :
System.Text.RegularExpressions retombe de 98 374 à 7 137 o, et
System.Runtime.Numerics disparaît.

⚠️ Coût assumé : six tests de décodage disparaissent avec IsbnScanner,
le décodeur n'étant plus en C#. Le décodage a été vérifié dans le
navigateur sur un EAN-13 rendu en canvas — 9782846391009 ressort bien en
ZBAR_EAN13 — mais cela reste une vérification, pas un garde-fou.
À confirmer avec le livre en main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-19 00:51:56 +02:00
co-authored by Claude Opus 5
parent 718df2c986
commit 278d6579f7
8 changed files with 269 additions and 438 deletions
-102
View File
@@ -1,102 +0,0 @@
using MaBibli.Client.Scan;
using ZXing.Common;
using ZXing.OneD;
namespace MaBibli.Tests;
/// <summary>
/// Tests du décodage pur (aucune caméra, aucun navigateur) : on encode un EAN-13 avec
/// ZXing, on le convertit en buffer RGBA comme le ferait <c>getImageData</c>, et on
/// vérifie le trajet aller-retour. Aucune image externe n'est nécessaire.
/// </summary>
public class IsbnScannerTests
{
// Les ISBN de vérification du projet (voir CLAUDE.md).
[Theory]
[InlineData("9782070612758")] // Le Petit Prince
[InlineData("9782253004226")] // Germinal
[InlineData("9782080704092")] // Le Horla
[InlineData("9780262033848")] // Introduction to Algorithms
public void Decode_un_EAN13_genere(string isbn)
{
var (rgba, largeur, hauteur) = ImageEan13(isbn);
Assert.Equal(isbn, IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Decode_aussi_un_EAN8()
{
// Pas un ISBN, mais le format est dans les hints : le décodeur doit le lire,
// c'est la couche appelante qui écarte les codes non-ISBN.
var matrice = new EAN8Writer().encode("96385074", ZXing.BarcodeFormat.EAN_8, 260, 160);
var (rgba, largeur, hauteur) = VersRgba(matrice);
Assert.Equal("96385074", IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Renvoie_null_sur_une_frame_sans_code_barres()
{
// Cas très majoritaire en usage réel : la caméra filme autre chose.
// ZXing doit rendre null, jamais lever d'exception.
const int largeur = 640, hauteur = 480;
var rgba = new byte[largeur * hauteur * 4];
var alea = new Random(1234);
alea.NextBytes(rgba);
Assert.Null(IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Renvoie_null_sur_une_image_uniforme()
{
var rgba = new byte[320 * 200 * 4];
Array.Fill(rgba, (byte)255);
Assert.Null(IsbnScanner.TryDecode(rgba, 320, 200));
}
[Theory]
[InlineData(0, 100)]
[InlineData(100, 0)]
[InlineData(-5, 100)]
public void Renvoie_null_sur_des_dimensions_absurdes(int largeur, int hauteur)
=> Assert.Null(IsbnScanner.TryDecode(new byte[4000], largeur, hauteur));
[Fact]
public void Renvoie_null_si_le_buffer_est_trop_court()
{
// Une frame tronquée ne doit pas faire tomber la boucle de scan.
Assert.Null(IsbnScanner.TryDecode(new byte[100], 640, 480));
}
private static (byte[] Rgba, int Largeur, int Hauteur) ImageEan13(string isbn)
=> VersRgba(new EAN13Writer().encode(isbn, ZXing.BarcodeFormat.EAN_13, 380, 160));
/// <summary>
/// Convertit une BitMatrix en buffer RGBA, exactement la forme que le canvas
/// du navigateur fournit à C# (4 octets par pixel, opaque).
/// </summary>
private static (byte[] Rgba, int Largeur, int Hauteur) VersRgba(BitMatrix matrice)
{
var largeur = matrice.Width;
var hauteur = matrice.Height;
var rgba = new byte[largeur * hauteur * 4];
for (var y = 0; y < hauteur; y++)
{
for (var x = 0; x < largeur; x++)
{
var valeur = matrice[x, y] ? (byte)0 : (byte)255;
var i = ((y * largeur) + x) * 4;
rgba[i] = valeur;
rgba[i + 1] = valeur;
rgba[i + 2] = valeur;
rgba[i + 3] = 255;
}
}
return (rgba, largeur, hauteur);
}
}