- The live set found beside Photoshop is offered, never applied unasked: an
"Import keyboard shortcuts?" prompt names the set and how many keys it
changes, with Import / Don't Import. Either answer is recorded in
dialogs.photoshopShortcuts, so it is asked once; ui.inspect shows the
pending offer as kysOffer.
- Parsing and label matching moved to the engine (photocraft_engine::kys), so
edit.keyboardShortcuts {"importKys": <xml>} imports a set from agents, the
CLI and MCP; the app's dialog uses the same plan with its menus' labels.
- "The keyboard shortcut set was not imported: {error}" is translated.
- .kys input over 4 MiB is refused before it is read as text.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Expose native shape stroke controls with staged previews
* Document shape stroke defaults in MCP control schema
* Regenerate docs/scorecard.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: optimalbytee <optimalbytee@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
doc_open, doc_save and the other automation file paths only took
forward-slash relative paths, so an agent passing C:/Users/me/a.png with
a C:/ root was refused. An absolute path is now compared with the
root's absolute path as text: if it lies beneath the root, the root's
components are stripped and the rest goes through the same checks as a
relative path and is opened through the same directory capability.
Nothing is resolved on the filesystem, so traversal, prefixes, device
names and link escapes are refused as before; absolute paths outside
the root, or with no root granted, are still refused.
Fixes#2176.
Photoshop switches to the Move tool while Cmd (Ctrl off the Mac) is held
with every tool but the Hand, Rotate View, Zoom, Crop, Slice, Path Selection,
shape, Pen and Type tools. #896 did this for the selection tools per press; this
does it for the rest (Brush, Eraser, Gradient, Clone Stamp, Eyedropper, ...).
hold_keys gains Temporary::Move (a modifier, not a rebindable key); the
canvas sets PhotocraftApp::tool_override for the frame and the tool state
machine, the Move tool's selection check (move_ui::moves_selected_pixels)
and the tool modules that gate on the tool read active_tool(). The held-tool
latch now lasts through the release frame, otherwise the Move's Up went to
the selected tool. So Cmd-drag with the Brush moves the layer, Cmd+Alt-drag
duplicates it first (move_mods), Cmd-drag with a selection floats the
selected pixels, and Cmd-click picks the layer under the pointer.
Off the Mac, Cmd+Alt is Ctrl+Alt, which the brush tools keep for the
on-canvas resize (brush_resize, also Alt+right-drag there): with a brush
tool Ctrl+Alt-drag resizes, with the other tools (Gradient, Eyedropper...) it
duplicates. Tests pin both layouts on any host. docs/control-protocol.md
describes `command` on ui.pointer events.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Masks: target a new layer mask and keep separate colour pairs for mask and pixels (#2166)
Photoshop keeps one foreground/background pair for layer masks and one for
layer pixels and swaps them when the edit target changes; adding a mask
targets it, with white/black. PhotoCraft never touched the colours when a
mask was targeted, and adding a mask left the pixels targeted.
- engine: ToolState::target_mask swaps in the other target's pair
(mask pair starts at white/black, colours picked on either side are kept).
- ui-egui: sync_mask_targets applies the effective target (mask targeted,
active layer has a mask, composite channel, no Quick Mask) after every
command, every frame and every control request; the Layer Mask add
commands target the new mask.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fix merge: close mask colour-pair test before the deletion tests
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Use shared Color Picker for Layer Style colors (#2015)
* Use shared Color Picker for text style colors (#2015)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Edit › Keyboard Shortcuts: Import Photoshop Shortcuts… loads a .kys set (#1163)
Photoshop saves a shortcut set as .kys XML: <command name="Gaussian Blur...">
rows with one or more <shortcut>Control+Opt+Cmd+G</shortcut>, then the tool
keys. The dialog's new button (and File › Open or a drop of a .kys) parses it
with roxmltree, matches each row to our commands by menu label, and fills the
dialog's overrides, so OK applies the set the way hand-made changes are
(a key moved to a command is taken from its old owner) and Cancel discards it.
Rules, from Photoshop's own ambiguities: a label in several menus (Levels…
under Image › Adjustments and Layer › New Adjustment Layer; All under
Select, Edit › Purge and View › Show) goes to the command whose default is one
of the row's keys, else the one with a default, else the first in menu order.
A row's first key is kept unless one of its keys is already the command's
default (Zoom In is Cmd++ then Cmd+=). Scripts, Bridge, the frames commands
and the macOS application menu have no command here and are counted in the
message. Tool keys are not imported: the tool letters are fixed.
* First launch beside Photoshop imports its live shortcut set; the Open dialog lists .kys
Photoshop keeps the set in use as Keyboard Shortcuts.psp in its per-version
settings folder (macOS ~/Library/Preferences/Adobe Photoshop <v> Settings,
Windows %APPDATA%\Adobe\Adobe Photoshop <v>\Adobe Photoshop <v> Settings),
the same XML a saved .kys holds. The desktop app hands the newest version's
file to the UI (Services::photoshop_shortcuts); at the first launch that has
no shortcut overrides yet, kys_import::auto_import applies it through the
Keyboard Shortcuts dialog's own OK path and posts a notice naming the set and
the count. The preference dialogs.photoshopShortcuts records the source, so
the import happens once; someone who already changed a shortcut keeps theirs.
Reset All to Defaults undoes it.
The Open dialog's filter now lists .kys (it greyed them out), and dropping
Keyboard Shortcuts.psp imports it like a .kys.
* kys import: Ukrainian and Dutch strings; tests follow Cmd+K and Cmd+F as the defaults (#1900)
* kys import: user-facing strings name no other product (#1985)
The button is Import Shortcuts…, its tip names the .kys format, the notice
says "Keyboard shortcuts imported" and names the set, and a file that is not
a set is "not a keyboard shortcut set (.kys)". A set without a name is called
by its file name. Comments, identifiers and the XML root tag keep the format's
own name.
* rustfmt: format panels.rs and prefs_ui.rs
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Fix mask trash targeting and Solid Color Fill editing
* Fix Solid Color picker mode conversion and validate RGB ranges
* Apply rustfmt to Solid Color regression tests
* Fix Solid Color mode regression test using distinct RGB values
---------
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Save in the background so large saves don't freeze the window (#2017)
File › Save and Save As encoded and wrote the whole document on the UI thread,
so a multi-GB layered TIFF or PSB save left the window unresponsive for the
whole write, with no progress.
Saves now run as a background job: a new `Services::save_file` encodes and
writes atomically on a worker (the desktop app provides it), the document is
locked meanwhile, and the existing progress dialog shows the stage with Cancel.
Cancelling while encoding leaves the file untouched. The save is recorded when
the job ends, the unsaved-changes prompt waits for it, and a window close
during a save is held until it ends. Without the service (web, tests) saves
stay synchronous.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Background save: a save can't be cancelled once writing starts (#2072 review)
- `Services::save_file` now gets a `SaveCtl` and calls `commit()` right before
replacing the file. Cancel and Esc are refused after that, so a "Cancelled"
save can no longer replace the file while the document shows unsaved.
- A save's worker stays counted (`saving()`) until it returns, even when its job
was cancelled: quitting waits for it, and another save to the same file is
refused until it is gone.
- A failed or cancelled save no longer closes a window whose close was held.
- The pending reply carries the job id, and the job becomes `last_started`, so
control and MCP clients can wait for the save.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In Photoshop a single click (no drag) with a shape tool opens a creation
dialog to type the exact size. PhotoCraft ignored the click: the drag
geometry was below 1 px, so finish_shape returned without doing anything.
- New shape_dialog module: Create Rectangle (width, height, four corner
radii with a link toggle, From Center), Create Ellipse, Create Triangle
and Create Polygon (sides, star ratio). Width and height show in the
ruler unit and are stored in document pixels; values are remembered per
tool. OK runs the existing shape.create command at the clicked point
(or centred on it) with the options bar's fill and stroke; invalid
sizes/positions are rejected with an error, never a panic.
- canvas::finish_gesture opens the dialog when the gesture never left
the press point by more than 3 screen px; drags draw exactly as before.
Line and Custom Shape keep ignoring clicks.
- vector_ui::create_shape shares the fill/stroke params with the drag path.
- Catalog entries for the new labels and titles in every complete language.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Crop options bar gets Photoshop's gear menu (Set additional Crop options): Show Cropped Area
(H toggles it while the Crop tool shows a box; off hides everything outside the box behind the
canvas colour) and Enable Crop Shield with Color (Match Canvas, the pasteboard colour, or Custom…,
which opens the Color Picker), Opacity (default 75 %) and Auto Adjust Opacity (half the opacity
while the box is dragged). The shield used to be a fixed black at about 51 %. The options persist in
ToolOptions.crop_shield (older settings get Photoshop's defaults; a stray opacity is clamped) and
ui.set takes cropShield. Classic Mode and Auto Center Preview are left out: PhotoCraft always crops
in Classic Mode.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
View zoom now means device pixels per document pixel, so 100% shows one image pixel per physical
display pixel on a scaled (125%, 200%) display, as in Photoshop. Fit, Actual Pixels, pan/zoom anchoring,
scrollbars, the navigator, the pixel grid and every hit tolerance go through canvas_ppp / point_zoom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Crop options bar's overlay button opens Photoshop's overlay menu: six overlays, Auto/Always/Never
show modes, and O / Shift+O to cycle the overlay and its orientation. The default is Rule of Thirds with
Auto Show Overlay, as in Photoshop. ui.set takes cropOverlay, cropOverlayShow and cropOverlayOrientation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#1833. Control and MCP may run Edit Contents, Convert to Layers and Save Contents on smart objects whose source is in memory (embedded, or a Photoshop UUID backed by embedded liFD data). Before any disk read the engine re-checks the source's stored path through the session's authorize gate, including nested sources re-rendered while unpacking, so external linked files stay refused; explicit layer targets work with another layer selected, and interactive linked-file access is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui.inspect lists the open shell dialog (Workspace, Pixel Aspect Ratio, Show Extras,
32-bit Preview) with the id "shell", and ui.dialog.set/confirm/cancel accept it;
confirm goes through the automation authorizer and the dialog's shared OK. Fixes#1004.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui.inspect now has brushPicker: null while closed, {pos, list} while the Brush
Preset picker is open (list is its collapsed/filter/view/renaming state), so
agents can see what a right-click with a painting tool opened. Fixes#1558.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PHOTOCRAFT_AUTOMATION_*_ROOT variables are read only by the desktop app, so
headless photocraft-cli mcp/serve need the --automation-*-root flags. Say so,
and pass explicit roots in the .mcp.json and claude mcp add examples.
Fixes#1551.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui.set gains rotation, gradientBlendMode and gradientClassic in its signature
and the stale duplicate ui.set line goes; ui.menu.invoke documents params,
ui.pointer tool, app.save an optional path, and serve's doc.save tiffLayers.
Fixes#1550.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Preferences entry said ui.dialog.apply/confirm save the dialog, but the
automation policy refuses whole-preferences updates (SECURITY.md). Document the
refusal, what happens to the dialog, and prefs.set for single safe preferences.
Fixes#1555.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Eyedropper options bar now has Photoshop's Sample Size (Point Sample,
3 by 3 ... 101 by 101 Average), Sample (Current Layer, Current & Below,
All Layers, All Layers No Adjustments, Current & Below No Adjustments)
and Show Sampling Ring.
- engine: new `document.sampleColor {x, y, size, sampleLayer}`
(sample_cmds.rs). Averages the square centred on the pixel, clipped to
the canvas, alpha-weighted, in the document's colour model (Gray, Lab;
CMYK documents use their composite RGB, since the profile round trip
is not exact) and rounded to the document's bit depth.
- ui: eyedropper_ui.rs options bar; state.rs ToolOptions
eyedropper_size / eyedropper_sample / eyedropper_ring (serde). The
size is shared with the painting tools' Alt-click eyedropper, the
Curves / Color Picker eyedroppers and the Info panel readout.
- control: ui.set eyedropperSampleSize / eyedropperSample /
eyedropperRing; documented in control-protocol.md and the MCP schema.
- i18n: new strings in all 14 catalogs (plus the missing Russian hint
the tl! scan had not reached).
- scorecard: TOOL-1649-1.
The brush was one global session setting (only its smoothing was per tool), so
switching tools carried the size, hardness, mode, opacity, flow and dynamics
over. sync_tool_smoothing becomes sync_tool_brush: it saves the session brush
for the outgoing tool and loads the new tool's on every switch. UiState's
smoothing_tool/tool_smoothing become brush_tool/tool_brushes (serde default, so
saved UI state still loads), and the session's own fields (colours, erase flag,
stroke seed) are never carried across a swap.
Called from the frame start, the options bar before the brush chip, both canvas
stroke paths, ui.set {tool} (so tool + brushSize in one call sets the new tool's
size) and select_tool_preset. A tool seen for the first time inherits the brush
in hand at Photoshop's 10 % smoothing.
Tests: each_tool_keeps_its_own_brush, the renamed per-tool smoothing test,
per-tool coverage in the ui.set brushSize test, and pencil_tests switching tools
before setting the smoothing as the options bar does.
Headless::save recorded the saved revision only for .pcraft, so saving a
document back to its own PSD or PSB wrote the file correctly and still
left session.list reporting dirty: true. Automation clients that use the
flag could not tell a saved document from unsaved work.
The test is now "was this a layered document save" (PSD, PSB or .pcraft)
rather than "was this .pcraft". A flat export (PNG, JPEG, ...) stays a
copy: it leaves dirty and the document's path alone. The desktop app.save
path already recorded the revision for every format, so this only affects
the headless JSON-lines server and the MCP stdio server, which both route
through Headless::save.
Tests cover the reported sequence for both .psd and .psb -- create, reopen
the saved file, edit, then doc.save with no path -- plus a control that a
flat export is a copy. docs/control-protocol.md now states the outcome of
doc.save for the dirty flag and the document path.
Co-authored-by: Space Bunny Free <chasbigham@gmail.com>
Steps can be deleted and dragged into a new order, live while recording. Playing an action
while recording stores one named actions.play call, and nested playback stops on a child
failure, rejects cycles and goes at most 16 levels deep. Right-click an action to give it
F1-F12 (saved in preferences). View > Fit on Screen (Cmd+0), Zoom In/Out and 100% record and
replay. New actions.move command; actions.delete takes a step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save Preset... in File > New stores width, height, resolution, mode, depth, background (with
its colour) and display units in preferences. They are listed under a Saved category, where a
preset can be selected or deleted. Adds document.presets.save/list/get/delete; get returns
params for file.new, which gains an optional backgroundColor. Bad saved entries are skipped one
at a time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Camera Raw opens in a window of its own
The Camera Raw dialog (Filter › Camera Raw Filter and the open-time
dialog) was a full-window overlay drawn over the PhotoCraft window,
covering its title bar: it looked like a fullscreen app without window
controls. Adobe Camera Raw is a separate window.
Show it in an egui immediate viewport: an OS window titled "Camera Raw
(name)" / "Camera Raw Filter (layer)", 1360 x 900 by default, resizable
(minimum 900 x 600). The dialog stays modal: the main window is dimmed
and takes no input while it is open. Closing the window is Cancel (for
the open-time dialog that closes the raw being opened). Where the
platform has no extra windows (ctx.embed_viewports(), the web build) it
is drawn over the main window as before, with its painted title strip.
The drawing code is unchanged apart from the title strip, which is not
painted in a window of its own.
* Camera Raw window: scrim theme token, close-is-Cancel test, document the screenshot gap
- theme::Tokens gains `scrim` (the dim over the main window behind a modal
that takes all input); Camera Raw uses it instead of a hard-coded colour.
- closing_the_camera_raw_window_cancels: the overlay fallback keeps the
dialog open, a close request on its window cancels without history.
- control-protocol.md: eframe 0.36 cannot screenshot an immediate viewport,
so ui.screenshot shows the dimmed main window while Camera Raw has its own
window; agents read it via ui.inspect.cameraRaw and see it offscreen with
the snapshot example or kittest (which embed viewports).
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>
* type: transform editable text with temporary Ctrl/Cmd handles
* type: cover temporary transforms, editable files and preview costs
* docs: describe temporary Type transforms and automation
* type: release temporary previews immediately when gestures end
* test: benchmark the active 24 MP Type document
* Right-click Brush Preset picker: pick presets like Photoshop (#615, #1031)
One picker for the canvas right-click and the options-bar brush chip, drawn
once a frame and kept in UiState (brush_picker, brush_picker_list). Its
preset list is the Brushes panel's (brushes_tab::preset_list): a click picks
and keeps it open, a double-click picks and closes it, drag reorders and
right-click renames or deletes. A gear menu adds New Brush Preset… (named in
the rename bar), Rename/Delete Brush, list or grid view and Import Brushes….
Hardness shows for round tips only; Enter applies a typed size before it
closes the picker. ui.set gains brushPicker and brushPickerView.
Also fixes the Brushes panel's Rename Brush…/Rename Group… context items,
which never opened the rename bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Brush Preset picker: Polish translations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix Classic Gradient transparency and open its editor
* Translate the minimal Gradient Editor
* Document gradient preset and editor controls
* Group gradient translations to avoid append conflicts
* Split the minimal Gradient Editor out; keep the Classic Gradient transparency fix
The floating editor (preset list + opacity-stop number rows) is not Photoshop's Gradient
Editor, so it goes to a follow-up. What stays: classic drags no longer override the preset
with opaque FG/BG colours, the options-bar swatch shows the selected preset in both modes,
labelled Gradients-panel items, and the classic transparency regression tests. Drops the
ui.set/inspect gradientEditor field and the editor's translations.
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>
* Open camera raws in Camera Raw first (Open / Cancel)
Photoshop opens a raw file in Adobe Camera Raw before it becomes a
document; PhotoCraft developed it with the defaults straight away and
said so in a warning. An interactive open (File › Open, Open Recent,
drag and drop, the command line) of a raw developed from its sensor data
now shows the Camera Raw dialog first, titled "Camera Raw (name)", with
Open and Cancel.
- Open re-develops the raw when Temperature, Tint or Exposure changed:
as-shot white-balance multipliers times the filter's gains, and the
develop exposure (photocraft_io::raw::import_raw_tuned). The other
settings are one filter.cameraRaw step; the document starts
unmodified. Without an as-shot white balance or without the file's
bytes, Temperature / Tint stay RGB adjustments as in the filter.
- Cancel closes the document again.
- The "developed with default settings" note is no longer shown when
the dialog is.
- Preference rawDefaults.openInCameraRaw (default on), the first
visible Camera Raw Defaults setting. Automation opens (app.open, MCP,
CLI) never show the dialog.
- "NIKON CORPORATION" + "NIKON D3200" now reads "NIKON D3200" in the
import note.
Tests: open → set → Open re-develops (brighter, still 16-bit, one
document, unmodified); Cancel closes; unchanged settings add no step;
preference off skips the dialog; a closed document is an error.
Translations for the two new strings in every catalog.
* Camera Raw open: re-develop in the background, keep tab and view
Open re-read and re-developed the whole raw on the UI thread. In the desktop
app it now runs as a job (cameraRaw.redevelop, document locked) like the
open itself; the reply is {job, pending} and the shell finishes when it
lands. The final Camera Raw step goes through app.run (a job too) and the
document is marked unmodified once it lands. Tests and the web still run
inline.
The re-developed document replaces the old one at the same tab position
and path, and its view (zoom, scroll) carries over. New test covers the
background path with documents on both sides.
wb_gains now uses camera_raw::white_balance_gains, shared with the Camera
Raw filter and documented as an observed approximation. 'Developing {name}'
translated in every catalog.
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>
* Add the Magnetic Lasso tool
A selection border that snaps to edges as the pointer moves, in the
Lasso (L) flyout after the Polygonal Lasso.
- photocraft-algo `magnetic`: live-wire edge tracing (Mortensen &
Barrett 1995): Sobel strength, non-maximum suppression and gradient
direction link costs, a shortest path inside a corridor of the
detection width around the pointer's path, sub-pixel edge centres.
Pixels are read lazily in 128 px blocks.
- Engine: `EdgeSource` (the active pixel layer, else the composite) and
`select.magneticLasso`: rough points in, a border along the edges out;
`trace: false` selects a given outline, which is how the tool commits.
- UI: click to start, move to trace (button up or down), points fasten
by themselves (Frequency), click fastens, Alt-click straight, Alt-drag
freehand, Backspace/Delete removes points, click the first point,
double-click or Enter closes, Esc cancels, [ ] change the width.
Options bar: Width, Contrast, Frequency, tablet pressure. `ui.inspect`
reports the border in progress.
- The Lasso and Polygonal Lasso now apply the options bar's Feather;
`select.lasso` rejects a feather outside 0..1000.
- Icon adapted from Noun Project's "Magnetic Lasso Tool" by Lil' Seal
(CC BY 3.0, licence file and ATTRIBUTION.md row).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* it: translate the Magnetic Lasso strings (Italian landed after this PR was written)
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>
* layer style: instance-aware engine apply (layer.layerStyle.replace) with param overlays, contour presets and factory defaults
* layer style: instance-based dialog state preserves every effect; Preview checkbox gates the live preview
* layer style: Styles swatches and New Style… save the dialog pending state (style.presets.new effects param)
* layer style: Contour picker and Noise slider for shadows and glows (CPU-rendered noise; GPU falls back to the CPU for noisy effects)
* layer style: Make Default / Reset to Default per effect kind (user defaults persist with the presets)
* layer style: double-stroke band regression (manga case) and control-protocol docs for the instance-based dialog
* layer style: make Styles a top-level tab displaying preset swatches in the right panel
* i18n: translate the Layer Style preset strings in de and pt-br
The German and Brazilian Portuguese catalogs landed on main after this
branch added "Style {name} saved", "Make Default" and "Reset to
Default"; every_tl_literal_is_translated needs them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* presets: move the stray BTreeMap import to the top of the module
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>
* Add Camera Raw source-resolution preview navigation
* Keep web preview responsive and discard stale refinement errors
* Preserve Alt wheel intent throughout smoothed Camera Raw zoom
* Specify refinement result type for the wasm fallback
* Bound sparse Camera Raw proxy domains and validate detail tests
* Document Camera Raw zoom controls with native screenshots
* Drop the Camera Raw zoom screenshots; keep the guide as docs/camera-raw-zoom.md
We don't commit sizable binary assets. Removes fit.png and actual-size.png,
their LICENSE.txt and ATTRIBUTION row, and moves the controls guide next to
docs/camera-raw-histogram.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate the Camera Raw full-resolution notice in de and pt-br
Main added German and Brazilian Portuguese catalogues after this branch
forked; the i18n coverage tests need the new label in both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Camera Raw: read and write settings as a live PSD Smart Filter (part of #215)
- Map Camera Raw 18.4 (process 5/6) filter settings to and from PSD: custom white
balance, Light, Presence, parametric and four point curves, HSL, Color Grading,
sharpening, noise, grain and numeric post-crop vignette controls.
- Keep the original Photoshop descriptor; edits patch only changed fields. Unknown
settings or versions stay an opaque filter, preserved byte for byte.
- PSD stores integer slider levels: export rounds continuous PhotoCraft values
(hue 360 -> 0) instead of dropping the filter.
- Tests: field mapping, patching, opaque fallback, malformed input, rounding, and
8/16/32-bit PSD -> edit -> .pcraft -> PSD with mask, filter order and undo/redo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Camera Raw PSD: use as_chunks for curve pairs (clippy 1.99)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Camera Raw PSD: no product names in Rust comments, strings or test names
Only the descriptor class key itself keeps its literal name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Actions: actions.record/stop/play/list commands for agents and the CLI (part of #219)
The Actions list moves from UiState to the engine Session, so the panel, photocraft-cli, the control channel and MCP share one list. actions.play checks every nested step with the same authorization a top-level command gets in untrusted sessions.
* Regenerate docs/scorecard.md after rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Type: hitTest, caret and navigate commands so agents can edit text by position (part of #219)
Stateless queries in document pixels. The word, line and hit-test helpers move from type_tool.rs into photocraft-text, and the Type tool calls the same code.
* Regenerate docs/scorecard.md after rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: Color panel chips pick the colour to edit and open the Color Picker on double-click
A click on the foreground or background chip makes the Color panel's field and hue
strip edit that colour; a double-click opens the Color Picker on it, with its RGB,
HSB, Lab, CMYK and hex fields. Each chip remembers its own hue for greys.
Fixes#655
* refactor(ui): keep the Color panel's edited chip in UiState
The chip the Color panel edits is UiState::color_panel.background, so the
control channel and MCP can read it and set it with
ui.set {colorPanel: {background}}. The double-click test now clicks where
the chips overlap, where the foreground chip takes the click.
* Remember desktop window and panel sizes across restarts
* Window layout: discard a saved ui.ron that would crash startup
eframe restores the window geometry and egui memory before the app runs, and a
zoom factor of 0, NaN or 1e30, or a NaN/huge window size aborted every launch
(dpi/wgpu assertions). Check the file first and remove it if it holds such values.
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>
* feat(i18n): add Korean and live language switching
* i18n(ko): Edit Type (#598)
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>
* Edit type layers by double-clicking their thumbnails
* Translate Edit Type for French and Indonesian
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>