ImageViewer

Standalone image viewer that renders an ImageSource (URL, base64, or raw bytes) and layers optional chrome on top: a bordered frame, a toolbar with rotate / flip / zoom / fullscreen, download buttons, and crop interactions. For column-bound editing (drop zone, upload, validation) use ImageEdit instead.

Bare

Default — a plain <img> sized to its parent. No chrome.

600x400.png

Framed + Downloadable

Adds a border, padding, and a corner download button. This is the read-only preview shape ImageEdit uses.

placeholder.png

Toolbar

Adds rotate (CCW/CW), flip (H/V), zoom in/out, reset, fullscreen, and download buttons. The image responds to wheel-zoom (anchored at the cursor) and, when zoomed past 1×, drag-to-pan.

placeholder.png

Toolbar + Croppable

Enables an aspect-ratio picker and a Crop button. Left-drag on the image draws a dashed selection rectangle (constrained to the chosen aspect). Pressing Crop rasterizes the selection to PNG via an offscreen canvas and fires OnCropApplied; the viewer then displays the cropped result. Reset reverts to the original source.

placeholder.png
Blazor

ImageViewer Class

Parameters

Name
Type
Default
Description
Altstring?
Accessible alt text. Defaults to ImageViewer.FileName if unset.
Croppablebool
False
When true (and ImageViewer.Toolbar is also true), enables crop interactions: the toolbar gains an aspect-ratio picker and a Crop button; left-drag on the image draws a dashed selection rectangle. Pressing Crop rasterizes the selection to PNG and fires ImageViewer.OnCropApplied; the viewer then displays the cropped result. Reset reverts to the original ImageViewer.Source. Setting this also adds crossorigin='anonymous' to the <img> so the rasterizer can read the pixels back via toBlob. For ImageSource.Url sources that means the remote server must send Access-Control-Allow-Origin for the image to load at all; if it doesn't, switch to ImageSource.Bytes (which resolves to a same-origin data URL and isn't subject to canvas tainting).
Downloadablebool
False
When true, exposes a download UX: With ImageViewer.Toolbar, adds a download button to the toolbar. With ImageViewer.Framed (no toolbar), renders an overlay button in the corner. Without either, makes the image itself click-to-download.
FallbackRenderFragment?
Rendered when ImageViewer.Source is null.
FileNamestring?
File name used when downloading and as the default ImageViewer.Alt text. Falls back to the source's own ImageSource.FileName, otherwise 'image'.
Framedbool
False
When true, wraps the image in a Fluent-tokened bordered frame matching the read-only preview style used by ImageEdit. Default false (bare img).
IsDirtybool
False
Current dirty state — true when the displayed image differs from the baseline (rotation, flip, or applied crop). Pure view state (zoom, pan) doesn't toggle dirty on its own.
OutputFormatImageOutputFormat
Auto
Output format for the rasterised crop result (and the committed image bytes from ImageViewer.CommitAsync). Defaults to ImageOutputFormat.Auto — the format is derived from the source file's extension (JPEG inputs stay JPEG, everything else emits PNG). Pin to ImageOutputFormat.Png for lossless writes regardless of source, or ImageOutputFormat.Jpeg for size-sensitive photo flows.
OutputQualitydouble
0.9
JPEG quality in [0, 1]. Ignored when ImageViewer.OutputFormat is ImageOutputFormat.Png (PNG is lossless and has no quality knob). Defaults to 0.9 — visually indistinguishable from the original for most photos while still saving a lot vs PNG.
SourceImageSource?
Image data to render. When null, ImageViewer.Fallback is rendered instead (or nothing if no fallback is supplied).
Toolbarbool
False
When true, renders a toolbar above the image with rotate / flip / zoom / reset / fullscreen controls. Implies a wrapper element regardless of ImageViewer.Framed. Drag-to-pan on the image becomes available when zoomed past 1×.
Name: Alt
Type: string?
Description: Accessible alt text. Defaults to ImageViewer.FileName if unset.
Name: Croppable
Type: bool
Default: False
Description: When true (and ImageViewer.Toolbar is also true), enables crop interactions: the toolbar gains an aspect-ratio picker and a Crop button; left-drag on the image draws a dashed selection rectangle. Pressing Crop rasterizes the selection to PNG and fires ImageViewer.OnCropApplied; the viewer then displays the cropped result. Reset reverts to the original ImageViewer.Source. Setting this also adds crossorigin='anonymous' to the <img> so the rasterizer can read the pixels back via toBlob. For ImageSource.Url sources that means the remote server must send Access-Control-Allow-Origin for the image to load at all; if it doesn't, switch to ImageSource.Bytes (which resolves to a same-origin data URL and isn't subject to canvas tainting).
Name: Downloadable
Type: bool
Default: False
Description: When true, exposes a download UX: With ImageViewer.Toolbar, adds a download button to the toolbar. With ImageViewer.Framed (no toolbar), renders an overlay button in the corner. Without either, makes the image itself click-to-download.
Name: Fallback
Type: RenderFragment?
Description: Rendered when ImageViewer.Source is null.
Name: FileName
Type: string?
Description: File name used when downloading and as the default ImageViewer.Alt text. Falls back to the source's own ImageSource.FileName, otherwise 'image'.
Name: Framed
Type: bool
Default: False
Description: When true, wraps the image in a Fluent-tokened bordered frame matching the read-only preview style used by ImageEdit. Default false (bare img).
Name: IsDirty
Type: bool
Default: False
Description: Current dirty state — true when the displayed image differs from the baseline (rotation, flip, or applied crop). Pure view state (zoom, pan) doesn't toggle dirty on its own.
Name: OutputFormat
Type: ImageOutputFormat
Default: Auto
Description: Output format for the rasterised crop result (and the committed image bytes from ImageViewer.CommitAsync). Defaults to ImageOutputFormat.Auto — the format is derived from the source file's extension (JPEG inputs stay JPEG, everything else emits PNG). Pin to ImageOutputFormat.Png for lossless writes regardless of source, or ImageOutputFormat.Jpeg for size-sensitive photo flows.
Name: OutputQuality
Type: double
Default: 0.9
Description: JPEG quality in [0, 1]. Ignored when ImageViewer.OutputFormat is ImageOutputFormat.Png (PNG is lossless and has no quality knob). Defaults to 0.9 — visually indistinguishable from the original for most photos while still saving a lot vs PNG.
Name: Source
Type: ImageSource?
Description: Image data to render. When null, ImageViewer.Fallback is rendered instead (or nothing if no fallback is supplied).
Name: Toolbar
Type: bool
Default: False
Description: When true, renders a toolbar above the image with rotate / flip / zoom / reset / fullscreen controls. Implies a wrapper element regardless of ImageViewer.Framed. Drag-to-pan on the image becomes available when zoomed past 1×.

