Bonoscan.Components 0.31.0

Bonoscan.Components

A reusable C# Blazor document-scanner component. It opens the camera, finds the document in real time (a DocAligner ONNX corner-detection model — no OCR), auto-captures when you hold it steady inside the frame, deskews it with a pure-C# perspective warp, and hands you the result. Also does single-image upload and torch/flashlight where supported.

The library is split into three packages that all sit behind a DI-resolved IScannerEngine:

  • Bonoscan.Components — the shared, host-agnostic UI and engine pieces: the components (DocumentScanner, ScannerView, ResultView, DocumentUpload), ScannerOptions / ScannerState / ScannerResult / ScannerLabels, the one shared scanner.js viewfinder (camera loop + framing + hold-to-capture state machine + overlay) and its ScannerEngineBase, and the pure-C# Warper / OutputDims (homography + bilinear perspective warp over RgbImage, encoded via StbImageWriteSharp). It carries the DocAligner detection contract and image decode/encode in-repo (StbImageSharp / StbImageWriteSharp) — no external detector dependency — but carries no detector implementation itself.
  • Bonoscan.Components.Client — the in-browser engine BrowserScannerEngine for Blazor WebAssembly / MAUI Blazor Hybrid ("wasm" mode). Live detection runs entirely in JS: the shared scanner.js imports the DocAligner Web Worker module from DocDetect.Detection.Browser (onnxruntime-web, off the main thread) — no .NET on the live path; the engine supplies the C# capture warp + upload. The model + onnxruntime-web are embedded in DocDetect.Detection.Browser — this package hosts no model.
  • Bonoscan.Components.Server — the Blazor Server engine ServerScannerEngine: a thin ScannerEngineBase subclass ("http" mode) where the shared scanner.js POSTs each frame to an off-circuit /_bonoscan/detect endpoint (native ONNX, with the DocAligner model embedded in the package) and a /_bonoscan/capture endpoint (server-side warp), so per-frame work never touches the SignalR circuit.

The Server engine's DocAligner model + ONNX Runtime are bundled inside Bonoscan.Components.Server; the WebAssembly client's model + onnxruntime-web ship inside DocDetect.Detection.Browser. Either way the component is self-contained — nothing is fetched from a CDN at runtime.

Install

Reference the package for your host model — Bonoscan.Components.Client for Blazor WebAssembly / MAUI, Bonoscan.Components.Server for Blazor Server (each pulls in the shared Bonoscan.Components):

dotnet add package Bonoscan.Components.Client   # or Bonoscan.Components.Server

The components, scoped CSS, the thin JS interop, and the detection model ship inside the packages as static web assets and are served automatically from _content/… — nothing to copy, no build step.

Use it

1. Register the service (in Program.cs, or MauiProgram.cs for Hybrid):

// Blazor WebAssembly / MAUI Hybrid
using Microsoft.Extensions.DependencyInjection;

builder.Services.AddBonoscanClient(o =>
{
    o.GuideAspect   = 0.707;   // A4 portrait guide; auto-rotates; null = no guide
    o.FillThreshold = 0.60;    // document must fill 60% of the guide before capture
    o.CaptureWidth  = 1240;
    o.CaptureHeight = 1754;    // PNG by default; set o.Mime = "image/jpeg" for JPEG
});
// Blazor Server
builder.Services.AddBonoscanServer(o =>
{
    o.Scanner = s => { s.GuideAspect = 0.707; s.CaptureWidth = 1240; s.CaptureHeight = 1754; };
});

// after the app is built, map the off-circuit endpoints:
app.MapBonoscanDetection();

On Blazor Server the captured image is returned to .NET over the SignalR circuit, so raise the hub's receive limit above its 32 KB default (a scan is larger) or the circuit drops mid-capture:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(o => o.MaximumReceiveMessageSize = 32 * 1024 * 1024);

2. Drop in the component:

@using Bonoscan.Components

<DocumentScanner OnCaptured="OnCaptured" />

@code {
    private void OnCaptured(ScannerResult r)
    {
        // r.Bytes is the image data you own — store / upload / OCR it.
        // r.Mime is the encoded content type (e.g. "image/png"); r.FileName a suggested name.
        // r.ImageUrl is a transient blob: URL for an immediate <img src> / download only.
        // r.FromUpload is true if it came from the file picker rather than the camera.
    }
}

The full-bleed camera, auto-capture, framing guide, torch, upload, and a result view with Scan again / Download are all built in.

Upload only (no camera)

For a flow where the user just uploads an image and the library crops/deskews it — no camera, no <video>/<canvas> — use DocumentUpload (same registration, same ScannerResult):

@using Bonoscan.Components

<DocumentUpload OnCaptured="OnCaptured" />

It accepts click-to-choose and drag-and-drop. Same Options, Labels, OnCaptured/OnError/OnConfirm, Layout, and result-view slots as DocumentScanner.

The scanner engine is a single active instance: don't render DocumentUpload and DocumentScanner on the same page at the same time — use one or the other.

Getting the image data (server / native, not just display)

