NC Picto · Docs

Picto architecture

Last reviewed: 2026-10-10 · Last updated: 2026-10-20 · Change: minor (accurate GPU matrix/TRC display conversion; large payloads in the document model are Arc-shared, #4535; original design plan retained) · Target: Adobe Photoshop 2026

Current state (2026-10-10)

What exists on main today. The numbered sections below are the original design plan of 2026-09-30; where the code differs, this summary wins.


Design plan (2026-09-30)

This plan sets out:


1. Goals and principles

Goals

Principles

  1. Engine-first, headless-first. Every feature is reachable without a GUI: from tests, the CLI and MCP. The GUI is one client of the engine.
  2. Everything is a command. Each user-visible action has a stable CommandId (such as filter.blur.gaussianBlur) with typed, serializable parameters. The menu, command palette, shortcuts, recorded actions/macros, CLI, MCP and plugins all dispatch the same commands.
  3. Data describes the UI; toolkits draw it. Tools produce overlay primitives (lines, handles, marching-ants paths) as data. Filter and adjustment dialogs are generated from parameter schemas. A new toolkit implements one renderer for each, not about 150 bespoke dialogs.
  4. One algorithm, one parameter struct. Every algorithm has a CPU reference implementation (Rust + rayon, deterministic). Some also get a GPU implementation (WGSL). Both read the same #[repr(C)] bytemuck::Pod parameter struct. We never keep hand-synced copies of an algorithm in several languages.
  5. Immutable snapshots, copy-on-write tiles. Pixel data is stored in Arc-shared 256² tiles. Taking a document snapshot is O(layers), which makes undo, background jobs, autosave and UI reads cheap and lock-free. Every other large payload in picto-doc is Arc-shared too (the XMP packet, EXIF, raw PSD resources and blocks, a layer's or Duotone's raw PSD data, an unsupported adjustment's bytes), so a snapshot never copies one; a new field that can hold a large payload follows the same rule (#4535).
  6. Determinism. The same input gives the same output across CPU and GPU (within tolerance), across thread counts, and between preview and export. Noise is hashed from document coordinates.
  7. Pure Rust by default. C/C++ dependencies are allowed only behind Cargo features, in I/O-edge crates (for example an optional LibRaw).
  8. Clean-room. We study other editors' behaviour only, and we must not copy proprietary source (Rust, WGSL, C++ or JS), even where it ships as readable source. Specs come from public format docs (Adobe PSD spec, ISO/ICC), academic papers (PatchMatch, Poisson blending, ARAP) and observed behaviour. Never paste code from another product.

1.1 Avoiding GIMP's hole

The criticism of GIMP is historically true, and much of it still applies in 2026.

Bit depth.

Non-destructive editing.

CMYK.

PSD.

Root cause: fundamental properties were baked into the core as assumptions instead of being data. Pixel depth, colour model, and "a layer is a pixel buffer" were all hard-coded. Every later feature that touched them needed a rewrite of everything built on top.

Sources: GIMP 2.10 release notes, GEGL on Wikipedia, GIMP 3.2 release notes, LWN on GIMP 3.0 colour.

Rules we adopt from day one (cheap now, and each would force a rewrite later):

Property Rule Where
Bit depth Every surface carries a runtime PixelFormat { depth: U8|U16|F16|F32, model, alpha }. Algorithms are written generically over a Sample trait and monomorphized per depth, or run on f32 working buffers and convert at tile edges. No u8 pixel type appears in any public engine API. CI runs the golden suite at 8, 16 and 32f. raster, color, algo
Colour model ColorMode (RGB, Gray, CMYK, Lab, Indexed, Bitmap, plus multichannel/spot) is in doc from the start. Channel count is dynamic (1–N plus alpha plus spot). Only RGB/Gray rendering ships first, but the storage, PSD round-trip and conversion paths never assume 3 or 4 channels. doc, raster, psd
Colour management Every document has an ICC profile. The compositor takes a working-space descriptor. The display transform is a separate final stage. Blending space (gamma vs linear) is a per-document option. color, compose
Non-destructive by construction LayerContent is an enum that includes Adjustment, Fill, Text, Shape and Smart from v1, with smart filters as a stack on any layer. The compositor evaluates a graph, not a list of flattened buffers. Destructive ops are just "apply and bake". doc, compose
File formats Formats plug in through trait ImageDecoder/Encoder plus a registry with capability flags (depths, modes, layers, alpha, metadata, ICC). Adding a format is one crate feature. It must never need a change in core types. Lossless PSD round-trip preserves unknown blocks. codecs, io, psd
Size Tiled, sparse storage behind a TileStore trait, with 64-bit coordinates for PSB-scale canvases (300k × 300k). Nothing assumes a document fits in one allocation or one GPU texture. raster
UI toolkit The core never depends on the toolkit (§3–§4). GIMP spent years porting GTK2 to GTK3, and our UI crate can be replaced without touching the engine. layering rule 2

The test is simple: adding 64-bit float, a new colour model, or a new file format should touch a leaf crate, not the foundation. We check this in phase 2 by adding a (hidden) CMYK and a 32f code path early, even before they are exposed in the UI.


2. Workspace layout

picto/
├─ Cargo.toml                  # [workspace], shared deps, lints, profiles
├─ crates/
│  │  ── foundation (no_std-friendly where practical, wasm-safe) ──
│  ├─ geom/                    picto-geom      points, rects, affine/perspective, tile coords, bezier (kurbo)
│  ├─ cms/                     picto-cms       pure-Rust ICC colour management: profiles, transforms, intents, BPC, soft proof, 3D LUTs
│  ├─ color/                   picto-color     pixel formats, color spaces, blend-mode math (scalar reference); conversions via cms
│  ├─ raster/                  picto-raster    tiled COW surfaces, masks, mip pyramids, damage regions, pixel iterators
│  │  ── document ──
│  ├─ doc/                     picto-doc       document model: layer tree, masks, effects, channels, paths, guides, metadata (pure data + serde)
│  ├─ ops/                     picto-ops       reversible operations on a doc + transactions + history/undo
│  │  ── algorithms (CPU reference impls, rayon, wasm-safe) ──
│  ├─ algo/                    picto-algo      adjustments, filters, selection, inpaint/heal, warp/transform, resampling
│  ├─ paint/                   picto-paint     brush engine: dabs, dynamics, stroke smoothing, brush presets (.abr import later)
│  ├─ text/                    picto-text      font DB trait, shaping + layout (parley/skrifa/harfrust), text-layer rasterization
│  ├─ vector/                  picto-vector    shapes, paths, strokes → coverage (vello_cpu / kurbo)
│  │  ── rendering ──
│  ├─ compose/                 picto-compose   layer tree → DrawOp plan; CPU compositor (reference + export + wasm fallback)
│  ├─ gpu/                     picto-gpu       wgpu backend for the DrawOp plan + GPU kernels (WGSL), residency cache, memory budget
│  ├─ viewport/                picto-viewport  camera (zoom/pan/rotate), visible tiles/mips, renders a doc into a wgpu texture; toolkit-agnostic
│  │  ── I/O ──
│  ├─ psd/                     picto-psd       PSD/PSB read + write; its OWN format-level model; depends on nothing in this workspace
│  ├─ adobe-assets/            picto-adobe-assets  .abr .asl .aco/.ase .grd .pat .csh .atn .cube/.3dl, ACR .xmp presets (standalone, like psd)
│  ├─ codecs/                  picto-codecs    flat raster decode/encode at format-supported depths; AVIF encode only (feature `avif`), HEIF/HEIC decode only (feature `heif`); no JPEG XL
│  ├─ heif/                    picto-heif      optional HEIF/HEIC decoder (heic-rs, pure Rust); used only by codecs behind its `heif` feature (standalone)
│  ├─ affinity/                picto-affinity  bounded native Affinity reader: archive, object stream, layers, vectors, text, pixels (standalone; read only)
│  ├─ raw/                     picto-raw       clean-room camera RAW decode (DNG, CR2, TIFF/EP) + develop pipeline (standalone, like psd)
│  ├─ format/                  picto-format    native document format (.pcraft bundle): manifest + content-addressed tiles
│  ├─ io/                      picto-io        import/export orchestration; doc ⇄ PSD mapping; PDF/SVG import (features)
│  │  ── intelligence ──
│  ├─ ml/                      picto-ml        model registry + InferenceBackend trait (ort native / burn|candle web); SAM2, matting, depth, sky
│  ├─ plugins/                 picto-plugins   sandboxed WebAssembly filter plug-ins (wasmi host, limits, ABI v1, registry)
│  │  ── the façade every frontend talks to ──
│  ├─ tools/                   picto-tools     tool state machines (move, marquee, lasso, brush, clone, gradient, crop, transform, pen, text…)
│  ├─ engine/                  picto-engine    Session: open docs, command registry + dispatch, jobs, events, view-models, preferences
│  │  ── platform + frontends ──
│  ├─ platform/                picto-platform  traits: file dialogs, clipboard, fonts, tablet input, menus, storage; native + web impls
│  ├─ ui-egui/                 picto-ui-egui   the (first) GUI shell: panels, dialogs, canvas widget, theme
│  ├─ automation/              picto-automation  MCP server (rmcp) + JSON-RPC over the command registry
│  └─ testkit/                 picto-testkit   golden images, perceptual diff, fixtures, PSD corpus helpers
├─ apps/
│  ├─ picto/              desktop binary (winit + wgpu + ui-egui + platform-native)
│  ├─ picto-cli/          headless batch CLI (open → commands → export), also hosts `mcp` subcommand
│  ├─ picto-web/          wasm32 binary (ui-egui + platform-web), demo
│  └─ picto-web-boot/     wasm32 binary that starts the packaged web app from parts of its wasm
├─ shaders/                    (or inside gpu/) WGSL, with generated struct headers
├─ assets/                     icons, bundled fonts, default brushes/swatches/presets
├─ fuzz/                       cargo-fuzz targets (psd, codecs, format)
├─ xtask/                      build tasks: layer-check, shader-gen, corpus fetch, release
└─ plan/                       this directory

What exists today. This layout is the target design. Built so far: geom, cms, color, raster, psd, codecs, doc, ops, algo, paint, text, vector, compose, gpu, format, raw, io, plugins, engine, ui-egui, automation, testkit, and the three apps. Not yet split out: viewport and tools live inside ui-egui and engine; platform services are function hooks injected by each app (ui_egui::Services); adobe-assets and ml are not started.

Crate granularity: start with the crates above. Split algo into -adjust, -filters, -select, -inpaint and -warp once any module passes about 10k lines, or once compile times hurt. Its internal module boundaries should already follow those lines.

Naming: directories are short and packages are prefixed picto-. picto-psd gets a neutral, publishable name (such as psd-rw) if we release it to crates.io.


3. Dependency layering (enforced)

 L7  apps/*                         (binaries: wire everything together)
 L6  ui-egui · automation · platform
 L5  engine
 L4  tools · viewport · io · ml · plugins
 L3  compose · gpu · format
 L2  ops · algo · paint · text · vector
 L1  doc
 L0  geom · cms · color · raster           psd, codecs, raw, adobe-assets (standalone, no workspace deps)

Rules (checked by cargo xtask layers in CI, which parses cargo metadata):

  1. A crate may depend only on crates in lower layers. No cycles and no sideways dependencies, except where listed.
  2. Nothing below L6 may depend on any UI toolkit, winit, or a platform implementation. Platform services reach the engine through traits defined in engine (or in platform's trait-only core), and are injected at startup.
  3. picto-psd depends on no workspace crate. The doc ⇄ PSD mapping lives in io. This keeps the PSD crate publishable and reusable by other projects. The same holds for every standalone crate, with one documented exception: codecs → heif (both standalone and publishable; STANDALONE_EXCEPTIONS in xtask/src/layers.rs).
  4. wasm gate: every crate in L0–L5 (except feature-gated native backends) must build for wasm32-unknown-unknown. CI runs cargo build -p picto-engine --target wasm32-unknown-unknown --no-default-features --features web.
  5. gpu is optional for engine. Engine features are gpu (default on) and cpu-only, and cpu-only builds are what the headless CLI and CI tests use.
  6. C dependencies (optional LibRaw, pdfium) only behind features, only in codecs, raw or io, and never on by default for the web target.

4. The UI-swap seam (the most important interface)

A frontend needs exactly five things from the core. Keeping these small and data-oriented is what makes the UI replaceable.

4.1 Session: commands in, events out

// picto-engine
pub struct Session { /* docs, registry, job pool, prefs, services */ }

impl Session {
    pub fn new(services: Services) -> Self;                         // platform traits injected
    pub fn dispatch(&self, doc: Option<DocId>, cmd: CommandInvocation) -> Result<JobHandle, CommandError>;
    pub fn registry(&self) -> &CommandRegistry;                     // metadata for menus/palette/shortcuts
    pub fn snapshot(&self, doc: DocId) -> Arc<DocSnapshot>;         // lock-free read (arc-swap)
    pub fn events(&self) -> EventReceiver;                          // DocChanged{damage}, JobProgress, Toast, …
    pub fn view_models(&self, doc: DocId) -> ViewModels;            // layers, history, channels, tool options…
}

pub struct CommandInvocation { pub id: CommandId, pub params: serde_json::Value /* validated vs schema */ }

4.2 CommandRegistry: the menu, palette and automation catalogue

Each command registers the following:

Consumers:

4.3 Tools: pointer events in, ops and overlays out

// picto-tools
pub struct PointerEvent { pub pos_doc: Point, pub pressure: f32, pub tilt: Vector, pub twist: f32,
                          pub time_us: u64, pub buttons: Buttons, pub mods: Modifiers, pub kind: PointerKind /* mouse|pen|eraser|touch */ }

pub trait Tool {
    fn id(&self) -> ToolId;
    fn options_schema(&self) -> Schema;                       // options bar generated from schema
    fn on_pointer(&mut self, ev: &PointerEvent, cx: &mut ToolCx) -> ToolResponse;   // ToolCx gives snapshot + op sink
    fn on_key(&mut self, key: Key, cx: &mut ToolCx) -> ToolResponse;
    fn overlay(&self, view: &ViewTransform) -> Vec<OverlayPrim>;                     // drawn by any UI
    fn cursor(&self) -> CursorSpec;
}

pub enum OverlayPrim { Path{path: BezPath, style: StrokeStyle, space: Space}, MarchingAnts{path: BezPath},
                       Handle{pos: Point, kind: HandleKind}, BrushOutline{center: Point, radius: f32, ..},
                       Text{pos: Point, text: String}, Guide{..}, Grid{..} }

Coalesced high-rate pen samples come from platform and are handed to tools as a batch, so the brush sees every sample even when the UI renders at 60–120 Hz.

4.4 Viewport: document to pixels

// picto-viewport
pub struct Viewport { pub camera: Camera /* zoom, pan, rotation, dpr */, /* tile/mip cache */ }
impl Viewport {
    pub fn render_gpu(&mut self, snap: &DocSnapshot, gpu: &GpuContext, target: &wgpu::TextureView, damage: &Region);
    pub fn render_cpu(&mut self, snap: &DocSnapshot, target: &mut RgbaImageMut, damage: &Region); // wasm fallback, tests
    pub fn doc_to_screen(&self) -> Affine;  pub fn screen_to_doc(&self) -> Affine;
}

4.5 View-models and schemas

Litmus test for the seam: apps/picto-cli and the automation server must be able to do everything the GUI does, except pointer painting, which they do by replaying PointerEvent streams through tools.


5. Document model (picto-doc)

pub struct Document {
    pub id: DocId, pub size: Size, pub resolution_dpi: f32,
    pub mode: ColorMode /* Rgb | Gray | Cmyk | Lab | Bitmap | Indexed */, pub depth: Depth /* U8 | U16 | F32 */,
    pub profile: IccProfileRef,
    pub root: LayerGroup,            // tree
    pub channels: Vec<AlphaChannel>, // saved selections/spot
    pub paths: Vec<NamedPath>, pub guides: Guides, pub slices: .., pub metadata: Metadata /* XMP, EXIF */,
    pub selection: Option<Mask>,     // current selection (a raster mask + optional vector outline)
}

pub struct Layer { pub id: LayerId, pub name: String, pub visible: bool, pub locks: Locks,
                   pub blend: BlendMode, pub opacity: f32, pub fill_opacity: f32, pub clipped: bool,
                   pub mask: Option<RasterMask>, pub vector_mask: Option<VectorMask>,
                   pub effects: Effects, pub blend_if: BlendIf, pub content: LayerContent }

pub enum LayerContent {
    Raster(TiledSurface),
    Group(LayerGroup /* children, pass-through or isolated */),
    Adjustment(Adjustment /* Curves, Levels, HueSat, … as params */),
    Fill(Fill /* solid | gradient | pattern */),
    Text(TextLayer /* runs, paragraph styles, warp; cached raster */),
    Shape(ShapeLayer /* vector paths + fill/stroke */),
    Smart(SmartObject /* embedded or linked source + transform + smart filters */),
}

6. Operations and history (picto-ops)


7. Rendering

7.1 Planner (compose)

plan(&DocSnapshot, region) -> Vec<DrawOp> flattens the layer tree into a linear instruction stream:

Both backends consume the same plan. This is the only place that encodes Photoshop layer semantics: pass-through groups, clipping groups, fill vs opacity, blend-if and knockout.

7.2 Backends

7.3 Color

7.4 Oracle

PSD files contain Photoshop's own merged composite. Running our compositor on a PSD's layers and diffing against its embedded composite gives a free, large conformance test suite. testkit automates this over a PSD corpus. The corpora are fetched at pinned commits into corpus/ (never committed) by cargo xtask corpus --all; our own Photoshop-authored oracles live in https://github.com/storytold/photocraft-corpus (see docs/development.md › Test corpora).


8. picto-psd: standalone PSD/PSB crate

Scope: faithful, format-level read and write of PSD (v1) and PSB (v2, 64-bit lengths). It has no dependency on our document model.

psd/src/
  header.rs         color mode, depth, size
  color_mode_data.rs
  resources/        image resources (1005 resolution, 1036 thumbnail, 1039 ICC, 1060 XMP, 1058 EXIF, slices, guides, …)
  layers/           layer records, channel image data (raw | RLE/PackBits | ZIP | ZIP+prediction), masks, blending ranges
  tagged/           additional layer info blocks, one module per key:
                    luni lsct lyid lfx2/lrFX (effects) TySh (text) SoLd/PlLd/lnk2 (smart objects) vmsk/vsms/vogk (vector)
                    adjustment keys (curv levl hue2 blnc selc mixr grdm phfl brit thrs post nvrt expA vibA clrL blwh)
                    SoCo GdFl PtFl (fills), Patt, Txt2, …
  descriptor.rs     ActionDescriptor / OSType parser+writer (used by many tagged blocks)
  engine_data.rs    text "EngineData" (PostScript-like) parser+writer
  image_data.rs     merged composite
  write.rs          serializer (+ RLE/ZIP encoders)

9. Native file format (picto-format)


10. Algorithms (algo, paint, text, vector, ml)

Area Contents (first pass) GPU kernel?
Adjustments levels, curves, brightness/contrast, exposure, vibrance, hue/sat, color balance, B&W, photo filter, channel mixer, gradient map, selective color, invert/posterize/threshold, 3D LUT yes (LUT + small kernels)
Filters gaussian/box/motion/radial/surface/lens blur, unsharp/smart sharpen, high pass, noise add/reduce/median/dust, distort family (twirl, wave, polar, spherize, displace), stylize, render (clouds, lens flare), pixelate, color to alpha many
Selection marquee/lasso geometry → mask, magic wand (flood fill tolerance), color range, quick select (graph cut / superpixels), feather, grow/shrink, refine edge some
Inpaint/heal spot heal, healing brush (Poisson), content-aware fill (PatchMatch + multiscale EM) later
Warp free transform (affine/perspective/warp mesh), liquify, puppet (ARAP), lens correction, resampling (Lanczos/bicubic) yes
Paint (paint) dab generation (hardness, roundness, angle, spacing, scatter), dynamics from pressure/tilt/velocity, smoothing (pulled string / lazy mouse), wet edges, airbrush, eraser, clone/heal sources, blend modes per stroke, .abr import CPU first, GPU later
Text (text) font DB trait (system fonts native, bundled fonts on web), parley layout, per-run styles, paragraph options, warp; renders to a raster cache —
Vector (vector) shapes, bezier paths, pen tool geometry, strokes (dash, align, caps), boolean ops → coverage via vello_cpu —
ML (ml) InferenceBackend trait; tasks: segment_point/box (SAM2), matte_subject (background removal), depth, sky; model registry with SHA-256 verification and on-demand download backend-dependent

Rules for every algorithm:


11. Engine runtime model

 UI thread ──dispatch()──► Engine thread (owns mutable docs, applies ops, publishes snapshots)
     ▲                         │  spawns
     │ events (damage, progress)│
     │                         ▼
     │                  Job pool (rayon): filters, AI, export, autosave — read snapshots, return transactions
     │
 Render (UI thread or dedicated): Viewport::render_gpu(snapshot) into toolkit texture

12. Automation and extensibility


13. Testing and quality

Layer What
Unit per crate; algorithms against small hand-computed fixtures
Golden images testkit::assert_image_eq(actual, "golden/name.png", tol); goldens regenerated by xtask bless
CPU/GPU parity every GPU kernel vs its CPU reference, software adapter in CI
PSD round-trip, composite oracle, fuzzing
Engine command-level tests: open fixture → dispatch commands → assert snapshot/render; action replays
UI headless egui tests (egui_kittest) for panels; kept thin because the UI is thin
Perf criterion benches + budget checks in CI (composite 4k² 20 layers, brush dab throughput, open 100 MP)
Portability CI matrix: macOS arm64, Windows x64, Linux x64, wasm32 build check

Performance targets for v1:


14. Cargo workspace sketch

[workspace]
resolver = "3"
members = ["crates/*", "apps/*", "xtask"]

[workspace.package]
edition = "2024"
license = "MIT OR Apache-2.0"        # decision pending, see §16
rust-version = "1.95"

[workspace.dependencies]
# foundation
bytemuck = { version = "1", features = ["derive"] }
kurbo = "*"          # pin at scaffold time
glam = "*"
smallvec = "1"
arc-swap = "1"
rayon = "1"
thiserror = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1"
blake3 = "1"
zstd = "*"           # native; ruzstd for wasm decode
# color / codecs
# ICC: our own picto-cms (pure Rust)
image = { version = "*", default-features = false }
jxl-oxide = "*"
# gpu
wgpu = "*"           # one version shared by gpu, viewport, ui
# text / vector
parley = "*"
skrifa = "*"
vello_cpu = "*"
# ml
ort = { version = "*", optional = true }
# automation
rmcp = "*"

[workspace.lints.rust]
unsafe_code = "deny"   # allowed per-crate with justification (gpu, platform)

[profile.release]
lto = "thin"
codegen-units = 1

[profile.dev.package."*"]
opt-level = 2          # image code is unusable at opt-level 0

Dependency requirements are centralized in the workspace Cargo.toml ([workspace.dependencies]); Cargo.lock records the exact resolved versions.


15. Phased roadmap

Each phase ends with a demoable build and green CI on all targets. Phases 8–10 (colour/print pro, photographer/designer pro, frontier) and the per-feature phase assignments are in target-app-parity.md.

Phase Scope Done when
0. Skeleton (1–2 wk) workspace, all crates stubbed, layering check, CI matrix incl. wasm, testkit, xtask cargo xtask ci green on 3 OS + wasm
1. Viewer raster doc, tiles, codecs (png/jpeg/tiff/webp), viewport pan/zoom/rotate on GPU + CPU fallback, .pcraft save/open, CLI convert open a 100 MP JPEG, pan at refresh rate, save/reopen losslessly
2. Layers layer tree, all 27 blend modes, opacity/fill, masks, groups, clipping; planner + CPU/GPU compositors + parity tests; history; PSD read fidelity 1–3 composite-oracle suite ≥ 95% within tolerance on the corpus
3. Paint and select platform tablet input, brush engine + presets, eraser, marquee/lasso/wand, move, free transform, crop draw with pen pressure at <1 frame latency; selection-restricted painting
4. Adjust and filter adjustment layers (first 10), first 20 filters with schema-generated dialogs + live preview; PSD write; actions recording PSD open → edit → save opens correctly in Photoshop/Photopea
5. Text, vector, styles text layers, shapes/pen/paths, layer effects (drop shadow, stroke, glows, overlays, bevel) PSD text/shape/effects round-trip at fidelity 4–5
6. Smart features ML backend + SAM2 select subject/object, background removal, content-aware fill, healing, RAW develop select subject + remove background on a portrait in <3 s
7. Automation and web MCP server + CLI parity, batch; picto-web demo an agent edits a PSD through MCP; the web demo opens a PSD, paints and filters

16. Open decisions

  1. License. MIT/Apache-2.0 is maximally reusable. GPL-3.0 is the Krita/GIMP model and protects against proprietary forks. Note that some candidate deps are LGPL (rawler) or GPL-or-commercial (Slint), and MIT/Apache would constrain which of them we can use.
  2. Project name. picto (the repo name) is assumed.
  3. PSD crate. Build our own, or adopt or fork ag-psd/psd. Decide after the week-1 spike.
  4. Default working depth and blending. 8-bit + gamma-space blending matches Photoshop. 16-bit/float + linear is the modern default. The proposal is to follow Photoshop by default and make the other a per-document option.

Revision history

Date Change Summary
2026-10-21 minor Removed references to the deleted rust-framework-options.md; dropped the UI toolkit and plug-in items from Open decisions, both recorded as decided
2026-10-20 minor GPU display matrix/shaper path preserves near-black ICC values; complex transforms retain the 3D LUT fallback (#7958)
2026-10-14 minor Principle 5: every large payload in the document model is Arc-shared, so snapshots never copy one (#4535)
2026-10-13 minor Workspace size and #[test] counts recounted (393k → 539k lines, 5,460 → 8,040 tests); other current-state figures unchanged
2026-10-10 minor Current-state summary and deviations from the plan; status line; broken link to photoshop-parity.md repointed to target-app-parity.md
2026-09-30 major Design plan v1