Events

Name
Type
Description
OnCancelEventCallback
When set together with ImageViewer.Toolbar, renders a red prohibited- circle button at the right end of the toolbar. Always enabled. Consumers typically use this to exit edit mode and discard in-progress transforms — the viewer doesn't auto-revert anything on its own; remount it (e.g. via @key) or update ImageViewer.Source to wipe its internal state.
OnClickEventCallback<MouseEventArgs>
Click handler on the <img>. Suppresses the click-to-download fallback.
OnCropAppliedEventCallback<CropResult>
Fires when the user commits a crop. Receives the rasterized PNG payload.
OnDirtyChangeEventCallback<bool>
Fires whenever ImageViewer.IsDirty changes. Drives a parent's Save-button enabled state without polling.
OnSaveEventCallback
When set together with ImageViewer.Toolbar, renders a green checkmark button at the right end of the toolbar. The button is disabled while the viewer is clean — there's nothing to save. Click handler is fire- and-forget; the consumer typically calls ImageViewer.CommitAsync from here to bake the displayed image to bytes for persistence. Designed for the ImageEdit edit-mode flow (commit edits back into the bound column). Standalone consumers can use it the same way.
Name: OnCancel
Type: EventCallback
Description: When set together with ImageViewer.Toolbar, renders a red prohibited- circle button at the right end of the toolbar. Always enabled. Consumers typically use this to exit edit mode and discard in-progress transforms — the viewer doesn't auto-revert anything on its own; remount it (e.g. via @key) or update ImageViewer.Source to wipe its internal state.
Name: OnClick
Type: EventCallback<MouseEventArgs>
Description: Click handler on the <img>. Suppresses the click-to-download fallback.
Name: OnCropApplied
Type: EventCallback<CropResult>
Description: Fires when the user commits a crop. Receives the rasterized PNG payload.
Name: OnDirtyChange
Type: EventCallback<bool>
Description: Fires whenever ImageViewer.IsDirty changes. Drives a parent's Save-button enabled state without polling.
Name: OnSave
Type: EventCallback
Description: When set together with ImageViewer.Toolbar, renders a green checkmark button at the right end of the toolbar. The button is disabled while the viewer is clean — there's nothing to save. Click handler is fire- and-forget; the consumer typically calls ImageViewer.CommitAsync from here to bake the displayed image to bytes for persistence. Designed for the ImageEdit edit-mode flow (commit edits back into the bound column). Standalone consumers can use it the same way.

