loading

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

Theme
Direction
Language

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.

Razor
@* 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

ParameterPurpose
ImageThe image to edit. Setting it replaces the session and its history. The editor works on a copy, so your buffer stays yours.
StringsEvery user-visible label - see Localisation.
Direction"ltr" or "rtl". The layout uses logical CSS properties, so it mirrors correctly.
Class, StyleApplied to the root element. bmg-theme-dark or bmg-theme-light force a theme.
ShowToolbar, ShowPanelHide the built-in chrome without replacing it.
Toolbar, Panel, FooterReplace the chrome entirely; each receives a BmageEditorContext.
EmptyStateReplace the "drop an image here" view.
MaxProxyDimensionLongest edge used for interactive editing. Falls back to BitBmageOptions.MaxInteractiveDimension, then 2048. 0 always edits at full resolution.
MaxHistoryDepthUndo steps kept. Default 100.
ExportOptionsSettings used by the built-in save action.
OnDocumentChanged, OnExported, OnImageLoaded, OnErrorCallbacks. 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.

Razor
@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.

C#

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.

Razor
<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.

CSS
.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.

C#
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

ToolBehaviour
SelectClick to select an annotation, drag to move it, arrow keys to nudge, Delete to remove.
PanDrag to move the view.
CropDrag a rectangle, adjust it with eight handles, apply from the toolbar.
DrawFreehand, smoothed so fast strokes do not look polygonal.
Line, ArrowDrag from start to end; hold Shift to snap to 15° increments.
Rectangle, EllipseDrag to size; hold Shift for a square or a circle.
HighlightA translucent block, like a marker pen.
RedactSolid, pixelate or blur; replaces the pixels underneath.
TextPlaces a label you then edit.
StepAuto-numbered markers for tutorials and bug reports.
KeysAction
Ctrl / ⌘ + ZUndo
Ctrl + Shift + Z, Ctrl + YRedo
Delete, BackspaceDelete the selected annotation
Arrow keysNudge the selection by 1px, or 10px with Shift
EscClear the selection, or cancel a pending crop
+, -Zoom
Ctrl + 0Fit 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.

An unhandled error has occurred. Reload 🗙