fit_canvas only grew the canvas to the boards' right/bottom edges, so a
board moved left of or above the origin (Move tool drag, layer.translate,
layer.artboard.set with negative x/y) stayed partly off-canvas. It now
shifts the whole document (boards, contents, guides, paths...) right/down
by the overhang via image_cmds::translate_doc and grows the canvas, inside
the move's undo step, like Photoshop's artboard documents.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Color Picker: no bigger than Photoshop's (532 x 376 points, was 682 x 437)
A 200-point colour field, 42-point component fields, buttons at their
width rather than stretched, smaller new/current swatches and tighter
spacing. Every control stays; the 16-point window padding stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Color Picker: actions as wide as the longest label, not the column (133 points, was 160)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Layers: clip groups follow New Layer, Arrange, Reorder and Delete (#2203)
Adding, moving or deleting layers never touched Layer::clipped, so a
new layer above a clipping base split its group and became the base of
the layers clipped above it, and moving or deleting a base left its
clipped layers clipping to whatever ended up below them.
The new clip_group module applies Photoshop's rules, measured on #2203:
a layer added or moved joins the clip group it lands in; a clipped
layer moved stays clipped while it still sits on a clip group; the
layers clipped to a base that moves away or is deleted are released;
and a clipped run left with nothing below it is released. They are
applied by layer.new.layer (new clipToPrevious param, Photoshop's Use
Previous Layer to Create Clipping Mask), layer.arrange.*, layer.moveTo
(Layers panel drags, batches and ⌥-drag copies) and layer.delete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layers: the New Layer dialog (Layer › New › Layer…, ⇧⌘N)
Layer › New › Layer… made a layer straight away. It now opens
Photoshop's New Layer dialog: Name (focused and selected), Use Previous
Layer to Create Clipping Mask, Color, Mode, Opacity, and Fill with
<mode>-neutral color, greyed out with "(No neutral color exists for
<mode> mode.)" for modes that have none. OK makes the layer in one
history step.
layer.new.layer gains mode, opacity, color and fillNeutral params
(new engine module new_layer.rs) beside clipToPrevious. The neutral
colors (white for the darkening modes and Divide, black for the
lightening and difference modes, 50% gray for the light modes) are
checked by compositing at 8, 16 and 32 bits.
As in Photoshop, the Layers panel's New Layer button still makes a
layer at once and ⌥-click opens the dialog; commands with params
(automation, Actions, MCP) never show it.
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>
While editing type, a run of clicks at one spot now selects like Photoshop:
two clicks the word, three the laid-out line (a column in vertical type),
four the paragraph up to its hard return, five all text; the next quick
click starts over with a caret. egui stops counting at three, so the Type
tool keeps its own count per layer (next_click_count) and the ranges come
from a pure helper over the text and its layout (click_selection).
Scorecard TYPE-218-8 is done; docs/gaps.md G8 updated.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Actions: a command that runs other commands is recorded once (#3067)
A journaled command that called Session::execute journaled the inner
calls as well as its own entry, so a recorded action replayed them twice
(Straighten Layer: rotate + crop, then Straighten again). dispatch now
remembers the journal length when a command starts; when a replayable
command succeeds its entry replaces the entries of the commands it ran.
Edit > Transform > Again is not replayable and keeps its concrete transform.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Actions: Again on a Copy records its steps; taken-back steps are dropped (#3067)
- edit.transform.againCopy is not replayable, like Transform Again (#1440):
it records layer.duplicate + edit.transform, which play in a session
that never ran a transform. With one layer selected the duplicate names
no layer id, so it plays on any document.
- compound, Again on a Copy and Frame from Layers take back what they ran
when they fail; they now also drop those commands from the journal, so
recording does not replay an undone edit.
- Recording pauses while a replayable command runs: a script that plays an
action no longer flushes its earlier steps into the recording and loses
its own entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
Every existing save path (layered TIFF, print PDF, ACO/ASE, .shc, Smart
Filters in PSD, atomic-save failure) already has a save-and-read-back
test; SVG and Photoshop PDF have no writer yet (FILE-215-10).
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Enable Layers context-menu Quick Export as PNG for a multi-layer selection. Capture each selected source before showing one destination prompt and write separate numbered PNG siblings with sanitized layer names, without silently replacing existing sibling files.
Add actual PNG encoding and cancellation regressions, preserve the captured selection when the dialog is open, and update context-menu enablement tests.
Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Tools: Horizontal and Vertical Type Mask (#1055)
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>
* Type Mask: Shift/Alt at commit combine with the selection; no Layers row for the working text (#3036)
Committing a Type Mask with Shift held (click outside, Cmd+Shift+Enter or
the check button) adds the glyphs to the selection, Alt subtracts them and
Shift+Alt intersects, as in Photoshop; without modifiers it still replaces.
The Layers panel no longer lists the hidden working text layer while a
Type Mask session is open (layer_tree_ui::panel_rows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adding, moving or deleting layers never touched Layer::clipped, so a
new layer above a clipping base split its group and became the base of
the layers clipped above it, and moving or deleting a base left its
clipped layers clipping to whatever ended up below them.
The new clip_group module applies Photoshop's rules, measured on #2203:
a layer added or moved joins the clip group it lands in; a clipped
layer moved stays clipped while it still sits on a clip group; the
layers clipped to a base that moves away or is deleted are released;
and a clipped run left with nothing below it is released. They are
applied by layer.new.layer (new clipToPrevious param, Photoshop's Use
Previous Layer to Create Clipping Mask), layer.arrange.*, layer.moveTo
(Layers panel drags, batches and ⌥-drag copies) and layer.delete.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Remove the 320-point list height cap and nested dock scrolling. Reserve space for the footer while filling the available panel height.
Add a panel-height regression test and check recording autoscroll against the visible footer instead of a fixed coordinate.
Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
* Tools: Artboard tool (#2858)
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>
* Artboard tool: '+' buttons add a board beside the selected one; ⇧ keeps proportions (#3033)
With the Artboard tool, the selected board shows a "+" button beside each
side. A click adds an empty board of the same size (background and preset
copied) on that side, the 100 px artboard gap away and past any board in
the way; ⌥-click adds a copy with its contents instead. A board left of or
above the canvas origin shifts the whole document right/down and grows the
canvas, so it stays visible. Both go through the new engine command
`layer.artboard.addBeside` {layer?, side, duplicate} (one undo step).
⇧ while dragging a corner handle keeps the board's aspect ratio: the
opposite corner stays and the board scales by the larger axis factor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): translate dynamic labels the literal scanner can't see
The Czech catalog covered every enforced key (menus, tl! literals, blend
modes, preferences) but not the labels built at run time: filter and
Filter Gallery parameter names and choices humanized from their keys,
Camera Raw controls, the Flame / Picture Frame / Tree render filters,
measurement columns, file info fields and the macOS application menu.
Those showed in English under Czech.
Adds 853 entries (2,441 -> 3,294) for keys that the other catalogs
already translate and that still exist in the code. Existing entries are
unchanged. Translated clean-room from the plain meaning of the English
labels, with the terminology already used in cs.tsv; no proprietary
localisation was consulted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): credit the Czech catalog in ATTRIBUTION.md and LICENSE-translations.txt
cs.tsv is a bundled translation asset but had no row in ATTRIBUTION.md
and was not named in LICENSE-translations.txt. Add both, worded like the
other catalogs (PhotoCraft contributors, original translations, MIT OR
Apache-2.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): terminology review fixes for the new dynamic labels
Changes from an independent review pass over the added block:
- "Create By" (De-Interlace: how the missing lines are made) reads
"Vytvořit pomocí", not "Vytvořil" (author)
- luminance is "jas" in Camera Raw, as in the existing "Luminance" label
("Detail jasu", "HSL – jas", "Jas")
- slider labels are nouns, like the existing "Redukce šumu" (Reduce Color
Noise, Sharpen Details, Lighten Grout)
- grain and flame choices agree in gender with their neighbours; "Shadow"
matches "Highlight"/"Světla" as "Stíny"; "At" is "Pozice"; "Water Paper"
is "Mokrý papír"; "Leaves" is "listů" everywhere
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* contributors: add my own people.toml entry
Own entry only, as AGENTS.md describes: credited by the name on my public
GitHub profile (sync_github_name), otherwise by @username.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): drop three rows that main added in the meantime
"Columns", "Margin" and "Automatic" were added to cs.tsv on main while this
branch was open, with the same translations, so the appended block no longer
needs them (the catalog has no duplicate keys).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n (cs): translate two strings that are still missing from cs.tsv
"Drag (lasso) or click points (polygonal) ..." and "The current gradient
(pick one in Window > Gradients)" exist in the other catalogs but not yet
in cs.tsv on main.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* i18n (cs): translate context-menu, channel and layer-style labels
These labels are rendered through tl!() with a variable, so the literal-based coverage test does not see them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Localize Czech status messages and notice wrappers
Translate shell messages before interpolating file names and engine errors.
Use the existing plural API for counted objects and preserve English fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Localize Czech UI labels and hardware information
Translate brush controls, accessibility names and rasterization prompts at
render sites. Preserve adapter, driver and display/profile identifiers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Localize Czech composed labels and distinguish Navigator zoom
Translate profile, preset and shortcut messages before interpolation, and use
a Navigator context for the zoom level. Keep shortcut warning state separate
from translated text and preserve document names and shortcut codes.
Cover remaining visible counts and add rendering/glyph regression tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix Czech UI localization review follow-ups
Keep copied System Info and API diagnostics in English while localizing the
on-screen text. Avoid double translation of accessible names, honor command-id
shortcut labels, and translate stored auto backend values only for display.
Refine Czech warning punctuation and catalog wording, simplify template blocks,
and correct the documented catalog count to 3,522 rows. Add four regression tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* people.toml: keep axlecoffee's sync_github_name with their entry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Paths: Stroke Path with any brush-based tool and Simulate Pressure (#2978)
path.stroke now takes every brush-based tool of Photoshop's Stroke Path
dialog: besides the Brush, Pencil and Eraser it renders itself, the
Background Eraser, Clone and Pattern Stamp, Healing and Spot Healing
Brush, History Brush, Blur, Sharpen, Smudge, Dodge, Burn, Sponge, Color
Replacement and Mixer Brush run their paint.* command along each
subpath, in one history step, with only path.stroke journalled.
simulatePressure resamples each subpath and ramps the pressure from 0 at
its start to full at the middle and back to 0 at its end, so a brush
whose size follows pen pressure tapers at both ends.
The Stroke Path dialog (Pen/Path Selection right-click menu, the Paths
panel row menu, Alt-click on the panel's Stroke button) asks for the
tool and Simulate Pressure, as in Photoshop. Scorecard TOOL-214-11 done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Automation: classify path.stroke's params as a document vector path (#2978)
The new Stroke Path params doc says the tools run "along the path", which
registry_filesystem_path_params_are_classified reads as a possible host
file path. path.stroke strokes a document path and forwards only to
paint.* commands that take no files, so it joins the vector-path list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Leaving the Crop tool kept a drawn frame pending, so picking another tool
from the toolbar or by its key left the image uncropped. As in Photoshop,
the per-frame crop sync now commits a drawn frame (as Enter would) once the
current tool is no longer Crop; the untouched default frame is still just
dropped. The pen's eraser end and a gesture in progress keep the frame
pending; held tools (Space, Cmd) never change the current tool.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With no font installed at all (a bare CI image, a minimal container) usvg drops every <text>
while parsing, so nothing reaches the conversion and the file opened as "nothing to draw". The
parser now notes when a drawing has text and the font database has no faces, and the import
warns that the text needs a font and was dropped. Fixes the FreeBSD svg test, which has been red
since the reader landed.
edit.installedProfiles lists ICC profiles from the OS and user profile
directories, so automation must not run it, the same as the other
profile commands that read ambient paths. Without a policy the registry
check in workspace::tests fails, which has kept CI red on main since the
command landed.
* Keep the Color panel selected when switching themes (#3371)
* Scope Camera Raw color checks to its settings panel (#3371)
---------
Co-authored-by: prekabreki <prekabreki@users.noreply.github.com>
Creating an adjustment or fill layer still targets its mask (#2166), so
painting and Invert edit the mask, but Properties now shows the layer's
own settings, as in Photoshop. A new UiState.properties_layer remembers
the layer; clicking its mask thumbnail, targeting the pixels or selecting
another layer clears it and Properties shows the mask controls again.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
select.rect takes an optional `line` ("row"|"column"); the canvas sends it
for the Single Row / Single Column Marquee, so History records
"Single Row Marquee" / "Single Column Marquee" instead of
"Rectangular Marquee". An unknown `line` is an error.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix Camera Raw preview color management
* Apply canvas color management to refined previews
* Camera Raw detail preview: clippy and rustfmt
allow too_many_arguments on DetailPreview::paint (the display joins seven
existing params) and as_chunks for the RGBA8 rows (clippy 1.99).
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>
* fix(crop): unlock Background when extending canvas (#3081)
* Crop: fill an extended Background with the background colour
Photoshop's Crop tool keeps the Background when the frame extends past
the canvas and fills the added area with the background colour, as an
opaque Canvas Size extension does. Do that instead of converting the
Background to a normal layer; other layers still get transparent pixels.
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>
* Transform: Distort, Perspective and Warp keep shape layers live (#3009)
Edit › Transform › Distort / Perspective and Warp on a shape layer let the
user edit the box or mesh and then refused at commit ("needs a rasterized
shape"). Photoshop maps the shape's path and keeps it a shape.
- photocraft-vector: new `map` module. `map_points` sends every anchor and
control point through a map (Distort / Perspective: exact for straight
segments, exact end tangents for curves). `map_path` follows a curved map
(Warp), halving each segment until the mapped control points follow the
mapped curve within a tolerance (at most 64 pieces a segment, bounded in
total); straight segments whose image is straight stay straight.
- engine: `transform_layer` and `warp_layer` map a shape's path instead of
refusing; a live rectangle/ellipse/polygon/line becomes a plain path. A
linked vector mask now follows Distort / Perspective / Warp too. A map that
sends a point to infinity is an error, not a corrupt path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Transform Path: Distort, Perspective and Warp apply to paths (#3027)
path.transform accepted only an affine matrix, so the Free Transform
Path commit refused any box pulled out of a parallelogram ("Distort and
Perspective can't be applied to a path"), and Warp was not offered on a
path at all. path.transform now also takes a `quad` (with an optional
`rect` frame, default the path's control bounds) and a warp (`style`,
`mesh` or `warp`, as edit.transform.warp), validated (finite numbers,
magnitude at most 1e6, non-degenerate quad) and mapped with the
vector::map helpers from #3025. A parallelogram quad stays affine, so a
live shape stays live. The UI commit sends the quad or warp instead of
refusing, and Transform Path offers Warp; each is one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Toolbox: bolder Switch Colors arrow with filled heads
* Toolbox: match the reference Switch Colors glyph
A bold diagonal curve from the icon's top-right to its bottom-left with
filled heads, measured from the reference glyph, replacing the quarter
arc. Geometry extracted to swap_arrow.rs with unit tests.
* Toolbox: correct the swap glyph reference and arrow directions
* Fix formatting and Clippy checks for swap glyph
* swap_arrow: drop the test's env-var PNG dump
Tests shouldn't read env vars to write files; the render assertions stay.
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: scan system fonts off the UI thread (#3021)
The first `photocraft_text::shared()` call scans every installed font, and
nothing made that call before the Type tool's options bar drew its font
menu, so the scan ran inside a UI frame (over 60 s on some Macs, #2518).
- photocraft-text: `warm_shared()` starts the scan on a background thread;
`try_shared()` returns the engine without blocking (None while the scan
runs, started there if nobody warmed it; built in place on the web, in
tests, if the thread can't start or the worker died). `shared()` keeps
its blocking behaviour for engine commands.
- The desktop app warms the fonts at start-up.
- Font menus that draw every frame (options bar, Character, Glyphs and the
Character/Paragraph Styles family menus) show a disabled "Loading fonts…"
box and repaint until the scan is done. "Loading fonts…" is in all 16
catalogs.
Complements #2644 (CoreText discovery on macOS): this changes where the
scan runs, not how fonts are discovered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Type: axis sliders wait for the background font scan too
#2996 (landed on main) added axis_sliders, which called the blocking
photocraft_text::shared() every frame; use try_shared() so the Character
section doesn't stall while the scan runs.
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>
Edit › Transform › Distort / Perspective and Warp on a shape layer let the
user edit the box or mesh and then refused at commit ("needs a rasterized
shape"). Photoshop maps the shape's path and keeps it a shape.
- photocraft-vector: new `map` module. `map_points` sends every anchor and
control point through a map (Distort / Perspective: exact for straight
segments, exact end tangents for curves). `map_path` follows a curved map
(Warp), halving each segment until the mapped control points follow the
mapped curve within a tolerance (at most 64 pieces a segment, bounded in
total); straight segments whose image is straight stay straight.
- engine: `transform_layer` and `warp_layer` map a shape's path instead of
refusing; a live rectangle/ellipse/polygon/line becomes a plain path. A
linked vector mask now follows Distort / Perspective / Warp too. A map that
sends a point to infinity is an error, not a corrupt path.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(linux): say so when no file dialog can open (#3354)
File › Open / Save / Export did nothing on a minimal Wayland session
(labwc, Void) and said nothing: rfd loads libdbus-1 for the XDG file
chooser portal and falls back to zenity, and when neither works it only
logs and answers "nothing picked", which the app took for Cancel.
A background probe now looks once for the pieces (libdbus in the linker
cache, the portal's FileChooser interface on the session bus, zenity on
PATH). When a dialog comes back empty and none of them is there, the
shell answers with the new FileDialogAnswer::Unavailable and the status
bar tells the user what to install. The probe only explains an empty
answer; it never blocks a dialog, and anything it can't tell counts as
present.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(linux): read the ldconfig cache on non-English systems
The startup library check and the new file dialog probe parse
`ldconfig -p`, but accepted the listing only when its first line
contained "libs found in cache". On a German system the header is
"5522 Bibliotheken im Cache ... gefunden", so the cache was treated as
unreadable and the check silently fell back to a directory search.
Run ldconfig with LC_ALL=C, and accept any listing that starts with a
count. Test: a translated header is a valid listing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* file_dialog_probe: no dead-code errors off Linux
Findings, problem and on_path are only used by the Linux probe (and the
tests); the empty non-Linux start() was never called. Windows/macOS
clippy -D warnings failed on them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A file with no embedded ICC profile whose EXIF ColorSpace tag says sRGB
(what cameras write) now opens as sRGB, as in Photoshop: it is tagged
sRGB before the Color Settings policy runs, so it is kept, reported as a
mismatch, or converted like any embedded profile. Before, it was read as
untagged and took the working space, so with a wide working space such
as Display P3 its colours were reinterpreted.
Preferences › File Handling › Ignore EXIF Profile Tag (off by default)
restores the untagged reading, and is now shown in the dialog.
photocraft_codecs::exif_color_space reads the tag from the Exif sub-IFD,
bounds-checked; anything malformed reads as no tag. Settings that do
nothing: 43 → 42 of 150.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Smart Radius, Shift Edge, Decontaminate, Output and the Refine Edge
title had no catalog rows, so the Select and Mask dialog stayed partly
English in every language; Greek and Polish also missed Modify's
Apply At Canvas Bounds. A new test checks every generated select.*
dialog (params, choices, title) in all registered languages.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
On Windows, eframe saves a minimized window's size (160 pt wide, raised to
64 pt tall), and the minimum size isn't applied to a restored window, so
PhotoCraft reopened as a 160 x 64 strip on every launch. A window_builder
hook grows a restored window to the minimum size, keeping its centre.
Co-authored-by: Katt <238968166+KattKnight@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Installed ICC profiles in the profile menus, and Mirror in Print
- photocraft_engine::installed_profiles: scans the OS colour profile
folders (Windows, macOS, Linux), bounded and cached; new
edit.installedProfiles command.
- Print's Printer Profile and the Proof Setup, Convert to Profile and
Assign Profile dialogs list the installed profiles after the
built-ins; a profile given by path that isn't listed shows by name.
- file.print emulsionDown mirrors the page (PDF and preview), with a
Functions › Emulsion Down (mirror) checkbox.
- Print dialog: cm/mm/in, Height and Width fields, Print Resolution.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print dialog: translate Emulsion Down, Functions, Print Resolution in every catalog; cargo fmt
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>
The Color panel converted between the foreground and its hue, saturation and
brightness with egui's `Hsva`, which works in linear light. So a 50% grey
(128) read as Brightness 22%, and the colour field's left edge halfway down
was drawn as 188, not 128. Photoshop's HSB, and the Color Picker dialog's,
work on the sRGB-encoded values.
`srgb_hsva` / `hsva_srgb` now use the Color Picker's `rgb_to_hsv` /
`hsv_to_rgb`, and the field, hue strip, HSB slider tracks and swatch draw
through a new `hsva_color32` instead of egui's linear `Color32::from(Hsva)`.
`Hsva` only holds the numbers now.
Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Distort and Perspective are greyed for editable type (#2701) and the engine
refuses them at commit, but the box's ⌘ / ⌘⌥⇧ corner drags didn't look at the
layer, so the box distorted and the commit then failed. On a box over live type
those drags now act as a plain corner drag (⌥ and ⇧ keep their meaning).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>