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>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace MaBibli.Client.Scan;
|
||||
|
||||
/// <summary>
|
||||
/// Mesure du coût réel du décodage <b>dans le navigateur</b>.
|
||||
///
|
||||
/// CLAUDE.md ne dispose que de chiffres JIT x64 (0,53 ms/frame dans le pire cas) et d'une
|
||||
/// extrapolation non vérifiée pour le WASM interprété (~5-10 ms/frame). Ce banc d'essai
|
||||
/// donne le chiffre constaté, sur la machine de l'utilisateur, sans caméra ni code-barres.
|
||||
///
|
||||
/// On mesure volontairement le <b>pire cas</b> — une frame de bruit sans code-barres —
|
||||
/// car c'est l'échec de décodage qui coûte le plus cher, et la quasi-totalité des frames
|
||||
/// d'une caméra sont dans ce cas. C'est donc lui qui gouverne le framerate atteignable.
|
||||
///
|
||||
/// Depuis la console du navigateur :
|
||||
/// <code>await DotNet.invokeMethodAsync('MaBibli.Client', 'MesurerScan', 640, 288, 20)</code>
|
||||
/// </summary>
|
||||
public static class BancEssaiScan
|
||||
{
|
||||
[JSInvokable("MesurerScan")]
|
||||
public static double MesurerPireCasMs(int largeur, int hauteur, int iterations)
|
||||
{
|
||||
var rgba = new byte[largeur * hauteur * 4];
|
||||
new Random(1234).NextBytes(rgba);
|
||||
|
||||
// Un tour à blanc : la première exécution paie l'initialisation de ZXing,
|
||||
// qui n'a rien à voir avec le coût par frame en régime établi.
|
||||
IsbnScanner.TryDecode(rgba, largeur, hauteur);
|
||||
|
||||
var depart = Stopwatch.GetTimestamp();
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
IsbnScanner.TryDecode(rgba, largeur, hauteur);
|
||||
}
|
||||
|
||||
return Stopwatch.GetElapsedTime(depart).TotalMilliseconds / iterations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user