Le decodage EAN-13/EAN-8 est fait en C# par ZXing.Net (IsbnScanner), conformement a la decision actee dans CLAUDE.md : le JavaScript ne fournit que les pixels de la camera, aucune logique de decodage ne lui est confiee. Le scan se greffe en amont du flux d'ajout par ISBN existant : des qu'un code est lu et reconnu comme ISBN-13 valide, la recherche de notices est declenchee sans que l'utilisateur ait a retaper quoi que ce soit. La saisie manuelle reste accessible en permanence, y compris quand la camera echoue. Points traites explicitement : - camera arriere par defaut (facingMode "environment", en "ideal" pour ne pas echouer sur un PC sans camera arriere) ; - liberation de la camera a l'annulation, au succes et au demontage du composant ; - cadence limitee a ~12 images/s et decodage restreint a la bande centrale reduite a 640 px de large : inutile de decoder chaque frame en pleine taille ; - messages distincts pour permission refusee, absence de camera, camera occupee et contexte non securise (getUserMedia exige HTTPS ou localhost : une IP de reseau local en http ne marchera jamais, c'est une source de confusion garantie). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
3.5 KiB
C#
103 lines
3.5 KiB
C#
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);
|
|
}
|
|
}
|