The editor
Everything in one component: crop, rotate, adjustments, filters, drawing, annotations, layers, history and export. Drop an image on it, or open one from its own drop zone - then try the theme, direction and language switches.
Try it
The document behind the editor
This is the whole edit: a list of pixel operations and a list of annotation objects. It is a value, so it can be stored, sent to a server, replayed on the original file, or applied to a different image entirely. It updates as you work.
Load an image into the editor above to see its document here.
Using it
The simple path stays simple: the editor fills its container, so give it a height, and everything else has a working default. The advanced path is the same component with more parameters.
@* The simple path stays simple. *@<div style="height: 70vh"> <BmageEditor @ref="editor" /></div><button @onclick='() => editor.DownloadAsync("edited")'>Save</button>@* And the advanced path is the same component. *@<BmageEditor Strings="german" Class="bmg-theme-dark" MaxProxyDimension="3000" OnDocumentChanged="doc => Persist(doc)"> <Toolbar Context="editor"> <button @onclick="() => editor.SetTool(EditorToolKind.Crop)">Crop</button> <button disabled="@(!editor.CanUndo)" @onclick="editor.Undo">Undo</button> </Toolbar></BmageEditor>
The editor needs the stylesheet _content/Bit.Bmage.Editor/bit-bmage-editor.css
linked from App.razor or index.html - see
Getting started.
Parameters
| Parameter | Purpose |
|---|---|
Image | The image to edit. Setting it replaces the session and its history. The editor works on a copy, so your buffer stays yours. |
Strings | Every user-visible label - see Localisation. |
Direction | "ltr" or "rtl". The layout uses logical CSS properties, so it mirrors correctly. |
Class, Style | Applied to the root element. bmg-theme-dark or bmg-theme-light force a theme. |
ShowToolbar, ShowPanel | Hide the built-in chrome without replacing it. |
Toolbar, Panel, Footer | Replace the chrome entirely; each receives a BmageEditorContext. |
EmptyState | Replace the "drop an image here" view. |
MaxProxyDimension | Longest edge used for interactive editing. Falls back to BitBmageOptions.MaxInteractiveDimension, then 2048. 0 always edits at full resolution. |
MaxHistoryDepth | Undo steps kept. Default 100. |
ExportOptions | Settings used by the built-in save action. |
OnDocumentChanged, OnExported, OnImageLoaded, OnError | Callbacks. OnImageLoaded matters more than it looks: the editor can load an image through its own drop zone while your Image is still null, so a page whose buttons depend on an image should re-render from it. |
Driving it from code
Everything the built-in toolbar can do is a public method, and the same surface is handed to
custom chrome through BmageEditorContext - so a toolbar you write can do
everything the built-in one does.
@code { private BmageEditor? _editor; void Crop16x9() { _editor!.SetTool(EditorToolKind.Crop); _editor.SetCropAspect(AspectRatio.Ratio16x9); } async Task Save() => await _editor!.DownloadAsync("edited", new ImageExportOptions { Format = ImageFormat.WebP, Quality = 0.9 }); async Task AddCallout() => await _editor!.AddAnnotationAsync(new ArrowAnnotation { Start = new Vector2(100, 400), End = new Vector2(300, 200), Style = ShapeStyle.Default with { Stroke = new StrokeStyle { Width = 6 } }, });}
For an adjustment driven by your own slider, pass transient: true while dragging
and commit once when the interaction ends, so the history gets one entry rather than hundreds.
Replacing the chrome
Toolbar, Panel and Footer take a render fragment with a
BmageEditorContext: the current tool and settings, the document, the annotations,
the history and its position, the detected capabilities, the output size, and every action -
tool selection, crop, rotate, flip, adjustments, annotations, undo, redo, reset, zoom, load,
paste, export, download and clipboard copy.
<BmageEditor ExportOptions="_options"> <Toolbar Context="editor"> <div class="my-toolbar"> <button class="@(editor.Tool == EditorToolKind.Crop ? "on" : null)" @onclick="() => editor.SetTool(EditorToolKind.Crop)"> Crop </button> <button disabled="@(!editor.CanUndo)" @onclick="editor.Undo">Undo</button> <button disabled="@(!editor.CanRedo)" @onclick="editor.Redo">Redo</button> <span>@editor.OutputSize.Width × @editor.OutputSize.Height</span> <button @onclick='() => editor.DownloadAsync("image")'>Save</button> </div> </Toolbar></BmageEditor>Theming
Every colour and metric is a CSS custom property on .bmg-editor. Override the
ones you care about - this site does exactly that to make the editor follow the theme switch
in the header rather than the operating system (the site theme above), while
light and dark show the component's own palettes.
.my-editor { --bmg-accent: #7c3aed; --bmg-accent-text: #ffffff; --bmg-bg: #0f0f12; --bmg-bg-subtle: #17171c; --bmg-canvas-bg: #202027; --bmg-border: #2a2a33; --bmg-text: #eeeef2; --bmg-radius: 4px; --bmg-panel-width: 320px;}/* <BmageEditor Class="my-editor" /> */
Dark mode follows prefers-color-scheme unless forced with
bmg-theme-light or bmg-theme-dark. High contrast mode and
prefers-reduced-motion are honoured.
Localisation
BmageEditorStrings is a record with a property for every label, so a translation
is an object rather than a resource file - and you can override only the words you care
about. Number and size formatting are functions on the same record. For a right-to-left
language set Direction="rtl": the panel, the crop handles and the toolbar all
mirror. The Persian chip above switches both at once.
var german = BmageEditorStrings.Default with{ Crop = "Zuschneiden", Undo = "Rückgängig", Redo = "Wiederholen", Download = "Herunterladen", DropImageHere = "Bild hier ablegen oder klicken zum Auswählen",};// Formatting is replaceable too, because it is a function on the same record.var strings = BmageEditorStrings.Default with{ FormatFileSize = bytes => $"{bytes / 1024.0:0.#} Ko", FormatDimensions = (w, h) => $"{w}×{h} px",};// <BmageEditor Strings="german" Direction="ltr" />Tools and keyboard
| Tool | Behaviour |
|---|---|
| Select | Click to select an annotation, drag to move it, arrow keys to nudge, Delete to remove. |
| Pan | Drag to move the view. |
| Crop | Drag a rectangle, adjust it with eight handles, apply from the toolbar. |
| Draw | Freehand, smoothed so fast strokes do not look polygonal. |
| Line, Arrow | Drag from start to end; hold Shift to snap to 15° increments. |
| Rectangle, Ellipse | Drag to size; hold Shift for a square or a circle. |
| Highlight | A translucent block, like a marker pen. |
| Redact | Solid, pixelate or blur; replaces the pixels underneath. |
| Text | Places a label you then edit. |
| Step | Auto-numbered markers for tutorials and bug reports. |
| Keys | Action |
|---|---|
| Ctrl / ⌘ + Z | Undo |
| Ctrl + Shift + Z, Ctrl + Y | Redo |
| Delete, Backspace | Delete the selected annotation |
| Arrow keys | Nudge the selection by 1px, or 10px with Shift |
| Esc | Clear the selection, or cancel a pending crop |
| +, - | Zoom |
| Ctrl + 0 | Fit to window |
The canvas is a focusable application region with a described keyboard hint. The
toolbar is a toolbar of labelled buttons carrying aria-pressed, the
panel is a tablist with arrow-key navigation, crop handles are focusable buttons
with 24px hit targets, and status messages use live regions. On touch screens the canvas
claims the drag (so drawing does not scroll the page) and the panel moves below the canvas.
Large images
By default the editor edits through a proxy no larger than 2048px on its longest edge and
replays the edit at full resolution on export - for a 24 megapixel photo, a 25× cut in
the pixels each preview touches. Because every operation rescales itself, the preview and the
export are one edit at two resolutions rather than two code paths that could disagree.
ImageEditSession.UsesProxy and ProxyScale say when it is happening;
set MaxProxyDimension="0" to always edit at full resolution, or
MaxInteractiveDimension on BitBmageOptions to change the default for
every editor in the app.
History stores descriptions, not pixels: a hundred undo steps on a 6000 × 4000 image stay under a megabyte.