photocn

useImageEditor

The headless controller behind every component, from photocn/react.

import {
  ImageEditorProvider, // owns state (pass options) or shares it (pass editor)
  useImageEditor,      // read the nearest provider
  useImageEditorState, // create state yourself
} from "photocn/react";

Options

Accepted by useImageEditorState, <ImageEditorProvider> and <ImageEditor>.

PropTypeDescription
srcstring | File | Blob | ArrayBuffer | HTMLImageElementThe image to edit.
defaultParamsEditorParamsInitial edit (e.g. a restored session).
onParamsChange(params) => voidFires after every edit.
tool / defaultTool / onToolChangeImageEditorToolIdControlled or uncontrolled active tool. Default "adjust".
onImageLoad / onImageError(result) / (error) => voidImage decode lifecycle.
onExport(result) => voidCalled after every successful export.
disabledbooleanDisable all interaction.
filterPresetsFilterPreset[]Replace the built-in presets. Use createLutPreset() or createMatrixPreset() from photocn/filters.
aspectRatioOptions{ value, label }[]Crop ratio presets, written landscape-first ("3:2"); the portrait toggle flips them.
cropToolImageEditorToolIdTool that shows the crop view. Default "compose".
renderMode"worker" | "main"Defaults to worker when OffscreenCanvas is supported.
spawnWorker() => WorkerCustom worker spawn (for strict CSP).
proxyMaxDimnumber | resolverLong-edge cap for the interactive preview proxy. 0 disables.
keyboardShortcutsbooleanCmd+Z / Shift+Cmd+Z / Esc. With several editors, only the last one touched responds. Default true.
historyLimit / commitDelaynumberUndo depth (100) and slider-drag merge window in ms (400).

Returned API

PropTypeDescription
status"idle" | "loading" | "ready" | "error"Also available as isLoading, isReady, hasImage and error.
params / setParams / patchEditorParamsRaw params. patch(section, values, { transient }) merges slider drags into one undo step.
history{ undo, redo, canUndo, canRedo }Undo stack.
tool / setToolImageEditorToolIdThe active tool.
openFile / load() / (src) => Promise<void>Open the file picker, or load an image programmatically.
adjust{ value, setLights, setColors, setEffects, resetSection, reset }Tone and color.
filters{ value, presets, loading, select, setMix, reset }select() takes a preset, a label or null.
curves{ value, histogram, set, commit, reset }Four channels: RGB, R, G, B.
blur{ value, set, setCenter, commitCenter, reset }Bokeh and gaussian.
blend{ value, hasImage, setImage, setMix, reset }Second-image blend.
geometry{ value, crop, polygon, setCrop, rotate, flip, setStraighten, setPerspective, setCorner, setAspectRatio, toggleOrientation, reset, … }Non-destructive crop & transform, applied in a fixed order from the original. Design notes: docs/compose.md in the repo.
compare{ active, setActive }Show the original while held.
recipes{ current, apply }Serializable looks.
histogram{ data, canvasRef }Live RGB histogram.
exportImage / download({ format, quality, width, height }?) => Promise<ExportResult>Encode at full resolution, or resize with width/height (ratio kept if you give one side). JPEG keeps EXIF.
canvasRef / stageRef / rootRefRefObjectAttach to the preview canvas, its container and the editor root.
canvasKeynumberUse as the preview canvas's key. A canvas handed to the render worker can't be reused, so the editor asks for a fresh one when it needs to (new image, color space change).
engine / workerUseMiniPhotoEditorResult / WorkerEditorEscape hatches to the renderer, EXIF and the worker bridge.

Geometry

Crop, straighten, perspective, quarter turns and flips are stored as one small state and rendered from the original pixels every frame, always in the same order: orientation, perspective, straighten, crop. The crop is only a window, so it can be widened again at any time. Straightening shrinks the rendered crop just enough to hide empty corners, and your crop comes back when you straighten back.

const { geometry } = useImageEditor();

geometry.rotate(1);                      // quarter turn; the crop turns with the picture
geometry.setAspectRatio("16:9");         // follows the crop's orientation
geometry.toggleOrientation();            // 16:9 ⇄ 9:16
geometry.setStraighten(3.5);             // ±45°, frame stays put, auto-zoom
geometry.setPerspective({ y: 0.25 });    // keystone sliders, -1..1
geometry.setCrop({ x: 0.1, y: 0.1, width: 0.8, height: 0.8 }); // normalized
geometry.outputSize;                     // { width, height } at full resolution

await editor.exportImage({ format: "jpeg", width: 1600 }); // resize happens at export

Custom filters

import { createLutPreset, filterPresets } from "photocn/filters";

// A 33×1089 3D LUT strip (the same format as the built-ins).
const presets = [...filterPresets, createLutPreset("teal-orange", "/luts/teal-orange.png")];

<ImageEditor src={src} filterPresets={presets} />