The ScannerResult from OnCaptured carries the image bytes in r.Bytes. The bytes stay valid after the scanner stops, the component unmounts, or another scan runs.

<DocumentScanner OnCaptured="OnCaptured" />

@code {
    private void OnCaptured(ScannerResult r)
    {
        byte[] bytes = r.Bytes.ToArray();   // the image data: store / upload / OCR
        // r.Mime is the content type; r.FileName a suggested name.
        // r.ImageUrl is a transient blob: URL for an immediate <img>/download only.
    }
}

r.Bytes is materialized at capture and is identical across WebAssembly, Server, and MAUI. r.ImageUrl is a blob: URL owned by the live scanner, valid only until the next capture/rescan; for a result you keep, build a URL from r.Bytes.

3. Make sure your host links the scoped-CSS bundle. The component's styles ride along in your app's {AppName}.styles.css bundle — the Blazor templates already include this line, but if you removed it, add it back:

<link href="YourApp.styles.css" rel="stylesheet" />

Useful parameters

Options (per-instance override of the DI defaults), Labels (a ScannerLabels with all the static UI text), OnCaptured, OnError, OnConfirm, AutoStart, EnableCamera, EnableUpload, ShowTorch, ShowResult (set false to render your own result UI from OnCaptured), ShowDownload, ShowBrand, IdleContent (a custom idle overlay), and Class. The component is driven entirely by these parameters and its callbacks — there is no @ref API; to tear it down, stop rendering it (it releases the camera on dispose). For a fully custom flow, use the headless <ScannerView> below.

Detection is an ONNX model — there are no detection threshold knobs to tune; accuracy is the model's. The auto-capture feel (hold time, steadiness, framing guide) is fully tunable via ScannerOptions (see below).

Customize the built-in UI

Keep the ready-made scanner but reshape it — without forking it:

  • Embed it inline. By default the scanner is a full-viewport overlay; set Layout="ScannerLayout.Inline" to make it an in-flow box you size yourself (via the --scanner-width / --scanner-height variables or your own wrapper), e.g. inside a card or a column.
  • Replace individual pieces. Each chrome element is a replaceable slot, so you can swap just the part you want and keep the rest. The live-scan slots — BrandContent, StatusContent, GuidanceContent (the live hint pill) — receive the scan handle (context.State + its commands). The result slots — ResultContent (the whole result view) and ResultActionsContent (just its buttons) — receive a ScannerResultContext: context.Result for the captured image (Result.ImageUrl, Result.Bytes) and context.Retake / context.Confirm for the actions. Everything is driven from inside the slot — no @ref.
<DocumentScanner Layout="ScannerLayout.Inline" OnConfirm="Use">
    <ResultActionsContent>
        <button @onclick="context.Retake">Retake</button>
        <button @onclick="context.Confirm">Use this scan</button>
    </ResultActionsContent>
</DocumentScanner>

Build your own UI (the building blocks)

There are two ways to use the library — pick per your needs:

  1. Premade<DocumentScanner>, the full styled flow. Override only the colours/sizing via the --scanner-* CSS variables (below).
  2. Building blocks — compose the two single-responsibility components into your own layout, the way DocumentScanner does internally:
    • <ScannerView> — the scan engine only (camera, detection, auto-capture, upload, torch). It renders just the camera stage and raises OnCaptured(ScannerResult); it never renders a result. Its slot context is an IScannerHandle: the live State (Phase, Reason, Progress, Fill, …) plus StartAsync, StopAsync, RescanAsync, ResetAsync, UploadAsync, ToggleTorchAsync, GetCornersAsync.
    • <ResultView> — the result display only. Takes a Result and raises OnRetake / OnConfirm.
@if (_result is null)
{
    <ScannerView AutoStart="true" OnCaptured="r => _result = r" OnError="e => _err = e" />
}
else
{
    <ResultView Result="_result" ShowConfirm="true"
                OnRetake="() => _result = null" OnConfirm="Use" />
}

@code {
    private ScannerResult? _result;
    private string? _err;
    private void Use() { var bytes = _result!.Bytes; /* store / upload / OCR */ }
}

Errors from the camera/detector reach OnError and the host's ILogger, so they're visible in a MAUI WebView and on a Blazor Server circuit, not only the browser console.

Theme it (CSS variables)

Link the token stylesheet once (it defines every --scanner-* default at zero specificity, for both the DOM chrome and the <canvas> viewfinder):

<link rel="stylesheet" href="_content/Bonoscan.Components/bonoscan.css" />

Then override any token from your own :root (it always wins, regardless of load order):

:root {
    --scanner-accent: #3b82f6;       /* primary / lock-on colour      */
    --scanner-accent-ink: #ffffff;   /* text on the accent button     */
    --scanner-amber: #f5b454;        /* "align the frame" colour      */
    --scanner-text: #eef2f4;
    --scanner-danger: #ff6b6b;
    --scanner-bg: #000;              /* letterbox backdrop            */
    --scanner-font: "Inter", system-ui, sans-serif;
    --scanner-mono: "JetBrains Mono", ui-monospace, monospace;
}

