image-editor β Architecture & Developer Guide
Overview
image-editor separates a Qt-free editing core from the Qt view, mediated by a controller β the same discipline as image_selector, so the core can also back a headless/MCP process.
- Model / engine β everything in
core/(pure NumPy/OpenCV, zero Qt imports) - Controller β
app_controller.py - View β
main.py+ everything inwidgets/
The design rule: every image operation takes a NumPy array and returns one. The UI only assembles state and displays results.
Module map
image-editor/
βββ main.py Entry point β boots Qt, wires controller + window
βββ config.py JSON config (~/.config/image-editor/)
βββ img_io.py Unicode-safe imread/imwrite (+ alpha-aware read, quality params)
βββ app_controller.py Mediates Document β widgets; global/local edit state
βββ core/ Qt-free engine
β βββ document.py Document = base image + layer stack; render(scale)
β βββ layers.py Layer types (adjust / adjust_set / heal / paste / warp) + compositing
β βββ mask.py Mask recipes: brush, shapes, gradients, luminance, RasterSource
β βββ ops.py Per-pixel adjustments + op registry
β βββ curves.py Monotone-spline (PCHIP) β 256-LUT
β βββ film_luts.py Camera profiles + Fujifilm-inspired film simulations
β βββ blend.py Composite + blend modes, seamless paste, colour match
β βββ heal.py Inpaint dispatch (classical β LaMa)
β βββ warp.py Liquify / reshape displacement field
β βββ project.py .iedit save/load (JSON + embedded arrays)
β βββ backends/ Optional ML, lazily imported
β βββ segment.py SAM box/point segmentation
β βββ inpaint_lama.py LaMa erase
βββ widgets/
βββ main_window.py Window, toolbar, tabbed rail, background jobs
βββ canvas.py Zoom/pan preview + brush / crop / warp tools + selection overlay
βββ curve_editor.py Draggable tone-curve widget
The document model
@dataclass
class Document:
base: np.ndarray # immutable original, BGR uint8, full res
layers: list[Layer] # ordered, bottom β top
crop_rect: tuple | None # normalised, post-rotation
rotation: int # 0 / 90 / 180 / 270
# + undo/redo history over (layers, crop, rotation)
A Layer produces an effect image, which the document composites back through
the layer's mask and blend mode β so a global adjustment and a local one share
one code path (global = no mask). Expensive layers (heal, paste, warp) cache
their result per resolution.
Masks are resolution-independent recipes
Mask holds a list of sources (brush strokes, shapes, gradients, luminance ranges,
or a RasterSource from SAM) in normalised coordinates, plus feather and an
optional edge-aware (guided-filter) refine. resolve(h, w) rasterizes to a float
[0,1] map at any resolution β so a mask drives both the proxy preview and the
full-res export.
The render pipeline
AppController.render(full=False) is the single source of truth, and it renders
pending previews in the same position they'll occupy once committed, so preview
== final:
base = Document.render(scale) # committed layers, geometry
β pending local-selection edit # (uncommitted) masked adjust_set
β pending paste # (uncommitted) seamless clone
β pending reshape/warp # (uncommitted) remap
β global grade # adjust sliders + curves + film look
scale < 1 renders a fast proxy for live preview; Save always calls
render(full=True) and writes atomically (tempfile β move) with the original
timestamp preserved.
Optional ML backends
core/backends/ is lazily imported and always guarded β if a package or model is
missing, the feature degrades (Erase β classical inpaint; Remove person β
unavailable). Heavy calls run on a QThread (_RenderWorker) so the UI never
freezes. See installation.md for the CPU-only install recipes
and the licence note.
Testing
tests/test_core.py exercises the Qt-free core with pytest: curves, ops, masks,
compositing, geometry, layers, project round-trips, and (when the backends are
installed) a full SAMβLaMa person removal.
.venv/bin/python -m pytest tests -q
Further reading
- analysis.md β the feature research (how each tool implements erasing, local adjustments, curves, blending) and the tech-stack rationale.
- plan.md β the phased build plan.
- changelog.md β what's been built, newest first.