Commit Graph

55 Commits

Author SHA1 Message Date
aiongg 050caf9ecd Merge PR #6681: Content-Aware Move: live preview for large selections (TOOL-213-4, #6680)
# Conflicts:
#	docs/gaps.md
#	docs/scorecard.md
#	docs/target-app-parity.md
2026-10-21 05:04:04 +00:00
aiongg 4804148ff9 Merge PR #3759: Pattern Stamp and Background Eraser draw live; Spot Healing shows its heal area (TOOL-213-7, #3754)
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-21 05:04:04 +00:00
aiongg fc371711e6 Merge PR #5847: Shapes: Density and Feather on shape layers (#5846)
# Conflicts:
#	docs/scorecard.md
#	docs/target-app-parity.md
#	docs/ui-parity.md
2026-10-21 05:04:03 +00:00
David Mann a23d48f130 Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:25:37 +02:00
David Mann 73ba0b7a68 Merge main into feat/tool-213-4-cam-preview-large
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:12:13 +02:00
David Mann 38605ceac5 Merge main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	crates/ui-egui/src/canvas.rs
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-20 19:05:39 +02:00
David Mann 0b39361076 Merge main into feat/tool-214-12-shape-feather 2026-10-20 17:12:29 +02:00
Brian T ca69059dd0 Edit: Copy with no selection keeps fill and adjustment layers (#2344) (#7497)
Copy refused fill and adjustment layers ("the active layer has no
pixels"). With no pixel selection they are now taken whole, like smart
objects, type, shape and group layers, and Paste recreates them in this
or another document. An adjustment layer's clip spans the canvas, so
paste placement and the OS-clipboard mirror stay consistent.

Scorecard UI-217-23 note, gaps.md G9 and target-app-parity.md updated;
the row stays partial (a single pixel layer still copies pixels only).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:23:26 -04:00
David Mann 2f21722eb4 Content-Aware Move: live preview for large selections (TOOL-213-4, #6680)
The live preview only covered selections up to 256x256 px; larger ones (the
command takes up to 16 M) errored, the UI remembered the failure and showed
nothing until release.

Engine: a preview whose selection (or landed area) is over 256x256 is now
solved on a scaled-down copy of the area around the selection and where it
lands (area-average resize of the target, or of the composite with Sample All
Layers, and of the selection), on a one-layer document of its own, then
enlarged back (bilinear, float samples, so any depth) over the selection's
shape where it lands and, for a move, where it left. The landed content is
sampled through the scaled placement, so rounding the scaled offset does not
shift it. ContentAwarePreview::refine gives the full-resolution job, with the
command's own margin, so the refined preview is the command's result bit for
bit. Every step checks the JobCtx; the session is never touched; bad params
and selections over the command's limit still error. Selections within
256x256 take the unchanged path (same output as before, checked by hash
against main). The bilinear resampler used by Transform On Drop now runs its
rows in parallel (same results).

Measured (release, 24 MP 8-bit document, Ryzen 9 7950X with other builds
running): coarse preview 600x400 0.26 s, 1500x1000 0.38 s, 3000x2000 0.69 s
(256x256 full-resolution preview: 0.21 s); the exact refine takes as long as
the command (0.8 s, 4.1 s, 9.4 s).

UI: cam_preview shows the coarse result, then starts the refine as a second
cancellable job and swaps it in; a failed refine leaves the coarse result
shown, and a failed first job is still not retried.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 17:32:28 +02:00
David Mann 6bb45c8d5b Shapes: Density and Feather on shape layers (#5846)
A shape layer's path is its vector mask in Photoshop (Properties > Masks),
with its own vector-mask density and feather. PhotoCraft's ShapeLayer had
neither, so soft shapes could not be made, and stroked soft shapes from PSDs
lost both on import.

- doc: ShapeLayer gains `density` (0..1, default 1) and `feather` (px,
  default 0).
- vector: the shape renderer applies them to the fill as a soft vector mask
  (fill shows beyond the path at 1 - density; the feather is the vector
  mask's Gaussian), so the shape's own pixels, the GPU (which samples them)
  and the stroke/fill split all follow. The stroke stays crisp. The feather
  blur moves from compose::masks to vector::feather and both share it and the
  feathered area, so a shape and an equal vector mask render the same pixels.
- compose: soft shapes' bounds reach their fill, their layer effects follow
  the soft alpha, and the split cache key includes density and feather.
- engine: `layer.vectorMask.edit` on a shape layer sets density/feather in one
  history step (path/enabled/linked/invert are refused there); density is now
  validated (finite 0..100) instead of clamped. `layer.vectorMask.info`
  reports a shape's values.
- ui: Properties shows the Vector Mask Density and Feather sliders for a
  selected shape layer in both themes (live drag, one undo per gesture).
- .pcraft: manifest and convert carry both with serde defaults (older files
  load at 100 % and 0 px).
- PSD: export writes them as the vector density/feather mask parameters;
  import reads them into the shape (re-rendered, as the stored pixels lack the
  fill beyond the path). Soft unstroked solid/gradient shapes now import as
  shapes instead of fill layers with a vector mask; pattern-filled ones stay
  fill layers.

Photoshop's 10 px feather on a 100 px black square, row 100, x = 30..72:
ours is within 1 level of every measured value (8, 16 and 32 bits);
density 50 % shows 127/128 beyond the path and 0 inside.

Scorecard: TOOL-214-12 done.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 03:07:46 +02:00
David Mann 8601a7b170 docs: resolve the conflict markers left in the main merge
The merge of main (5a4df1a3) committed unresolved conflict markers in the
status lines of gaps.md, target-app-parity.md and ui-parity.md and in the
Type/Vector rows of target-app-parity.md. Keep main's text and add this
PR's own items back: G8 Warp Text preview done (TYPE-218-7), the Type
row's evidence, and the selection-modifier and Alt-wheel rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 02:05:26 +02:00
David Mann 91bffdec0b Merge main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:57:30 +02:00
David Mann 5a4df1a363 Merge main; BUG-208-4 done, rows re-checked against main
Re-checked every row this PR flips against current main: the cited
tests still exist and none of the rows was flipped meanwhile.
BUG-208-4 is done too: shift_alt_before_the_drag_intersects_the_selection
(marquee_tests.rs) covers the marquee Shift+Alt intersect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:29:48 +02:00
baz 9df067b297 Shapes: on-canvas corner radius widgets for live rectangles (TOOL-214-12) (#5276)
* Shapes: on-canvas corner radius widgets for live rectangles (TOOL-214-12)

As in Photoshop, with Path Selection or the Rectangle tool the active shape
layer's live rectangle shows a round widget at the centre of each corner arc
(a few screen points inside a sharp corner). Dragging one rounds every corner
alike, Alt drags that corner alone; the radius follows the move along the
corner's diagonal from the press, within half the shorter side. The drag
previews the new outline and commits one shape.edit with radii on release,
one history step; a click changes nothing.

Docs: ui-parity row done, TOOL-214-12 note updated (still partial: shape
feather), target-app-parity, regenerated scorecard.

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

* Corner radius widgets: every selected rectangle, Move tool, cursor and readout

As in Photoshop:
- every selected live rectangle shows its widgets, also with the Move tool
  when Show Transform Controls is on;
- dragging any widget gives every selected rectangle the same radius (each
  capped at half its own shorter side); Alt changes that corner on each;
  all the shape.edit calls share one history step (coalesce);
- the pointer over a widget is the arrow with a rounded-corner badge, and a
  drag shows an "R: 25.0 px" readout;
- while dragging, the transform box and the Move rollover highlight hide, and
  the Move tool's snapping started by the press is dropped.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:14:49 -04:00
David Mann e795a09d31 Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 15:10:52 +02:00
David Mann 0543f25682 Merge main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 15:07:24 +02:00
LorenzoFasolino d11be6de78 docs: drop stale missing-tool rows, only Art History Brush is missing (#5063)
Task T-0002 del lab artcraft-lab.

File toccati:
   docs/brush-parity.md      | 5 +++--
   docs/gaps.md              | 6 +++---
   docs/target-app-parity.md | 5 +++--
   docs/ui-parity.md         | 7 ++++---
   4 files changed, 13 insertions(+), 10 deletions(-)

Evidenze: artifacts/T-0002/ (nel lab, non in questo repo).
2026-10-14 01:58:18 -04:00
David Mann 577c4eea67 Merge origin/main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 20:10:40 +02:00
David Mann 9c608e63b7 Merge origin/main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 19:55:08 +02:00
jonathanwxh-cell 01b354a340 Add filename templates to Batch export (#4466)
* Add filename templates to Batch export

* Keep Batch naming evidence out of the shared gap-summary header

* Avoid catalog append conflicts for Batch naming labels

* Keep Batch naming labels clear of catalog append conflicts

* Keep Batch naming details in the automation parity row

* Separate Batch documentation from evolving Actions details

---------

Co-authored-by: jonathnwxh <258340642+jonathanwxh-cell@users.noreply.github.com>
2026-10-13 13:03:02 -04:00
David Mann d2816df448 Merge origin/main into docs/scorecard-stale-rows-2
None of the five rows was flipped on main; every test the notes cite
exists on main. Main made TYPE-218-1 (type on a path) partial, so the
Type recount in gaps.md G8 and the target-app-parity Type row now say
5 done, 3 partial, 1 missing and name it partial. Header lines of
gaps.md and ui-parity.md keep main's entries plus this PR's.
docs/scorecard.md regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:44:44 +02:00
David Mann c951480e99 Merge origin/main into feat/tool-213-7-live-retouch
Background Eraser: main now samples and matches against the pre-stroke pixels (#4552); the
shared dab effect takes the pre-stroke surface so the live preview, whose working areas are
per dab, reads the same pixels as the commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:33:25 +02:00
David Mann 402d005e05 Merge origin/main into docs/scorecard-stale-rows-2
Conflicts in docs/gaps.md, docs/target-app-parity.md and docs/ui-parity.md:
kept main's status lines and rows and this branch's Type, selection-modifier
and Alt-wheel evidence. docs/scorecard.md regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:04:36 +02:00
John Cambra 3a307a757e Play real Photoshop action sets: TrueGrit .atn mappings and fixes (#4356, #219) (#4698)
Third-party action sets that load since 3204231 now play. Four TrueGrit
Texture Supply sets (Debaser, Distress Press, Halftone Zine Machine,
Transparency) go from 223 to 3 unmapped steps of 533 and play end to end.

- .atn import, 108 -> 112 events: Select and Mask (refineSelectionEdge),
  Filter Gallery Spatter (GEfc), Ripple, Crystallize; targeting the layer
  mask or composite channel, loading the targeted channel, Step
  Backward/Forward, removeFromSelection (it silently imported as a plain
  select), Pattern Fill layers (by name), nudges in points, Layer From
  Background, Grayscale/Bitmap mode objects, Image Size by resolution.
- Engine: layer.translate units pt; layer.select mode remove;
  select.loadSelection channel targeted; image.imageSize keepPrintSize;
  layer.new.layerFromBackground ifPresent/select; pattern.import data.
  New from Clipboard keeps the copied document's resolution (was 72 ppi).
- Filters: Spatter's Smoothness smooths the displacement (torn but
  attached edges, as Photoshop's); Crystallize fills a cell from its point
  instead of the average, so masks keep hard edges.
- UI: .pat opens/drops into the Patterns panel (no UI import existed).
- Docs: gaps G11, file-format and target-app parity.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 01:10:58 -04:00
Dan Hiorns 4fd08b5395 Free Transform: H and V skew fields in the options bar (#4699)
* Free Transform: H and V skew fields in the options bar

Photoshop's Free Transform options bar has H and V skew fields after the
angle; ours stopped at the angle (ui-parity: "Options bar H/V skew
fields | missing", gaps G5).

The bar now reads the box as Photoshop does: the frame scaled (W, H),
skewed (H, V) and rotated about the reference point (`Parts`). Typing a
skew leans the box about the reference point and keeps W, H and the
angle. A box can be read with any V skew, so the session keeps the typed
one (`TransformSession::skew_v`, serde default). Skews stop at ±89°.

A sheared box used to read as taller (H = 100/cos h %); it now reads as
an H skew with H at 100 %. Readouts without skew are unchanged, flips
included.

Docs: ui-parity row done, G5 and target-app-parity notes updated.

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

* i18n(sr): translate the Free Transform skew field tooltips

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 00:11:54 -04:00
Valentin Klein 13657f35f2 Docs: refresh target-app-parity and file-format-parity against main (#4417) (#4418)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 20:04:19 -04:00
David Mann 9b70086a2d Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 01:43:25 +02:00
David Mann 001d16cd9f Merge remote-tracking branch 'origin/main' into feat/tool-213-7-live-retouch 2026-10-13 01:15:02 +02:00
Fabio Lissi 90c42f5555 Map Place, Open, Image Size in percent, the Paint Bucket and Insert Conditional (#219) (#4470)
- image.imageSize takes percent (both sides; an explicit side wins).
- actions.conditional {if, then, else}: Photoshop's Insert Conditional as a step, 33 conditions
  on the document (shape, mode, depth, selection, layers, alpha channels, unsaved) and the
  active layer (kind, locks, visibility, masks, effects); a failing branch stops the caller.
- .atn events: placeEvent → file.placeEmbedded/placeLinked (path, scale), open → file.openAs,
  imageSize in percent, fill from a point → paint.bucket (pixels), conditional. 105 → 108.
- Panel labels conditional steps as 'If cond → then / else'.
2026-10-12 19:12:35 -04:00
David Mann 9e22da62f6 Merge main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 01:06:25 +02:00
Fabio Lissi 13f5c104f6 Map the remaining frequent Photoshop action events (#219) (#4465)
- edit.transform gains the relative form (scaleX/scaleY %, rotate, dx/dy about the frame
  centre), as a Photoshop action records a Free Transform; image.applyImage takes a source
  layer by name.
- .atn events: transform (layer → translate or relative transform; selection →
  transformSelection), applyImageEvent, export (Save for Web, with the txtu path record
  decoded and written back; kept verbatim without a destination), newPlacedLayer /
  placedLayerEditContents / updatePlacedLayer, close with the Save answer → save + close,
  delete of the target channel → layer mask delete, Quick Mask on/off, polygon selections →
  select.lasso, Gradient Map (direct and as an adjustment layer). 92 → 105 events.
- Export tries every event listing a command id (Quick Mask on → set, off → clearEvent).
- Docs: event counts in the parity docs.
2026-10-12 19:02:15 -04:00
David Mann 132935eb2e Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	docs/gaps.md
#	docs/scorecard.md
#	docs/target-app-parity.md
2026-10-13 01:01:57 +02:00
Fabio Lissi 3204231d7d Load and save Photoshop Actions (.atn) (#4356, #219) (#4434)
* Load and save Photoshop Actions (.atn) (#219)

- photocraft-psd: `atn` parser and writer for version-16 action sets, exact round trip,
  truncation/garbage safe, fuzz target and `atn_dump` example. Descriptor fix: `indx` and
  `Idnt` reference items carry a class before the number (real action files failed to parse).
- photocraft-engine: `atn_cmds` maps 92 Photoshop events to commands and back (filters,
  adjustments per channel, image/canvas/crop/trim/rotate/flip/mode, make/set/select/delete/
  hide/show/move/duplicate, fill/stroke, selection, clipboard, purge, profiles, play, stop).
  Other steps load as `actions.photoshopStep` (descriptor kept, written back by save, fails at
  playback by name); tool selections load switched off. Commands `actions.load`,
  `actions.save`, `actions.stopMessage`, `actions.photoshopStep`. `Action` gains `dialogs`
  and `set`. `layer` params accept a layer name, so loaded steps replay in any document.
- UI: Actions panel menu › Load Actions… / Save Actions…; Photoshop steps labelled.
- CLI: `batch --actions set.atn`. Automation policy covers the new path params.
- Docs: file-format parity (.atn R/W), gaps G11, target-app parity, scorecard AUTO-219-2
  partial, development guide.

* Translate the Actions panel's Load/Save strings in every catalog

The eight new panel strings (Load/Save Actions…, the loaded/saved counts, the kept-step label)
were missing from all 19 catalogs; the languages that were complete are back at 100%.

* Show the Actions entries in the default dock's panel menu

The legacy dock renders its own group menu and only knew the Layers and Swatches entries, so
Load Actions… and Save Actions… were missing there.
2026-10-12 18:39:57 -04:00
David Mann 018213b8ea Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	docs/scorecard.md
#	docs/ui-parity.md
2026-10-13 00:29:04 +02:00
David Mann 8c7a2de4a7 Merge main into feat/tool-213-7-live-retouch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 00:04:59 +02:00
Diego Chagas de22bca760 Remove tool: Create a new layer, and Remove after each stroke off (#1750) (#3808)
paint.remove takes several strokes at once (`strokes`) and fills them as one job and one undo
step, so the fill sees every hole together; and `newLayer` writes the fill to a new layer above
the target, only where the hole is, leaving the target as it was (with Sample All Layers it
fills from the composite). The options bar shows Photoshop's three options; with Remove after
each stroke off, strokes collect as a tint on the canvas until Enter or the ✓ applies them and
Esc or ⊘ drops them. Strokes are dropped when the tool or document changes under them.
2026-10-12 08:51:56 -04:00
David Mann 086a82d547 Pattern Stamp and Background Eraser draw live; Spot Healing shows its heal area (TOOL-213-7, #3754)
Pattern Stamp (aligned through LiveRetouch, Impressionist dab by dab) and
Background Eraser now paint during the drag; the release commits exactly the
pixels the live stroke showed (per-pixel tests at 8 and 16 bits). Spot
Healing keeps healing on release and shows a dark overlay while dragging.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 23:50:35 +02:00
David Mann 1c8199d417 Merge remote-tracking branch 'origin/main' into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:52:18 +02:00
optimalbytee 3360f6e442 Speed up Tilt-Shift with existing per-level halo trimming (#2880)
Co-authored-by: optimalbytee <optimalbytee@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:58:04 -04:00
Roaldo Sulejmani 5025fd2895 Color Sampler tool (#1046) (#2753)
* Color Sampler tool (#1046)

Add the Color Sampler to the Eyedropper (I) flyout: click to pin numbered
sample points to canvas pixels, drag to move, Alt-click (or drag off-canvas)
to remove, Clear All in the options bar, at most 10 per document. Each point
reads one composite pixel (RGB 0-255 and naive device CMYK, same as the
pointer readout) and the Info panel lists them under their number with the
position in the ruler unit; the readouts refresh when the document changes.

Points are per-document engine view state (DocState.color_samplers): they
belong to their document, are discarded on close, and are not saved to
.pcraft or PSD. They are driven by commands shared by the UI, CLI, control
channel and MCP (view.colorSamplers.add/.move/.delete/.clear/.list); the
mutations change no pixels and add no History step.

Points follow the canvas geometry (crop, Canvas Size, Image Size, Image
rotation/flip, rotated crop) through a pending Session.sampler_map that
Session::edit applies before culling the points that land outside the new
canvas; the count removed is reported in the status bar.

- engine: new sampler_cmds.rs (commands + shared readout helpers) and the
  sampler_map wiring in lib.rs, image_cmds.rs, mode_cmds.rs, commands.rs,
  analysis_cmds.rs (compound rolls points back on error).
- ui-egui: Tool::ColorSampler + original sampler.svg icon, toolbar slot,
  Info panel list, options bar, pointer handling, overlay markers, cursor.
- docs/scorecard.md regenerated (TOOL-1649-1 no longer says the tool is
  missing). No menu item, so parity is unchanged (627/627).

* Color Sampler follow-up: tool count, README, i18n, roadmap

The full ui-egui suite (not just the filtered tests) surfaced these gaps:

- state.rs: Tool::ALL.len() assertion 52 -> 53.
- README.md: "52 tools" -> "53 tools" and add Color Sampler to the list,
  so readme_tool_list_matches_tool_all passes.
- i18n: the options-bar hint, "{n} of {max} sample points" and
  "No document open" need entries in every catalog that claims complete
  menus (every_tl_literal_is_translated); added to all 16 (coverage
  1584/1584). The translations were drafted alongside the tool.
- docs/roadmap.md: drop Color Sampler from the missing-tools list and note
  it landed.

* Color Sampler: fit main's new tools and Image Size resample type

Reset Tool covers ColorSampler, the test ViewXform sets aspect, and
Image Size checks Method::None instead of an Option.

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:09 -04:00
Sir Starch d8d8d9f3d2 Tools: Convert Point tool in the Pen flyout (#3204) (#3211)
Convert Point is a tool of its own, built on the existing path.convertPoint
edit that Pen + Alt already drives. A click makes a smooth anchor a corner, a
drag from an anchor pulls out smooth handles, a drag on a handle moves it
alone. The tool selects the anchor it converts so its handles show; a press on
empty canvas or a segment does nothing, and Cmd/Ctrl is Direct Selection, as
with the Pen. It shares the Pen's key (P).

Original glyph with an ATTRIBUTION.md row; tool name in all catalogs; README
tool list and the G6 / ui-parity / roadmap counts updated (tools 56 of 68, the
earlier 53 was stale).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:09:38 -04:00
Ayan acce8e0e99 Five exact-output speedups: dense Extrude 160s to 0.39s (#2685)
* Rasterize Extrude blocks and scatter Wind streaks once

* Prepare Spin Blur rotations and fixed-channel sampling

* Share Lens Correction interpolation across color and alpha

* Make wide prepress traps independent of radius

* Add full-operation timing for five imaging algorithms

* Record 24 full-operation benchmarks and exact-output checks

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 10:50:45 -04:00
David Mann 46f046ca4e scorecard: five rows catch up with work already on main
AGENTS.md sends contributors to the scorecard's missing and partial rows
when picking work; these five described code that has since landed.

- TYPE-218-7 missing -> done: Warp Text opens with the layer's warp
  (filter_dialog.rs warp_defaults) and previews live; covered by
  warp_text_dialog_tests.rs and
  warp_text_preview_matches_the_full_size_result_on_a_reduced_proxy.
- TOOL-214-13 partial -> done: Properties shows Density and Feather for
  both pixel and vector masks (mask_props_ui.rs, mask_props_ui_tests.rs).
- BUG-207-5 missing -> done: all twelve retouch tools are tested against
  mask, alpha channel, Channels-panel and Quick Mask targets; Sample All
  Layers and the smudge fringe have their own tests (retouch_cmds/tests.rs).
  Pencil and Mixer Brush targets are still untested, noted in the row.
- UI-217-18 missing -> partial: Alt+wheel zooms around the pointer at
  x1.1 a notch, measured on Photoshop in #1718, not the ~5 % the row asks
  for; the status bar zoom controls fit (#2823); Navigator not checked.
- BUG-208-4 stays partial with a corrected note: marquee add/subtract,
  Lasso add/subtract/intersect and Polygonal Lasso are tested from
  modifiers held at the press; a marquee Shift+Alt intersect test is
  still missing.

docs/gaps.md (G8), docs/target-app-parity.md (Type row) and
docs/ui-parity.md updated to match, with revision-history rows.
docs/scorecard.md regenerated: Tools 26/15/2, Workspace and UI 8/9/6,
Type 2/4/3, Open bugs 30/2/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 16:27:16 +02:00
Valentin Klein 42e4af6416 Tools: Frame tool (#2914) (#2915)
The Frame tool (K, its own toolbar slot after the Crop group) draws a
rectangular or elliptical frame; the options bar picks the shape, Shift
constrains to a square or circle and Alt draws from the centre. A frame
drawn over the selected pixel layer's content takes that layer in;
otherwise it is an empty frame.

Frames are built as Frame from Layers builds them: a group clipped by a
layer mask (rectangular or anti-aliased elliptical). The new engine
command layer.new.frame does it in one Session::edit, so one undo step.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:20:15 -04:00
Valentin Klein 017d1aea1c Tools: Perspective Crop (#1043) (#2864)
New Perspective Crop tool in the Crop flyout (C): drag a box or click four
corners, drag corners independently onto a skewed object, perspective grid
(Show Grid), Enter commits, Esc cancels. W/H/Resolution come from the Crop
tool's fields (Front Image, Clear), or the quad's mean side lengths.

Engine: image.perspectiveCrop {corners, width?, height?, resolution?,
interpolation?} maps the quad onto an upright canvas through the
quad-to-rectangle homography (Heckbert's square-to-quad map, inverted):
every raster layer, mask, alpha channel and the Quick Mask, one undo step.
The Background stays opaque over the background colour; type, shape, fill
and smart object layers are rasterized first; vector geometry follows the
nearest affine map. Non-convex, self-crossing, counter-clockwise or
degenerate quads are refused with an error.

algo: warp_surface_clipped bounds the per-tile warp by the new canvas even
when a layer reaches past the map's horizon; convex_quad, quad_size,
quad_to_rect.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:28:11 -04:00
Valentin Klein 2e0c426692 Tools: Artboard tool (#2858) (#2862)
Photoshop's Artboard tool in the Move flyout (V, ⇧V cycles Move ↔ Artboard):
dragging on an empty spot draws a new board (`layer.new.artboard`), clicking a
board selects it, dragging it moves it with its contents, and the selected
board's edge and corner handles resize it (`layer.artboard.set`,
moveContents: false). Each gesture is one undo step. Options bar: Size preset,
W/H, Make Portrait, Make Landscape, Add New Artboard. Original toolbar glyph
(assets/icons/artboard.svg, ATTRIBUTION.md row).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 07:12:53 -04:00
Valentin Klein 5866cd2cf6 Tools: Horizontal and Vertical Type Mask (#1055) (#2861)
The Type flyout (T, Shift+T cycle) gains Vertical Type Mask and Horizontal
Type Mask, in Photoshop's order. They type like the Type tools, with the
canvas under Quick Mask's colour and the glyphs clear while editing. On
commit the glyph coverage replaces the selection; no layer is left behind
and the whole session is one "Type Mask" history step. Escape always
cancels and leaves the selection unchanged.

The commit runs a new engine command, type.createSelection: a type layer's
glyphs (optionally removing that layer, the tools' hidden working text) or
type.create's keys directly, with new/add/subtract/intersect and an
anti-aliasing override. type.create's layer building is shared through
type_cmds::new_text_layer.

Original dashed-T icons (ATTRIBUTION.md), tool label translations in every
catalog, README tool list and the gaps/parity docs updated.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 07:06:10 -04:00
Valentin Klein c436aa7294 Tools: Freeform Pen and Curvature Pen (#1054) (#2856)
The Pen flyout (P / Shift+P) now holds Pen, Freeform Pen and Curvature Pen.

Freeform Pen: a drag draws freehand; on release the trace is fitted with
cubic Beziers within the options bar's Curve Fit (default 2 px) by
photocraft_vector::trace::fit_trace (Schneider, "An Algorithm for
Automatically Fitting Digitized Curves", Graphics Gems 1990, the fitting
Make Work Path already uses), keeping turns sharper than 60 degrees as
corners. Releasing on the start point closes the path.

Curvature Pen: each click adds a smooth point and the path runs through
every point (Catmull-Rom style handles from
photocraft_vector::edit::curvature_knots); dragging a point moves it,
double-clicking toggles corner/smooth, clicking the first point closes,
Enter/Esc finish and Backspace removes the last point, as with the Pen.

Both commit through the Pen's path: a shape layer in Shape mode, else the
targeted vector mask, else the work path, as one History step.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 06:56:44 -04:00
Fabio Lussi d96bb50161 Add Anchor Point and Delete Anchor Point tools (#1044) (#2756)
Paths could be reshaped after drawing since #865, but the number of
anchors could not change. Two new engine commands do that, and two tools
in the Pen flyout drive them on the canvas.

- photocraft_vector::edit::add_anchor splits the segment at t with de
  Casteljau, so a curve keeps its shape and gets a smooth anchor; a
  straight segment gets a corner on the line. delete_anchor joins the
  neighbours keeping their handles, drops an emptied subpath and refuses
  to remove a path's only anchor.
- path.addAnchor / path.deleteAnchor take the same targets as the other
  point edits (work path, saved path by name, shape layer path, vector
  mask) and record one history step each.
- Tool::AddAnchorPoint / DeleteAnchorPoint join the P group after the Pen.
  They act on Direct Selection's paths, highlight the segment or anchor
  under the pointer, show a +/- cursor badge, swap with Alt and turn into
  Direct Selection with Cmd/Ctrl. An edit clears a Direct Selection on the
  same path so it never points at a shifted anchor.
- Icons derived from Lucide pen-tool (ATTRIBUTION.md), hints and tool
  names in all 16 catalogs, README tool list, roadmap.

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 05:48:07 -04:00
Gert Pellin - GPE 303b7849e8 raw: fit non-DNG colour to the camera's embedded JPEG (#2135)
* raw: fit non-DNG colour to the camera's embedded JPEG

* raw look: clippy (as_chunks) and rustfmt after merging main

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:45:46 -04:00