Every other visual value is a token too — set only the ones you want; each falls back to the built-in default, so nothing in the components is a hardcoded colour/size you can't reach:

  • Surfaces / borders--scanner-chrome-bg, --scanner-chrome-border, --scanner-veil-bg, --scanner-ghost-bg, --scanner-ghost-bg-hover, --scanner-ghost-border, --scanner-ghost-border-hover.
  • Type sizes--scanner-brand-font-size, --scanner-status-font-size, --scanner-hint-font-size, --scanner-pill-font-size, --scanner-btn-font-size.
  • Radii / spacing--scanner-radius-pill, --scanner-radius-control, --scanner-pad-top, --scanner-pad-bottom, --scanner-pad-edge, --scanner-gap, --scanner-veil-gap, --scanner-btn-pad, --scanner-pill-pad, --scanner-status-pad, and (inline layout) --scanner-width / --scanner-height.
  • Sizing--scanner-control-size (round controls like the torch), --scanner-dot-size.
  • Effects--scanner-blur, --scanner-primary-shadow, --scanner-brand-shadow, --scanner-torch
    • --scanner-torch-glow, and the upload dropzone's --scanner-dropzone-border.

The library uses no ::deep, so for the scoped component chrome these tokens are the only way to retheme it. The live viewfinder drawn on the <canvas> (detected-document quad, framing guide, guidance banner, progress ring) themes from the same variables — plus two canvas-only ones: --scanner-guide (neutral framing guide + ring track, default white) and --scanner-overlay-scrim (guidance-banner bg + dim mask, default black). The canvas reads these once when a scan starts.

Hosting notes

  • Camera needs a secure context (HTTPS or localhost) in every host.
  • Blazor WebAssembly / MAUI — the shared scanner.js runs the camera loop and runs detection in a Web Worker (the DocAligner module from DocDetect.Detection.Browser, onnxruntime-web, off the main thread); .NET handles only the capture warp + upload.
  • In-browser detection speed (WebAssembly / MAUI) — serving over HTTPS (a secure context) lets onnxruntime-web use the WebGPU backend, the fastest path. To also let the WASM fallback run multi-threaded on devices without WebGPU, serve the document cross-origin-isolated (Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp or credentialless). Plain HTTP over a LAN IP gets the slowest single-threaded WASM path.
  • Blazor Server / Web App — fully supported, including prerendering. The shared scanner.js loop runs off the SignalR circuit (POSTing frames to the off-circuit /_bonoscan/detect / /_bonoscan/capture endpoints), so per-frame work never crosses the circuit; only discrete state transitions and the final result do. Use any render mode (InteractiveServer, InteractiveWebAssembly, or InteractiveAuto).
  • .NET MAUI Blazor Hybrid — grant the platform camera permission yourself (Android CAMERA in the manifest + handle the WebView OnPermissionRequest; iOS NSCameraUsageDescription).
  • One scanner instance is active at a time.

Notes

Detection is the DocAligner ONNX model that localises the document's four corners directly — robust to colour, lighting, and background. Portrait phone cameras stream landscape, so you'll see letterbox bars (the whole document stays visible rather than being cropped).

Showing the top 20 packages that depend on Bonoscan.Components.

Packages Downloads
Bonoscan.Components.Server
Blazor Server scan engine for Bonoscan.Components: an off-circuit HTTP detect endpoint (DocAligner ONNX, model embedded in this package), a thin JS live loop that keeps per-frame detection/tracking off the SignalR circuit, and a server-side C# perspective warp at capture. Provides the IScannerEngine implementation the ScannerView / DocumentUpload components consume on Blazor Server.
149
Praxxme.Components.Reactive
Reactive Praxxme Blazor components built on Fluxor for state-driven UIs (loading overlays, reactive list pages).
1
Praxxme.Components.Reactive
Reactive Praxxme Blazor components built on Fluxor for state-driven UIs (loading overlays, reactive list pages).
0

Version Downloads Last updated
0.31.0 197 06/04/2026
0.27.0 3 06/02/2026
0.26.0 3 06/02/2026
0.25.0 2 06/02/2026
0.24.0 2 06/02/2026
0.23.0 2 06/02/2026
0.22.0 4 05/31/2026
0.21.0 2 05/31/2026
0.20.0 3 05/31/2026
0.19.0 4 05/31/2026
0.18.0 2 05/31/2026
0.17.0 3 05/31/2026
0.16.0 2 05/31/2026
0.15.0 1 05/30/2026
0.14.0 2 05/30/2026
0.13.0 3 05/30/2026
0.12.0 5 05/30/2026
0.11.0 4 05/30/2026
0.10.0 2 05/29/2026
0.9.0 2 05/29/2026
0.8.0 1 05/29/2026
0.7.0 0 05/29/2026
0.6.0 2 05/29/2026
0.5.0 1 05/29/2026
0.4.0 2 05/29/2026
0.3.0 2 05/29/2026
0.2.0 2 05/29/2026
0.1.0 2 05/29/2026