Files
mabibli/MaBibli.Client/Scan/IsbnScanner.cs
T
mathieuandClaude Opus 5 f7ad9582f6 Ajouter le scan camera du code-barres ISBN
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>
2026-08-17 23:32:05 +02:00

54 lines
2.1 KiB
C#

using ZXing;
using ZXing.Common;
namespace MaBibli.Client.Scan;
/// <summary>
/// Décodage d'un code-barres EAN-13 / EAN-8 à partir d'un buffer de pixels RGBA.
///
/// Décision actée dans CLAUDE.md : le décodage reste **entièrement en C#** (ZXing.Net),
/// le JavaScript ne sert qu'à fournir les pixels de la caméra. Cette classe ne dépend
/// donc ni du navigateur, ni de Blazor : elle est réutilisable telle quelle hors WASM
/// (app native, décodage d'une photo côté serveur, scan en masse).
/// </summary>
public static class IsbnScanner
{
// ZXing.Net n'est pas thread-safe sur ce lecteur, mais le WASM est mono-thread
// et la boucle de scan est séquentielle : une instance partagée suffit et évite
// de reconstruire les hints à chaque frame.
private static readonly MultiFormatReader Reader = new()
{
Hints = new Dictionary<DecodeHintType, object>
{
[DecodeHintType.POSSIBLE_FORMATS] = new List<BarcodeFormat>
{
BarcodeFormat.EAN_13, BarcodeFormat.EAN_8,
},
[DecodeHintType.TRY_HARDER] = true,
},
};
/// <summary>
/// Tente de décoder un code-barres. Renvoie le texte brut du code, ou <c>null</c>
/// si la frame n'en contient aucun de lisible — cas très majoritaire en usage réel.
/// </summary>
/// <param name="rgba">Buffer brut issu de <c>ctx.getImageData(...).data</c> (4 octets par pixel).</param>
public static string? TryDecode(byte[] rgba, int largeur, int hauteur)
{
ArgumentNullException.ThrowIfNull(rgba);
if (largeur <= 0 || hauteur <= 0 || rgba.Length < largeur * hauteur * 4)
{
return null;
}
// RGBLuminanceSource consomme directement le buffer du canvas :
// aucune bibliothèque d'image nécessaire (ni SkiaSharp, ni ImageSharp).
var source = new RGBLuminanceSource(
rgba, largeur, hauteur, RGBLuminanceSource.BitmapFormat.RGBA32);
// La version .NET de ZXing renvoie null sur échec (pas d'exception).
return Reader.decode(new BinaryBitmap(new HybridBinarizer(source)))?.Text;
}
}