Circuids.Frame.Blazor
1.0.0-preview.12
Prefix Reserved
dotnet add package Circuids.Frame.Blazor --version 1.0.0-preview.12
NuGet\Install-Package Circuids.Frame.Blazor -Version 1.0.0-preview.12
<PackageReference Include="Circuids.Frame.Blazor" Version="1.0.0-preview.12" />
<PackageVersion Include="Circuids.Frame.Blazor" Version="1.0.0-preview.12" />
<PackageReference Include="Circuids.Frame.Blazor" />
paket add Circuids.Frame.Blazor --version 1.0.0-preview.12
#r "nuget: Circuids.Frame.Blazor, 1.0.0-preview.12"
#:package Circuids.Frame.Blazor@1.0.0-preview.12
#addin nuget:?package=Circuids.Frame.Blazor&version=1.0.0-preview.12&prerelease
#tool nuget:?package=Circuids.Frame.Blazor&version=1.0.0-preview.12&prerelease
Circuids.Frame.Blazor
Circuids.Frame.Blazor is the Blazor integration for Frame. It registers browser-backed implementations of Frame's core contracts so Razor components can access the camera, detect documents, capture with perspective correction, and export PDFs — all client-side.
Install this package in the Blazor app that runs your UI. Shared libraries can reference Circuids.Frame directly, or rely on this package transitively from the host app.
Install
dotnet add package Circuids.Frame.Blazor --version 1.0.0-preview.12
Targets net10.0.
Register Services
Blazor WebAssembly
using Circuids.Frame;
using Circuids.Frame.Blazor;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Registers Core + the Blazor integration (camera, detection, capture, PDF) in one call.
builder.Services.AddFrameForBlazor(options =>
{
options.AutoCapture.Enabled = true;
options.CaptureQuality.JpegQuality = 90;
});
await builder.Build().RunAsync();
Blazor Server
using Circuids.Frame;
using Circuids.Frame.Blazor;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Registers Core + the Blazor integration in one call.
builder.Services.AddFrameForBlazor();
var app = builder.Build();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
Use The Scanner
@using Circuids.Frame
@using Circuids.Frame.Blazor
<ImageScanner Options="@options"
OnDetectionChanged="@OnDetectionChangedAsync"
OnCaptureCompleted="@OnCaptureCompletedAsync"
OnCameraStateChanged="@OnCameraStateChangedAsync" />
@code {
private FrameOptions options = new();
private Task OnDetectionChangedAsync(DetectionStateChangedEventArgs args) => Task.CompletedTask;
private Task OnCaptureCompletedAsync(CaptureCompletedEventArgs args) => Task.CompletedTask;
private Task OnCameraStateChangedAsync(CameraStateChangedEventArgs args) => Task.CompletedTask;
}
ImageScanner composes CameraPreview and DetectionOverlay internally — one component, not three. CameraPreview and DetectionOverlay are also available standalone for advanced use cases.
Scan guide and styling
ImageScanner renders a live highlighted document quadrilateral derived from detected corners, with alignment-state color feedback (green when aligned, orange while searching). It also shows engine status: a "Loading detection engine…" indicator while OpenCV.js loads, and a visible error block with a Retry button if the engine fails to load.
All styles ship as component-isolated CSS (co-located .razor.css files). They are bundled automatically through your app's standard .styles.css — no stylesheet link is required, and scoped selectors prevent collisions with your CSS.
The overlay and corner-editor colors are configurable via FrameOptions.Appearance — set the detection/alignment quad stroke and fill, and the corner-editor drag-handle fill and border, using any CSS color:
options.Appearance.DetectionStroke = "#ff6600"; // quad stroke while searching
options.Appearance.DetectionFill = "rgba(255, 102, 0, 0.15)";
options.Appearance.AlignmentStroke = "#00cc66"; // quad stroke when aligned
options.Appearance.AlignmentFill = "rgba(0, 204, 102, 0.18)";
options.Appearance.HandleStroke = "#ffffff"; // corner-editor handle fill
options.Appearance.HandleBorder = "#000000"; // corner-editor handle border
Defaults preserve the built-in orange/green visuals.
Component sizing
ImageScanner, CameraPreview, and ImageScanEditor accept optional sizing parameters applied via inline style, so they win over scoped CSS without !important:
| Parameter | Type | Purpose |
|---|---|---|
Width |
string? |
CSS width for the component root. Defaults to the scoped width (100%). |
Height |
string? |
CSS height for the component root. Defaults to auto. |
MaxWidth |
string? |
CSS max-width for the component root. |
MaxHeight |
string? |
CSS max-height for the component root. |
ObjectFit |
ObjectFit |
How the media (video/image) is fitted within the stage. None (natural aspect ratio), Fill, Contain, Cover. |
A fixed Height constrains the stage, and ObjectFit controls how the media fills it. The detection quad and editor handles map to the displayed media box, so they stay aligned under Fill/Contain/Cover:
<ImageScanner Width="100%" Height="80dvh" ObjectFit="ObjectFit.Cover" ... />
Note: a fixed
Heightrequires an explicitObjectFit— the overlay and media must share the sameObjectFitfor the detection quad to stay aligned.
Render-mode safety: all Frame components initialize exclusively in
OnAfterRenderAsync(firstRender), so they work in Blazor Server, WebAssembly, and Auto render modes without prerendering issues.
Manual capture
CaptureAsync() captures immediately using the last detected corners. If detection has not produced corners yet — the camera just opened, or the document was briefly lost — it falls back to capturing the full frame instead of doing nothing. A capture with no usable corners can be corrected afterwards via the capture review or ImageScanEditor.
During a manual capture the detection loop is paused so the perspective warp is not queued behind live detection on the JS thread; detection resumes automatically when the capture completes (and stays paused while a capture review is showing).
Capture review (post-capture corner correction)
When auto-detection gets a side wrong, enable the capture review: every capture freezes the frame and shows an interactive corner editor over the frozen image, seeded with the auto-detected corners.
<ImageScanner Options="@options"
EnableCaptureReview="true"
OnCaptureCompleted="@OnCaptureCompletedAsync" />
With EnableCaptureReview, capture freezes the live frame, raises OnCaptureReview (CaptureReviewEventArgs: frozen ImageBytes, InitialCorners, as-taken Result), and shows a built-in corner editor. Confirm with adjusted corners to finalize a corrected result, or call KeepOriginalAsync() to accept the capture as taken. Correction happens over the frozen image, not the live feed.
Attached-image corner editor (ImageScanEditor)
Corrects the document quad of an image the user already has — a gallery photo, an uploaded file, a PDF page screenshot. Supply the encoded bytes, drag the four corners over the static image, and capture. The result is the same FrameScanResult the camera path produces. ConfirmAsync() works with the untouched default selection — dragging is optional, not required.
<InputFile OnChange="@OnImageSelectedAsync" accept="image/png,image/jpeg" />
@if (_imageBytes is not null)
{
<ImageScanEditor @ref="_editor"
ImageBytes="@_imageBytes"
OnCaptureCompleted="@OnCaptureCompletedAsync" />
<button @onclick="() => _editor.ConfirmAsync()">Correct & Capture</button>
}
@code {
private ImageScanEditor? _editor;
private byte[]? _imageBytes;
private async Task OnImageSelectedAsync(InputFileChangeEventArgs e)
{
await using var stream = e.File!.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
_imageBytes = ms.ToArray();
}
}
ImageBytes— the encoded image to correct (JPEG/PNG). Required.Dimensions— pixel dimensions ofImageBytes. Optional: when omitted, the editor derives intrinsic dimensions from the loaded image, so you never decode the image yourself.Options,InitialCorners,OnCaptureCompleted,ConfirmAsync()— shared corner-editor semantics. Confirming without any edit captures withInitialCornerswhen supplied, otherwise the editor's default inset quad.- Capture goes through
ICapturePipeline.CaptureAsync(DocumentImage, ...)— the same warp and enhancement as camera captures.
Image selection (picker, gallery, drag-and-drop) is intentionally not shipped — provide your own InputFile or equivalent, and pass the bytes to the editor. The same editor powers the scanner's capture review, so corner behavior is identical between camera and image sources.
Camera Permission
RequestPermissionAsync never throws for expected camera failures — denial, missing hardware, busy device, or an unsupported browser are all returned as values:
var permission = await scanner.RequestPermissionAsync();
switch (permission)
{
case CameraPermission.Granted:
break; // camera re-opened automatically
case CameraPermission.Denied:
break; // guide the user to update browser settings
case CameraPermission.NotSupported:
break; // camera API unavailable or insecure context
case CameraPermission.NotAvailable:
break; // no camera device found, or device busy
}
CameraPermission values: NotDetermined, Granted, Denied, NotSupported, NotAvailable.
PDF Export
Inject IPdfExporter and call ExportAsync or PrintAsync:
public class MyService(IPdfExporter pdfExporter)
{
public async Task<byte[]> ScanToPdfAsync(FrameScanResult scan, CancellationToken ct)
=> await pdfExporter.ExportAsync(scan, cancellationToken: ct);
}
Browser Technologies
| Capability | Browser technology |
|---|---|
| Camera access | getUserMedia |
| Image rendering | Canvas APIs |
| Document detection | OpenCV.js |
| PDF export | jsPDF |
| Frame data transfer | IJSStreamReference (no Base64) |
These are implementation details, not public API. They can change without affecting consumers.
Exceptions
Blazor integration failures throw BlazorFrameException, which derives from the Core FrameException. Catch FrameException to handle every Frame failure, or BlazorFrameException for Blazor-specific ones:
try
{
await scanner.CaptureAsync();
}
catch (Circuids.Frame.Blazor.BlazorFrameException ex)
{
// Blazor-specific integration failure (e.g. camera not opened).
}
Package Relationship
| Package | What it provides |
|---|---|
Circuids.Frame |
Platform-neutral core: contracts, models, configuration, results, events |
Circuids.Frame.Blazor |
Blazor integration: camera, JavaScript interop, UI components, browser implementation |
Learn More
- Repository: https://github.com/Circuids/Frame
- Samples: sample/Circuids.Frame.Blazor.Sample
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Circuids.Frame (>= 1.0.0-preview.12)
- Microsoft.AspNetCore.Components.Web (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview.12 | 79 | 9/4/2026 |
| 1.0.0-preview.11 | 87 | 8/17/2026 |
| 1.0.0-preview.10 | 77 | 8/17/2026 |
| 1.0.0-preview.9 | 79 | 8/14/2026 |
| 1.0.0-preview.8 | 84 | 8/12/2026 |
| 1.0.0-preview.7 | 79 | 8/3/2026 |
| 1.0.0-preview.6 | 72 | 8/3/2026 |
| 1.0.0-preview.5 | 75 | 8/3/2026 |
| 1.0.0-preview.4 | 68 | 8/3/2026 |
| 1.0.0-preview.3 | 78 | 8/2/2026 |
| 1.0.0-preview.2 | 70 | 8/1/2026 |
| 1.0.0-preview.1 | 76 | 8/1/2026 |
1.0.0-preview.12
- Fixed: capturing with the default corner selection did nothing — the editor reported its quad to .NET only after the user dragged a handle, so ConfirmAsync silently returned on an untouched selection (affected both ImageScanEditor and the camera capture review). The editor now reports its effective quad (initial or default) at setup, and ConfirmAsync falls back to InitialCorners or the default inset quad when no edit has been reported.
- Fixed: manual camera capture was delayed indefinitely or never completed — the shutter no-opped when detection had not produced corners yet, and the capture's OpenCV warp queued behind the continuous detection loop on the same JS thread. CaptureAsync now falls back to a full-frame capture when no corners exist yet, and the detection loop is paused for the duration of a manual capture.
- Fixed: OpenCV Mat leaks in frame-detection.js — each contours.get(i) wrapper and the selected max contour were never deleted, growing the WASM heap every detection tick (~10/sec) until detection and capture degraded over time.
- Fixed: the detection loop could fail to restart after a capture review or a repeated-failure stop — the loop's CTS/Task fields are now cleared when the run ends so a restart is not blocked by a stale non-null task.
1.0.0-preview.11
- Fixed: CameraPreview emitted a duplicate height property when ObjectFit was set alongside an explicit Height/MaxHeight — the trailing height:100% overrode the explicit value, so a fixed-height scanner letterboxed the feed. The height:100% fallback now applies only when no explicit height is given.
- Fixed: ImageScanEditor had the same sizing bug — with ObjectFit set and no explicit height, the image's height:100% resolved against an auto-height stage, so object-fit had no effect. The editor now fills its parent when ObjectFit is set without an explicit height.
1.0.0-preview.10
- Added: component sizing parameters — CameraPreview, ImageScanner, and ImageScanEditor now accept Width, Height, MaxWidth, MaxHeight (CSS lengths) and ObjectFit (new ObjectFit enum: Fill, Cover, Contain), applied via inline style so they win over scoped CSS without !important. Defaults are null, preserving current behavior.
- Added: ObjectFit enum (Circuids.Frame.Blazor) — Fill, Cover, Contain.
- Added: ImageScanner propagates sizing to its stage and capture-review editor, so a fixed Height constrains the live feed and the review editor.
- Added: DetectionOverlay accepts ObjectFit, mapped to SVG preserveAspectRatio (Fill→none, Contain→meet, Cover→slice) so the detection quad stays aligned to the displayed video for all three modes.
- Added: ImageScanEditor's corner overlay is object-fit-aware — the quad and drag handles map through the displayed image box, so they stay on the document corners under Cover/Contain.
- Fixed: camera opened at low resolution — getUserMedia resolution constraints were exact (hard) requirements, which over-constrained and fell back to a low mode. They now use ideal constraints so the browser picks the highest available mode up to the target.
- Changed: camera resolution targets raised — Low now targets 720p (1280×720), Medium 1080p (1920×1080), High 2K (2560×1440). All use ideal constraints, so cameras that cannot reach the target fall back gracefully to their best available mode.
- Fixed: CameraPreview emitted a duplicate height property when ObjectFit was set alongside an explicit Height/MaxHeight — the trailing height:100% overrode the explicit value, so a fixed-height scanner letterboxed the feed. The height:100% fallback now applies only when no explicit height is given.
1.0.0-preview.9
- Added: configurable scanner colors via `FrameOptions.Appearance` (`Appearance` model) — set the detection/alignment quad stroke and fill, and the corner-editor drag-handle fill and border, using any CSS color. Defaults preserve the existing orange/green visuals.
- Changed: capture-review actions refined — borderless dark-glass "Apply" and "Use Original" buttons that blend into the camera frame while staying legible.
- Fixed: print (IPdfExporter.PrintAsync) could fire too early or throw, and left the hidden iframe attached; the print flow now loads the image via object URL, waits for rendering, guards the print call, and cleans up reliably.
- Breaking: FrameScanner renamed to ImageScanner (component name no longer carries the Frame brand; all namespaces still carry it). Update `<FrameScanner>` to `<ImageScanner>` and the code-behind type.
- Added: ImageScanner capture review — set EnableCaptureReview to freeze the captured frame and show an interactive corner editor (ImageScanEditor) seeded with the auto-detected corners before the result is finalized; KeepOriginalAsync accepts the capture as taken. The review replaces the removed live-feed FrameCaptureEditor.
- Fixed: the capture-review overlay exposed only "Use Original" — added an "Apply" button that commits the manually adjusted corners via ConfirmAsync before finalizing the result.
- Breaking: FrameCaptureEditor removed. Correct corners after capture via ImageScanner's EnableCaptureReview, or over any image with ImageScanEditor.
- Added: ImageScanEditor component — corner editor over a supplied image. Supply image bytes (dimensions optional — derived from the loaded image), drag the four corners, and call ConfirmAsync to receive a perspective-corrected FrameScanResult.
- Changed: capture now warps encoded bytes only (frame-capture.js#warpDocumentFromBytes); live camera captures freeze the frame first. The live-video warp path is removed.
- Fixed: captured output dimensions are now derived from the document quad's edge lengths (scaled by OutputScale) instead of the source frame dimensions — a portrait document in a landscape frame is no longer stretched to landscape.
- Fixed: the corner-editor overlay (quad + drag handles) never rendered because component-isolated CSS did not reach the nodes frame-editor.js appends at runtime; the overlay styles now use ::deep anchored to the editor stage.
- Changed: detection's minimum contour area is now a fraction of the image area (mapped from PerformanceMode) — detection is resolution-invariant, matching the jscanify approach across camera resolutions.
1.0.0-preview.8
- Added: BlazorFrameException — base type for Blazor integration failures, deriving from the new Core FrameException. Camera-not-open state failures now throw this instead of InvalidOperationException.
- Fixed: the detection overlay is now responsive — the scan guide uses a thicker stroke on small screens (touch-first) and honors prefers-reduced-motion. The quad coordinate mapping is unchanged, so alignment is preserved across desktop and mobile.
1.0.0-preview.7
- Fixed: capture, raw-frame, and PDF byte transfers crashed or silently failed — the JS modules returned raw Uint8Array values that Blazor cannot deserialize into IJSStreamReference. The bytes are now wrapped in DotNet.createJSStreamReference so manual/auto capture, GetFramesAsync, and PDF export work correctly.
1.0.0-preview.6
- Fixed: capture produced undecodable image bytes — the warped output is now encoded to the configured OutputFormat (JPEG honoring CaptureQuality.JpegQuality; PNG lossless) so captures preview, export, and print correctly. Manual and auto-capture were both affected (same path).
- Fixed: detection and capture no longer pass the <video> element to cv.imread — the bundled OpenCV.js imread accepts only canvas/img sources, so the live frame is drawn to a scratch canvas first. Resolves "Detection failed repeatedly" and unhandled capture errors on real devices.
- Removed: the fixed dashed viewfinder border from the detection overlay — the live corner-based quadrilateral is the guide.
1.0.0-preview.5
- Fixed: detection and capture no longer pass the <video> element to cv.imread — the bundled OpenCV.js imread accepts only canvas/img sources, so the live frame is drawn to a scratch canvas first. Resolves "Detection failed repeatedly" and unhandled capture errors on real devices.
1.0.0-preview.4
- BREAKING: implements the new async IDocumentDetector.DetectAsync contract (detection no longer blocks the WASM thread — fixes the scanner hanging on "Loading detection engine…")
- BREAKING: FrameScanner passes dimensions-only CameraFrames (Data = null) on the detection path; no per-tick byte transfer
- Fixed: auto-capture now fires (StabilityTracker composes IsStable against AutoCaptureOptions.StabilityWindowMs)
- Fixed: manual capture no longer no-ops while detection is loading
- Fixed: alignment honors AutoCaptureOptions.MinAlignmentConfidence (was hardcoded 0.8)
- Fixed: RequestPermissionAsync never throws for expected camera failures (missing hardware, busy device, unsupported/insecure context); CameraPreview surfaces PermissionDenied on denial and Error on other failures
- Added: visible scan guide — target viewfinder border plus live highlighted document quadrilateral with alignment-state colors; styles ship as component-isolated CSS (auto-bundled — no stylesheet link needed)
- Added: engine status UX — loading indicator, visible engine-load error with Retry
- All components initialize exclusively in OnAfterRenderAsync(firstRender); detection loop starts only after the camera is open
1.0.0-preview.3
- Fixed JS Interop Issues and etc..
1.0.0-preview.2
- Breaking: flattened all namespaces to Circuids.Frame.Blazor (removed Components, Extensions, Camera, Capture, Detection, Export, Interop, Processing sub-namespaces)
- All types now in Circuids.Frame.Blazor root namespace
1.0.0-preview.1
- Initial preview release
- Blazor integration: browser-backed implementations of all Core contracts
- FrameScanner component: orchestrates camera, detection, and capture in one component
- CameraPreview component: live camera feed via getUserMedia
- DetectionOverlay component: real-time document highlighting and alignment guides
- OpenCV.js document detection: Canny edge detection, contour finding, corner extraction
- Perspective-corrected capture with warp via OpenCV.js
- jsPDF-based PDF export: programmatic byte generation and browser print dialog
- IJSStreamReference frame transfer: optimized byte array interop, no Base64 overhead
- Camera permission request and retry support
- AddFrameForBlazor DI extension (idempotent, order-independent)
- All components use code-behind; no @code blocks
- JavaScript interop is internal and hidden from consumers