Clone Stamp
Alt+Click source, paint to clone with adjustable size, hardness, opacity, spacing
The whole list, straight from the repo's own docs — 48 of them. The engine ones run on your machine; the interface ones are what you touch. Pick a group, or just scroll.
Alt+Click source, paint to clone with adjustable size, hardness, opacity, spacing
REJECTED / APPROVED / DRAFT / CONFIDENTIAL / UNDER REVIEW presets; JS renders label to OffscreenCanvas, Rust scales to brush size via bilinear resize and composites with "Red Stamp" history entry
each is its own sub-tool with its own panel section and its own URL, split out of the single combined tool they used to share. Crop: interactive SVG overlay with rule-of-thirds guides and 8 draggable resize handles, committed through Rust. Transform: flip horizontal/vertical, rotate 90° CW/CCW. Align: six buttons snap a selected text/shape's bounding box to any canvas edge or center, computed in Rust (align_annotation) as a single undo step. Selection Marker (above Align): a magic-wand flood-select — click a region to select pixels within a colour tolerance and delete them; the flood-fill, mask, and delete are all Rust (magic_wand_select / delete_selection), with Alt+A select-all and Alt+D deselect
soft drop shadow on the text glyphs and/or the background box, in the Text panel below the colour swatch: shared colour / opacity / offset / blur, rendered in Rust (offset + box-blurred silhouette, tile grown to fit so Align sees it) and persisted with the edit
EXIF keep/strip moved out of Compress into Settings → Security as a saved preference (applies to every export path); GPS / capture-time / lens stays or goes by your choice
one shared useBreakpoint hook: below ~1000px the top bar goes icon-only, below ~900px the side panels float as overlay drawers (scrim, one at a time) instead of crushing the canvas, below ~600px a "wider window" notice. Reduce Motion suppresses the layout slides
Skip-to-canvas link, landmark roles + labels (toolbar / panels / canvas), accessible names on the tool buttons, and Escape-to-close + role="dialog" semantics on the modals; on top of the existing tool / number / Alt shortcuts
one tile, one panel. Both halves move the same two numbers (Web Performance Gain and PageSpeed Insights Score read dimensions AND format/quality together), so they share a panel the way Squoosh does: the scores on top, then Resize, then Compress. Resampling is fully in WASM with no canvas round-trip. One Apply button, named for what is pending — "Apply Resize" when only the dimensions moved, "Apply Compression" when only quality/format/method moved, "Apply Compression & Resize" when both did. Dimensions-only re-saves in the photo's own format at full quality, so the quality slider is left alone
Brightness (−100% to +100%), contrast (0% to 300%); each adjustment is a separate undo snapshot
live RGB / Luma scope in the Review panel, computed in Rust (calculate_histogram) straight from the composite buffer — no per-frame offscreen-canvas sampling
all alpha blending (blend_pixel, blend_over) uses integer source-over math instead of per-pixel float ÷255.0, verified identical to the old result within ±1
its own sub-tool under Edit → Color Picker; hovering the canvas shows a floating 11×11 magnifier (sourced from Rust get_pixel_region); clicking picks the pixel color and sets it as both brush and text color
Box-blur with stroke-based region masking; configurable radius and intensity; its own sub-tool under Create → Blur Brush
Anti-aliased arrows with arrowhead (single or double), drawn directly on the pixel buffer; Create → Arrow
Rectangles, circles, hand-drawn circles, and lines rendered in WASM; Create → Shapes, with Arrow and Pins as their own sub-tools beside it
Freehand painting via WASM paint_dab + paint_stroke_to; configurable brush size, color, and opacity; Create → Brush, with Pen and Blur Brush as sibling sub-tools
Photoshop-style Bézier pen (Create → Pen): click to drop corner anchors, click-drag to pull smooth curve handles, and grab any anchor or handle to reshape the path. Enter closes it, Esc finishes it open. An optional solid background fills the closed interior (Rust scanline polygon fill, under the stroke). Committed paths stay re-editable — click one to re-open its anchors/handles — and round-trip through history, photo-switch, reload, and cloud sync as a kind-7 ShapeAnnotation (cubic control sequence flattened with de Casteljau)
Click-to-place text with configurable font family (12 browser-safe options), size, weight, and color; up to 8 recent texts that re-open the canvas text box at the last used position, restoring all text settings
Browser renders emoji to OffscreenCanvas, pixels sent to WASM stamp_pixels() for alpha compositing; Create → Emoji
Lossless PNG via Rust encoder, JPEG/WebP/AVIF via browser
start a new document from the upload dialog: dimensions + a page-size preset (FHD / Square / Story / 4×6 / 5×7 / 8×10) and a White / Black / hex / Transparent background; the fill is generated in Rust (blank_png → codec::export_png) with no canvas round-trip
256px WebP thumbnails built from the already-decoded working pixels via Rust resize_pixels (uploads decode once, not twice); working canvas stays at ≤2048px
Cross-photo pixel compositing with alpha blending; paste from clipboard supported
50-step undo/redo with labeled snapshots (including dimensions for crop/resize/rotate correctness), jump-to, delete entry
Switching photos saves the full WASM canvas + undo/redo stack to IndexedDB (PNG-encoded per snapshot). Switching back restores the exact edit session — same canvas state, same undo history, all redo steps intact
Staggered entrance: TopBar → Sidebar → Gallery (Framer Motion springs)
10 tools with gradient icons: Clone Stamp, Resize & Compress, Crop, Paint, Text, Arrows (FileText — coming soon), Shapes, Effects (Sparkles), Batch Image Editor (bulk logo stamp + grid mosaic view), Eraser (relabeled from AI)
Stamp (Clone / Stamps / Emojis), Shapes (Shapes / Arrows), Paint (Paint / Blur Brush / Pen), Effects (Levels / Color Picker) via shared TabGroup component
Hold Space for grab-to-pan; all tool handlers bypassed during pan
Squoosh-style draggable divider; overlay is positioned exactly over the canvas bounding box (tracks zoom/pan via ResizeObserver) so before/after layers are always pixel-aligned
Bottom strip with thumbnails, add/remove/switch/duplicate (content-addressed, zero-copy; carries edits); PgUp/PgDn cycling; multi-select with Export / Delete / Duplicate / Unselect; header count + per-tier limit (i) tooltip; originals preserved in IndexedDB at full resolution regardless of working-copy downscale
Right-side panel (Alt+H) with a header toggle group that opens up to three stacked sections sharing the body height (1 full / 2 halves / 3 thirds, each scrollable):
Drag-and-drop modal with file browser and paste-from-clipboard (Ctrl+V / paste button)
PNG, JPEG, WebP, AVIF format selector in the top bar
Alt+/ opens a full reference overlay grouped by category (two columns per group)
the AI tool relabeled (tool id ai unchanged for shortcut/persistence/routing): Brush Eraser (drag on canvas to scrub the active layer to transparent, local, free, no sign-in) plus three removal modes in one panel — Magic Eraser (local PatchMatch object removal, coming soon), Background Removal (rembg) and Object Removal (SD Inpaint), both Replicate-backed and Paid-tier. 4× Upscale moved to the Effects tool; OCR/text extraction moved to the Text tool; the old Smart Crop and Auto-Enhance placeholders were retired
full light & dark palettes (warm earth-tone dark, warm-paper light) driven by CSS-variable tokens in styles.css (:root light, .dark dark, @custom-variant dark); "System" follows the OS via matchMedia and updates live. Pre-paint guard in index.html (no FOUC). Set in Settings → Appearance, persisted to localStorage + Convex. JetBrains Mono + DM Sans throughout
toggleable top/left pixel rulers (tick labels track zoom) + a configurable non-destructive grid overlay: square px spacing, golden-ratio lines, or N×M divisions, with color + opacity. Set in Settings → Rulers & Grids, persisted. The grid-layout geometry is computed in Rust (grid_lines WASM export) as the single source, projected to an SVG overlay on the canvas
the editor's UI, tool, and gallery state lives in three Zustand stores (useUIStore / useToolStore / useGalleryStore) instead of AppShell local state; components subscribe with atomic selectors for minimal re-renders, and durable prefs (last master-bar tab, tool sub-modes) persist to IndexedDB via a write-deduped StateStorage adapter. Behaviour-preserving groundwork for splitting AppShell into feature modules. See State Management
the rail is five groups (Enhance · Select · Create · Edit · Batch) owning 34 sub-tools, replacing eleven flat tools. One registry (features/tools/toolGroups.ts) defines them, and the rail, the sub-tool row, the digits 1–5, the shortcut sheet, the canvas dispatch and the cursor all derive from it — there is no second table to fall out of step. Legacy tool ids are untouched underneath, so persistence and old links keep working
every sub-tool has its own address: #/create/brush, #/edit/color-picker, #/select/ellipse. Thirty-five older URL shapes redirect to the sub-tool they meant, each pinned by a test. The three Edit sub-tools that share a tool underneath used to collapse onto one URL; they no longer do
the lit sub-tool decides what a drag does and which cursor you get, instead of the whole parent tool sharing one. A sub-tool with no canvas gesture idles by default rather than inheriting whatever the clone stamp was doing
eyedroppered colours land in a Recent Colors list under Edit → Color Picker: click to re-apply to brush and text, ✕ to forget, Clear to empty. Same list UI as the guides list. Per-session, not persisted
reading text out of an image is its own sub-tool (Create → OCR) rather than a mode buried inside the Text panel
a rectangle or circle picked out of Review → Reselect gets a d-pad on its row; pressing it puts four ⊕ around the shape on the canvas. Each press lays another same-sized copy, clear of the original, in that direction, and each side counts independently — ← twice gives two marching left, and a later ↑ goes above the *original* rather than above the last copy. Built for laying out a diagram from repeated boxes. Rectangles and circles only; every other shape kind keeps a disabled placeholder so the row shape stays constant down the list. Offsets are computed from the shape's own bounding box (lib/duplicatePadGeometry.ts) and the copy itself is an engine-side struct clone (duplicate_shape_annotation)
any placed text or shape copies from its own row, offset down-right. The engine clones the annotation struct rather than re-adding it from a property list, so a shadow, rotation, background, corner radius or perspective warp travels with the copy; a test walks every field the engine emits rather than a hand-listed set, so a field added later is covered the moment it exists
the pulled-string "lazy mouse" leash reaches the Paint brush, the Eraser, the blur brush, pixelate, redact and the clone stamp from a single setting. One leash implementation (src/stabilizer.rs) with its level table exported to JS as stabilizer_leash, so no consumer re-derives the numbers. On the clone stamp the source offset is preserved exactly — a lagging tip samples a source lagging by the same vector — and a move inside the leash costs zero recomposites, because every stroke engine returns whether it actually stamped