Fusionner la phase scan camera (ZXing.Net)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-18 02:24:01 +02:00
co-authored by Claude Opus 5
10 changed files with 649 additions and 5 deletions
+39 -3
View File
@@ -60,18 +60,54 @@ Le décodage EAN-13 se fait en **C# avec [ZXing.Net](https://www.nuget.org/packa
|---|---|
| Décodage EAN-13 propre (380×160) | 0,04 ms/frame |
| **Pire cas** : frame 640×480 bruitée sans code-barres (échec) | 0,53 ms/frame |
| Surcoût du payload PWA | +192 Ko (brotli) |
| Poids de l'assembly `zxing.wasm` seul | +192 Ko (brotli) |
Le pire cas est le chiffre qui gouverne le framerate : la majorité des frames caméra ne contiennent pas de code-barres lisible, et c'est l'échec de décodage qui coûte le plus cher.
⚠️ Ces chiffres sont mesurés en **JIT x64 natif**. En Blazor WASM le code est *interprété* par défaut : compter un facteur ~10-20×, soit ~5-10 ms/frame — largement suffisant pour scanner à 10-15 fps. Activer `<RunAOTCompilation>true</RunAOTCompilation>` ramène ça à 1-2 ms, au prix d'un build nettement plus lent.
### Mesures en WASM réel — faites le 2026-08-18, phase scan
L'estimation « ~5-10 ms/frame en interprété » ci-dessus n'était qu'une extrapolation. Elle a
été **vérifiée dans un vrai navigateur** (Chromium 148, x86_64 de bureau), sur le publish
`Release` du client, en appelant le décodeur depuis la console via `BancEssaiScan` :
| Configuration | Pire cas 640×288 (bande visée) | Pire cas 640×480 |
|---|---|---|
| Publish `Release`, interprété | **4,4 6,4 ms/frame** | 6,7 7,8 ms/frame |
| Build `Debug`, interprété | 17,7 ms/frame | 28,7 ms/frame |
**L'estimation était bonne** : le mode interprété tient largement les 10-15 fps visés (une
frame toutes les 80 ms n'utilise que ~6 % du budget). **Aucune raison d'activer l'AOT**
il reste à confirmer sur un téléphone, sensiblement plus lent qu'un x86_64 de bureau.
⚠️ Ne jamais juger la fluidité sur un build `Debug` : il est **4 à 5× plus lent** que le
`Release`, de quoi conclure à tort qu'il faut l'AOT.
### ⚠️ Le surcoût de payload réel est bien supérieur à 192 Ko
Mesuré par différence entre deux publish `Release` complets (somme brotli de `_framework`) :
**+351 Ko** au total, dont ~4,7 Ko de code applicatif. Le coût imputable à ZXing.Net est donc
d'environ **+346 Ko brotli**, soit **1,8× le poids de son propre assembly**. Le surplus vient
des assemblies BCL que le trimmer ne peut plus retirer :
| Assembly | Delta brotli |
|---|---|
| `zxing.wasm` | +192 495 o |
| `System.Text.RegularExpressions` | +91 237 o (de 7 Ko à 98 Ko : ZXing utilise Regex, tout le moteur reste) |
| `System.Runtime.Numerics` | +30 701 o (nouveau) |
| `System.Private.CoreLib` | +17 586 o |
| divers (`Threading`, `Collections`, `InteropServices`…) | ~14 Ko |
Ne pas reprendre « +192 Ko » comme coût du scan : c'est le poids de l'assembly, pas celui
de la fonctionnalité.
### Pièges à connaître
- **Le JS interop ne disparaît pas.** `getUserMedia` et `<canvas>`/`getImageData` sont des API web sans équivalent C#. Prévoir ~30 lignes de JS maison dont le seul rôle est de pousser un `byte[]` vers C#. Toute la logique de décodage reste en C#.
- **Ne pas perdre de temps à essayer de réduire la taille via un reader ciblé.** Remplacer `MultiFormatReader` par `EAN13Reader` pour aider le trimmer **ne change rien** : mesuré à 192 495 octets à l'octet près dans les deux cas. ZXing.Net n'est pas trim-friendly.
- `RGBLuminanceSource` accepte directement le buffer RGBA du canvas (`BitmapFormat.RGBA32`) — **aucune bibliothèque d'image nécessaire** (pas de SkiaSharp ni ImageSharp).
- Le scan caméra exige **HTTPS** (garanti par YunoHost en prod ; en dev, `localhost` est considéré comme sûr).
- Le scan caméra exige **HTTPS** (garanti par YunoHost en prod ; en dev, `localhost` est considéré comme sûr). Corollaire vérifié : tester le scan depuis un téléphone en pointant l'IP locale du PC (`http://192.168.x.x`) **échouera toujours** — ce n'est pas un contexte sécurisé. Le composant détecte ce cas et le dit explicitement.
- **Une `DOMException` perd son `name` en traversant le pont JS→C#** : `getUserMedia` refusé remonte en C# sous la forme « Permission denied undefined », sans `NotAllowedError`. Or c'est ce nom qui distingue « permission refusée » de « pas de caméra » de « caméra occupée ». Le JS doit donc **attraper l'erreur et renvoyer un code de statut** ; parser le message côté C# ne marche pas.
- **L'import du module JS doit porter une chaîne de requête** (`./js/scanner-camera.js?m=1`). En développement, l'import map généré par Blazor réécrit le chemin vers un nom empreinté que l'API hôte ne sert pas (elle utilise `UseStaticFiles`, qui ignore les points d'entrée empreintés) : l'import part en 404 et le scan ne démarre jamais. La chaîne de requête empêche cette réécriture ; en publication rien n'est réécrit pour ce fichier, donc le comportement est identique.
### Squelette validé