Commit Graph

47 Commits

Author SHA1 Message Date
Brandon Thomas 7a5c1930b3
Sandboxed WebAssembly plug-ins: photocraft-plugins (wasmi), plugin.* commands, Filter › Plug-ins (#67)
* photocraft-plugins: sandboxed WebAssembly filter plug-ins (wasmi host, ABI v1)

A new L4 crate runs plug-ins in wasmi (pure Rust, MIT/Apache) with no host
imports, a fuel budget metered in slices with a wall-clock deadline, a memory
cap, a recursion cap and wasmi's strict module limits. Every band gets a fresh
instance; traps, budgets, bad manifests and bad pointers are all Errors.
Pixels go over as interleaved normalised f32 in tile-aligned bands, in
parallel on native, blended by the selection. Includes the example Invert
plug-in (no_std Rust, examples/plugins/invert-rs) built into a test fixture,
hostile-module tests, and docs/plugins.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Engine: plugin.* commands (install, list, run, remove, reload)

plugin.run filters the active pixel layer, a targeted channel or a smart
object (recorded as a smart filter) as one undo step; Preferences >
Plug-ins gains Use Additional Plug-ins Folder, loaded when prefs apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI: Filter > Plug-ins lists installed plug-ins with generated dialogs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plug-ins: bands never outgrow the memory cap; regenerate parity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bench: example plug-in Invert vs built-in Invert on 24 MP

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:08:28 -04:00
Brandon Thomas d5eb44e9fd
Colour-managed canvas: document profile -> monitor (P3/Adobe RGB/ProPhoto/Gray/CMYK), linear EXR, embedded CMYK (#66)
* CMYK documents display and export through their embedded CMYK profile

A thread-local CmykSpace (photocraft_color::convert::with_cmyk_space) replaces the built-in
coated CMYK while the CPU compositor, the GPU compositor's uploads and the PSD merged
composite convert a document's CMYK pixels; GPU residents track the profile they were
converted with.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* EXR/HDR are linear: tag imports linear sRGB, linearise exports

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Colour-managed canvas: document profile -> monitor profile on GPU and CPU paths

* engine display_color: CanvasDisplay per (document profile, mode, monitor), identity fast
  path when they match (sRGB on sRGB costs nothing); linear composites are stored
  sRGB-encoded in the 8-bit texture. canvas_lut now always carries the CM transform, with
  Proof Colors / Gamut Warning / 32-bit preview on top.
* GPU canvas applies the display LUT whenever the transform isn't the identity (also for
  filter/proxy previews); the CPU canvas and Navigator transform the 8-bit composite.
* Edit > Color Settings > Monitor Profile (auto|built-in|.icc). macOS 'auto' reads the main
  display's ICC profile at launch via osascript/AppKit (no unsafe); elsewhere sRGB.
* Tests: P3 (hand-computed), Adobe RGB, ProPhoto, Gray Gamma 2.2, synthetic CMYK, linear,
  monitor setting; GPU+CPU canvas test via kittest (skips without Rgba32Float).

Fixes #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* io: tag EXR/HDR linear in import_flat (adapt to the shared image_to_document)

#65 moved flat decoding into image_to_document(name, img) so camera raws can reuse it; the
linear-EXR tagging needs the file bytes for format detection, so it now runs in import_flat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:44:13 -04:00
Brandon Thomas 297f7f8521
macOS: open files from Finder/Open With/Dock; file path, recent, warnings and .pcraft fixes (#63)
* File open/save: set the path everywhere, Open Recent, show errors and import/export warnings

File › Open, drag-and-drop onto the window and launch paths now share
PhotocraftApp::open_file: the document is named after the file (not the full
path), keeps its path so File › Save writes back, and goes to Open Recent.
Failures are shown as errors (status bar + a non-blocking notice).

Import/export warnings from photocraft-io are no longer thrown away: they show
in the status bar and in a dismissable notice (native and web), and the
control protocol returns them from app.open/app.save/file.open/file.save/
file.saveAs/file.saveACopy. .pcraft is in the Open and Save filters, and
File › Save writes .pcraft documents in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* macOS: open files from Finder, Open With and the Dock (Apple events)

Finder double-click, Open With, drops on the Dock icon and open -a deliver
files as kAEOpenDocuments Apple events, not argv, and winit 0.30 ignores them.
Register an NSAppleEventManager handler through the small fmv-macos-events
crate (MIT OR Apache-2.0, 168 lines, audited, pinned =0.1.0; it keeps the
Objective-C unsafe out of our forbid(unsafe_code) workspace) before the event
loop starts, so both the launching event and later ones reach the UI as
OsEvent::Open; the window is focused. The quit Apple event goes through the
normal close path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:18:03 -04:00
Brandon Thomas b17c239db8
UI input: Ctrl+'+' zooms the canvas, dialogs don't cancel on outside click, menu hover-switch (#62)
* Shortcuts match like Photoshop: Ctrl/Cmd + '+' zooms, exact modifiers, clipboard keys

- Cmd/Ctrl+'=' also matches '+' (Shift+= on US layouts, numpad +, the '+' key on
  Nordic/German layouts); Delete also matches Backspace (the Mac delete key).
- Modifiers match exactly (Cmd+Alt+I no longer runs Cmd+I); Shift shortcuts on
  punctuation match the shifted character (Cmd+Shift+; arrives as Cmd+':').
- Zoom In/Out, Fit on Screen and 100% stay live while a dialog or a non-text
  widget has focus.
- clipboard_keys: egui-winit turns Cmd+C/X/V into Copy/Cut/Paste events (and
  drops Cmd+V when the clipboard holds an image); outside text fields they
  become key presses again so Edit > Copy/Cut/Paste/Copy Merged run.
- Snapping: Ctrl (Windows) / Control (Mac) disables snapping only while held
  during the drag, not at the press (Ctrl-click belongs to the tool).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Dialogs: no cancel on outside click; canvas pans and zooms under them

- Disable egui's keyboard UI zoom: Cmd/Ctrl +/-/0 never scale the interface.
- A click outside a dialog no longer silently cancels it (Esc still does).
- With a dialog open the canvas still scrolls, pinch/Cmd-scroll zooms and
  Space/middle/Hand-drag pans, like Photoshop; everything else stays modal.
- Clipboard key presses restored in raw_input_hook.
- Studio Properties card: Order::Middle (menus, popups, dialogs stay above it)
  and draggable by its title off the area being worked on.
- Magic Wand options (tolerance, anti-alias, contiguous, sample all layers) in
  the Studio themes too.
- egui_kittest tests: Ctrl+Plus zooms the canvas and leaves zoom_factor at 1,
  zoom works with a dialog open, outside clicks keep the dialog, scroll and
  space-drag pan under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Menu bar: hovering another title switches the open menu

Like Windows and macOS menu bars: with a menu open, moving to another top-level
title opens that menu. Tests for the switch and dropdown row spacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:56:12 -04:00
Brandon Thomas 86cc989e72
Never crash: ui-egui is panic-free (Rule 9) (#57)
- canvas: partial redraws read last_damage with unwrap_or(EMPTY), and the
  GPU cache is looked up with let-else after the upload.
- icons: a missing icon (and missing fallback) yields empty bytes, which
  egui shows as a broken image, instead of expect.
- type tool: the caret update is an if-let instead of expect("editing").
- Variables dialog: pages return early if their JSON state is malformed
  instead of unwrap.
- parity: the same hunk as #40.
- ui-egui denies unwrap/expect/panic/unimplemented/todo/unreachable
  (a one-line attribute in lib.rs).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:40:17 -04:00
Brandon Thomas 91ef877f2f
Never crash: algo pyramids and ui-egui parity without expect (#40)
Three remaining expect() sites in files no open PR touches:
algo::inpaint::complete and algo::pyramid::gaussian loop with while-let
instead of levels.last().expect(), and ui-egui parity::compute indexes
the menu it just pushed instead of last_mut().expect().

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:26:13 -04:00
Torstein 29506f3e93
feat(ui): confirm before discarding unsaved changes (#31)
Close, Close All, Close Others, Revert, Exit and the window close button
now ask Save / Don't Save / Cancel when a document has unsaved changes.
The action is parked until each dirty document has been answered, then
re-run. Window close requests are cancelled while the prompt is open.

Closes #9
2026-10-04 12:21:25 -04:00
Brandon Thomas 71272d5cd7 GPU: fs_adjust compiles under FXC (D3D12); a pipeline build failure falls back to the CPU
On D3D12 without DXC, wgpu compiles HLSL with FXC, which aborted on
fs_adjust ("internal error: compilation aborted unexpectedly"). wgpu turns
that into a panic, so every GPU test failed on the Windows runner, and the
app would crash where the same path is taken. A bisect on the Windows
runner pinned it to two cases of adjust()'s switch, Exposure and Selective
Color: both looped over dynamically indexed vector components. They are
now written per channel / per range with the same math (GPU-vs-CPU parity
is unchanged and passes on Metal and on the Windows runner).

Compositor::try_new_with_format catches validation and internal errors
while building the pipelines and returns Err; the canvas then uses the CPU
compositor (once, remembered) instead of crashing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:33:15 -04:00
Brandon Thomas 1c09936154 Clippy 1.99: chunks_exact with a constant size -> as_chunks; drop a no-op Option filter
Rust 1.99's clippy adds chunks_exact_to_as_chunks and some_filter, which
broke the Clippy step on every CI runner (it uses the latest stable). All
sites are mechanical: as_chunks drops the remainder exactly like
chunks_exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:33:14 -04:00
Brandon Thomas f7eb6ea9e7 Format: adopt the craft-family rustfmt.toml and format the workspace
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:31:17 -04:00
sommerper 159c11feb4
Layer Style dialog previews effects live on the canvas (#30)
The canvas only showed effects after OK. While the dialog is open, the
canvas now renders the document with the dialog's style applied: the same
commands OK runs, replayed on a scratch session (no history), cached by
document revision + style fields so switching pages doesn't re-render.
Cancel drops the preview. Works on both GPU and CPU canvas paths
(display_doc). Tests cover the preview, re-render on edit, and cancel.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:13:36 -04:00
sommerper 8d21ca349d
Dialogs can be moved by dragging the title bar (#29)
All dialogs (Layer Style, New Document, filters, ...) were pinned to the
centre. Dragging the title row now moves them, clamped so the dialog stays
on screen. The title text is no longer selectable, so dragging it moves
the dialog instead of selecting text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:13:31 -04:00
Brandon Thomas 35896f056a
Add File > Open Recent (fixes #5) (#23)
Tracks opened file paths (de-duplicated, newest first, capped at 10) in UI
state, and shows them as a dynamic File > Open Recent submenu after Open As…,
plus Clear Recent Files. Clicking an entry reopens that path; a stale index
errors gracefully. Recorded on both dialog opens and path opens (file
associations / control channel). Tests cover tracking/dedupe/cap/listing/clear
and a real open → recent → reopen round-trip.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 02:39:58 -04:00
Brandon Thomas ae7d802726
GPU: fall back to Rgba16Float when Rgba32Float isn't renderable (#7, #4) (#22)
Some drivers (e.g. Intel Vulkan on Linux/Windows) can't render to Rgba32Float,
so creating the 'fs_content' pipeline panicked with 'Format Rgba32Float is not
renderable' as soon as a document/image was opened. The accumulation format is
now chosen per adapter: Compositor::preferred_acc_format() keeps Rgba32Float
where it's a valid render target and falls back to Rgba16Float otherwise. The
compositor carries the format (pipelines, accumulation texture, byte size and
the readback half-decode all follow it); the app picks it from the eframe
adapter. Capable GPUs are unchanged (parity still exact, 18/18). A new test
forces the Rgba16Float path on any GPU and confirms it renders within display
tolerance (blend + adjustment). wasm green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 02:27:54 -04:00
Brandon Thomas 34d138c11f
Links open in the system browser via a platform service (#14) (#20)
ctx.open_url is unreliable on native (notably Windows), so the Help menu,
the Discord buttons, and the start-page links did nothing. Added an open_url
Service hook wired to the `open` crate on native (ShellExecute on Windows);
links::open prefers it and falls back to ctx.open_url (web). link_row now
routes clicks through it instead of ui.hyperlink_to. Test drives every Help
link id + the open() helper through a mock service.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 01:34:18 -04:00
Torstein 09c987f1a8
fix(ui): give menu bar labels more breathing room (#8)
Preserve menu label spacing after egui installs its default menu-bar style.

- Apply six pixels of horizontal padding per side.
- Cover label padding, overlap, and File-menu interaction across themes.
2026-10-04 01:06:35 -04:00
Torstein e6e82ca74d
feat(ui): add Exit to the File menu (#10)
Closes #3
2026-10-04 01:03:49 -04:00
Multipad-cyber ce13708af3
Layers: Add Layer Mask uses the active selection (#11)
Like Photoshop, the Add a mask button in the Layers panel and the
layer context menu's Add Layer Mask now turn an active selection into
the mask (Reveal Selection) and fall back to Reveal All without one.
Option-click on the button inverts it (Hide Selection / Hide All).

The choice lives in layer_menu_ui::add_mask_command so the button and
the menu agree; it only picks between the existing layer.layerMask.*
commands. Tests cover the mapping, the menu entries with and without a
selection, and that the new mask matches a rectangular selection.

Co-authored-by: Multipad-cyber <246280291+Multipad-cyber@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:02:49 -04:00
Brandon Thomas 5f84a7add8 Gradient dithering (reduces 8-bit banding), wired in the UI
paint_gradient gains a dither flag: one quantisation step of monochromatic
position-hash noise, breaking banding without colour speckle. paint.gradient
takes "dither":bool (default true, like Photoshop); the options-bar Dither
checkbox now persists (gradient_dither) and is sent. New gradient_dither_
breaks_banding test; clippy + wasm green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 18:26:30 -04:00
Brandon Thomas c89c33c704 Brush: paint with a blend mode (options-bar Mode), wired in the UI
paint.stroke/paint.pencil now honour a "mode" param (normal|multiply|screen|…),
and the options-bar Mode dropdown sets the session brush's blend mode (it was
decorative). The Eraser is forced to Normal (no blend mode in Photoshop). New
brush_blend_mode_multiply test; clippy + wasm green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 13:19:21 -04:00
Brandon Thomas 9dc394dfc8 UI: wire Paint Bucket Fill dropdown (Foreground/Pattern) to the engine
The options-bar Fill dropdown was decorative. It now persists in tool_options
(bucket_fill_pattern) and the click sends contents=foreground|pattern, so
Pattern actually flood-fills with the current pattern. ui tests + wasm green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 13:12:50 -04:00
Brandon Thomas 6152c0a402 Timeline: Play/Pause (auto-advance the playhead at the frame rate)
Play loops the playhead via timeline.setFrame at the timeline fps, using
egui time (wasm-safe, no Instant) and requesting repaints; pausing/closing
stops it. New pause icon. ui-egui tests + wasm green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 03:19:17 -04:00
Brandon Thomas 58653e617b File > Import > WIA Support: 625/625 menu items live (100%)
Platform-gated command for Windows Image Acquisition (scanners/cameras).
Reports device availability; real WIA COM device I/O is Windows + hardware
only (Photoshop gates this item to Windows too), so off-Windows it returns
available:false with zero devices. 1 test. Parity 624 -> 625 (100.0%);
FLOOR 620. Every Photoshop menu item now has a live command.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 02:56:15 -04:00
Brandon Thomas 53b6f2bafb Video: file footage, Frames to Layers, Render Video (parity 624/625, 99.8%)
layer.videoLayers.{newVideoLayerFromFile,replaceFootage,interpretFootage,
reloadFrame} load frames from a single image or an image-sequence folder;
file.import.videoFramesToLayers adds each frame as its own layer;
file.export.renderVideo composites the whole document at every timeline
frame and writes an image sequence (clean-room, no proprietary codec).
6 tests. Only Windows-only WIA Support remains. Parity 618 -> 624 (99.8%);
FLOOR 619.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 02:50:44 -04:00
Brandon Thomas 9cfbe11bf1 Layer > Video Layers: frame stacks + Rasterize Video (parity 618)
Layer.video (VideoData: frame surfaces + fps + source); the layer's
displayed content is the frame at the timeline playhead, re-synced after
any edit/navigation. Commands: New Blank Video Layer, Insert Blank /
Duplicate / Delete / Restore Frame, Restore All Frames, Show Altered
Video, Rasterize (Layer > Video Layers > Rasterize and Rasterize > Video).
Timeline duration grows to fit. 4 tests; engine green, clippy clean, wasm
OK. Parity 609 -> 618 (98.9%); FLOOR 613. (File-based footage + Render
Video + Video Frames to Layers + .pcraft frame persistence are follow-ups.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 02:43:52 -04:00
Brandon Thomas a35fe54d7c Timeline: register window.panel.timeline as live (parity 609)
timeline_ui::handles() wired into menus::is_live so the Timeline panel
counts toward parity. 608 -> 609 (97.4%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 02:23:51 -04:00
Brandon Thomas c080ed3e72 Window > Timeline panel
Floating Timeline panel driving the engine timeline.* commands: Create
Video Timeline, frame counter + prev/next, playhead scrubber, fps,
delete. UI stays in ui-egui (behaviour is engine commands). Parity
608 -> 609; FLOOR 604. Snapshot-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 02:21:23 -04:00
Brandon Thomas 1c474b8c7e Image > Trap (CMYK prepress trapping)
Clean-room trap: per-ink grayscale dilation (separable max filter,
par_rows/wasm-safe) applied only under already-inked pixels, so internal
colour edges get a trap band while object-vs-paper edges stay clean.
Engine image.trap (CMYK-only, active pixel layer, one history step).
algo 3 tests + engine 2 tests. Parity 607 -> 608; FLOOR 603.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 00:44:18 -04:00
Brandon Thomas 61aa2f9748 Edit > Presets > Migrate Presets
Merge a presets/preferences file (another install, or exported library)
into the current preset library, appending gradient/style/shape/pattern/
tool-preset groups the library lacks (by name); idempotent. Headless,
2 tests. Parity 606 -> 607; FLOOR 602.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 00:37:37 -04:00
Brandon Thomas 571230fdf8 Layer > New > Frame from Layers
Groups the selected layers and clips them to a frame rect (their bounds)
via a rectangular layer mask — the visual result of Photoshop's Frame for
this command, built from existing group + mask primitives (no doc-model
change). Headless/scriptable; 2 tests. Parity 605 -> 606; FLOOR 601.
(A distinct Frame layer type + the interactive Frame tool K are a later arc.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 20:40:32 -04:00
Brandon Thomas 3afb32105f Variables UI: Image > Variables (Define / Data Sets) dialog
Photoshop's data-driven graphics dialog in the ui-egui crate, driving the
headless engine commands: Define page (per-variable name + layer + type,
add/remove), Data Sets page (navigator, per-variable value editors, Apply).
State lives in the dialog's __variables field (command-shaped JSON), so the
control channel drives it via ui.dialog.set / confirm like a human. 2 tests;
offscreen snapshot verified. UI stays in ui-egui; all behaviour is engine
commands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 20:07:31 -04:00
Brandon Thomas 79fd79c2ae Variables and Data Sets: data-driven graphics (Image > Variables, data-set import/export)
Clean-room implementation of Photoshop's data-driven graphics, headless
and scriptable for CLI/MCP batch generation:
- doc: Variables model (visibility/text/pixel-replacement bindings + named
  data sets), threaded through .pcraft (manifest + convert).
- engine variables_cmds.rs: image.variables.define, image.variables.dataSets,
  image.applyDataSet (visibility/text/pixel replace w/ fit/fill/conform/as-is
  + 9 alignments, one history step), file.import.variableDataSets (CSV),
  file.export.dataSetsAsFiles (apply each set and export), variables.list.
- Pixel replacement loads+scales+aligns an image into the layer bounds via
  the existing resample helpers.
- 7 tests (doc round-trip + lookups; engine define/apply, CSV import, export,
  pixel replace, bad params). Parity 600 -> 605; FLOOR 600.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-02 16:43:22 -04:00
Brandon Thomas 9c9a680e45 Remove references to Photon Studio from docs, AGENTS.md and code comments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:25:59 -04:00
Brandon Thomas 42266291d5 Fidelity, GPU fallbacks, interactive performance; print spool-file fix
PSD oracle 111 -> 113 (tent effect blurs, bevel height/shading, glow
range, spread rounding, text gamma, gradient span, Lab blending, colour
burn/dodge edges), channel restrictions (brst), bevel contour/texture,
chisel techniques. Vector masks, pass-through clips, artboards, pattern
fills and bevel textures render on the GPU (parity <=1/255). PNG export
7x, PSD save 4x, .pcraft save 3.6x faster; CPU effects refresh 11x;
deadlock in the effect-map blur fixed. interactive_bench example.

Print: unique spool file per call (concurrent prints of same-named
documents overwrote each other).

Workspace: 2,040 tests pass, clippy clean, layers + wasm OK, parity 600/625.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 01:26:34 -04:00
Brandon Thomas a9c3efbb51 Default to Photoshop's default interface brightness (Pro Medium Gray)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:44:52 -04:00
Brandon Thomas 6f5aba08d3 Photomerge, Merge to HDR Pro, Lens Correction, Adaptive Wide Angle, Camera Raw, Vanishing Point
Harris+BRIEF/RANSAC panorama with bundle adjustment, seams and multi-band
blending (Auto-Align/Auto-Blend share it); Debevec-Malik HDR merge with
ghost removal and tone mapping; Brown-Conrady lens correction (filter,
batch, smart filter); wide-angle constraint mesh warp; Crop and
Straighten Photos; Camera Raw Filter (Basic, curves, mixer, grading,
detail, effects) with dialog; Vanishing Point planes (engine only).
Fixes warp_triangles tile binning.

Checkpoint also includes compiling in-progress work of the remaining agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:23:49 -04:00
Brandon Thomas ceb2667ac4 Preset panels, character/paragraph styles, Glyphs, Check Spelling
Gradients/Patterns/Styles/Shapes/Tool Presets/Clone Source panels with
headless preset commands and a Custom Shape tool; Color card tabs match
Essentials. Character and Paragraph Styles (doc model, .pcraft, PSD style
sheets), Glyphs panel + type.insertText, Check Spelling (SCOWL-50 list).

Checkpoint also includes compiling in-progress work of the remaining agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:02:34 -04:00
Brandon Thomas aa87012aab Analysis, notes, proof views, workspaces (+ in-progress work of other agents)
Image > Analysis (ruler, count, measurement scale/log/record, scale
marker), notes (PSD Anno round trip, Notes panel, import), Modifier Keys
panel, Proof Setup plates / Legacy Mac RGB / colour blindness (Vienot),
32-bit preview options, custom pixel aspect, extras options, New/Delete/
Lock Workspace. Parity 600/625, FLOOR 595.

Checkpoint: also contains compiling in-progress work from the parallel
panels, type styles, File menu, photo, filters, UI-parity and fidelity
agents, plus photocraft-cli serve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:49:07 -04:00
Brandon Thomas 08c6c08e2e Discord, website and GitHub links; fix AppStream validation on 22.04
- Help menu: Join the ArtCraft Discord, PhotoCraft Website
  (getartcraft.com/apps/photocraft), ArtCraft Website, PhotoCraft on
  GitHub, Report an Issue; separator before About. All also in the ⌘K
  palette and drivable via the control channel (returns {"url"}).
- "Join us on Discord" button + website/GitHub/ArtCraft links on the
  start screen and in About; a Discord button in the title bar.
- Linux metainfo: drop url type vcs-browser (unknown to appstream 0.15 on
  ubuntu-22.04, which failed both Linux release jobs); homepage is the app
  page, contact is Discord. nfpm homepage and Windows ARP links updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:04:04 -04:00
Brandon Thomas 1f1ddb2a47 Release pipeline id renames and PhotoCraft branding
- Lowercase reverse-DNS id ai.storyteller.photocraft across icons, Linux
  desktop/metainfo/mime/flatpak, Windows WiX, macOS Info.plist and workflows.
- PhotoCraft display name in the title bar, start screen, web loader,
  Windows exe metadata, README and docs.
- .gitignore target/, roadmap refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:25:17 -04:00
Brandon Thomas d98bee4c94 progress, release workflows 2026-10-01 10:49:51 -04:00
Brandon Thomas aad724f28e work in progress 2026-10-01 10:07:04 -04:00
Brandon Thomas 6b44c393fa continued work 2026-10-01 09:16:17 -04:00
Brandon Thomas 536a7346fb more algorithms, progress 2026-10-01 08:25:06 -04:00
Brandon Thomas a517477da8 readme, more impl 2026-09-30 11:15:03 -04:00
Brandon Thomas ae92ac20bf continued wip 2026-09-30 09:37:18 -04:00
Brandon Thomas 16941f7198 one-shot 2026-09-30 05:02:08 -04:00