Methods

Name
Parameters
Type
Description
CommitAsyncTask<CropResult>
Bakes the currently-displayed image — including any rotation and flips — into a PNG at source resolution. Returns null when the viewer is clean (nothing to save) or before the JS handle has attached. Any in-progress crop selection that hasn't been committed via the Crop button is intentionally ignored; click Crop first if you want it baked in.
HandleFullscreenChangebool isFullscreen
Task
Document-level fullscreenchange callback — JS reports whether the wrapper element is currently the document's fullscreen element. Esc-out flows through here too (it doesn't go through the toolbar's button).
HandleKeyDownstring key
bool shiftKey
Task
JS-driven keydown callback for the edit-mode shortcuts. Only fires for the keys we claim (the JS shim filters by key + modifier state), so we don't have to gate this method itself. Escape — invoke ImageViewer.OnCancel (if wired). Enter — commit the selection as a crop if there is one ≥ 5×5; otherwise invoke ImageViewer.OnSave if dirty. + / = / - / _ — zoom in / out. 0 — reset zoom + pan to identity. Delete / Backspace — clear the selection. Arrow keys — nudge the selection 1 px (10 px with Shift).
HandleWheeldouble deltaY
double cursorRelX
double cursorRelY
Task
Wheel-zoom callback — JS forwards each scroll tick (deltaY) plus the cursor's offset from the container center. Anchored zoom math follows React's: pan_new = (1 − r) · cursorRel + r · pan_prev where r = zoom_new / zoom_prev. Keeps the cursor over the same image-local point across the zoom step.
ResetDirtyAsyncTask
Marks the viewer clean without changing the displayed image. Use after a successful save: the cropped bytes are now the saved version, so future changes should be measured against this state. To revert the displayed image to the original ImageViewer.Source AND clear dirty, use the Reset toolbar button or update ImageViewer.Source.
RevertCropvoid
Drops the local crop overlay only — reverts to the original ImageViewer.Source. Rotation and flips stay where the user has them, so if those changed since the last save, ImageViewer.IsDirty may remain true. Use the toolbar's Reset button (or updating ImageViewer.Source) for a full revert.
Name: CommitAsync
Type: Task<CropResult>
Description: Bakes the currently-displayed image — including any rotation and flips — into a PNG at source resolution. Returns null when the viewer is clean (nothing to save) or before the JS handle has attached. Any in-progress crop selection that hasn't been committed via the Crop button is intentionally ignored; click Crop first if you want it baked in.
Name: HandleFullscreenChange
Parameters: bool isFullscreen
Type: Task
Description: Document-level fullscreenchange callback — JS reports whether the wrapper element is currently the document's fullscreen element. Esc-out flows through here too (it doesn't go through the toolbar's button).
Name: HandleKeyDown
Parameters: string key
bool shiftKey
Type: Task
Description: JS-driven keydown callback for the edit-mode shortcuts. Only fires for the keys we claim (the JS shim filters by key + modifier state), so we don't have to gate this method itself. Escape — invoke ImageViewer.OnCancel (if wired). Enter — commit the selection as a crop if there is one ≥ 5×5; otherwise invoke ImageViewer.OnSave if dirty. + / = / - / _ — zoom in / out. 0 — reset zoom + pan to identity. Delete / Backspace — clear the selection. Arrow keys — nudge the selection 1 px (10 px with Shift).
Name: HandleWheel
Parameters: double deltaY
double cursorRelX
double cursorRelY
Type: Task
Description: Wheel-zoom callback — JS forwards each scroll tick (deltaY) plus the cursor's offset from the container center. Anchored zoom math follows React's: pan_new = (1 − r) · cursorRel + r · pan_prev where r = zoom_new / zoom_prev. Keeps the cursor over the same image-local point across the zoom step.
Name: ResetDirtyAsync
Type: Task
Description: Marks the viewer clean without changing the displayed image. Use after a successful save: the cropped bytes are now the saved version, so future changes should be measured against this state. To revert the displayed image to the original ImageViewer.Source AND clear dirty, use the Reset toolbar button or update ImageViewer.Source.
Name: RevertCrop
Type: void
Description: Drops the local crop overlay only — reverts to the original ImageViewer.Source. Rotation and flips stay where the user has them, so if those changed since the last save, ImageViewer.IsDirty may remain true. Use the toolbar's Reset button (or updating ImageViewer.Source) for a full revert.
React