A pre-scan rejects element nesting past 512 levels before the XML parser recurses
(a 70 KB file of <g> aborted the process). .svgz is inflated by the importer with a
128 MB cap, so it can be scanned too and gzip bombs are refused; flate2 moves from a
dev-dependency to a dependency (already in the tree through usvg).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photoshop's Vertical, Horizontal, Dual Axis and Diagonal paint symmetry presets beside the
path-based symmetry: axes start at the document centre, Transform Symmetry moves and rotates
them (Shift snaps to 15°, Enter applies, Esc cancels), and all copies merge into one coverage
pass so overlaps don't build opacity. New paint.setSymmetry command and preset icons. Fixes#1648.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The document tab's context menu gains Reveal in Finder (a new file.revealInFinder command that reuses the smart-object reveal helper and targets the clicked tab's document), and dragging a tab sideways reorders the open documents through document.move, with a drop line, in both tab strips. Flips UI-217-6 to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every tool's input schema is rewritten once at startup: $refs inlined (use-site
descriptions win), $defs dropped, boolean schemas turned into {}, with a depth cap
for recursive definitions. ui_pointer events get a concrete schema. Strict providers
no longer reject the whole tool list. Fixes#1782.
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>
Outside the crop frame the pointer is Photoshop's curved turn cursor and a drag turns the frame
about its centre (Shift: 15 degree steps) with an angle readout; handles resize along the turned
axes and a double-click inside commits. image.crop takes an optional angle: Image Rotation >
Arbitrary so the frame is upright, then the crop, in one undo step. Fixes#1792.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit > Stroke… from the menus opens a Photoshop-style dialog (Width, Color, Location, Blending:
Mode, Opacity, Preserve Transparency) remembering its last values; called with params it runs
directly. edit.stroke gains mode and preserveTransparency, blending through Fill's compositing
(blend_color_mask), and validates them. The Blending section is shared with Fill
(dialog_blend_ui.rs). Fixes#1463.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
menus::invoke records each successfully run command (deduplicated, newest first, last 8) and the palette lists them when its query is empty, falling back to the full list in a fresh session. Flips UI-217-7 to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unknown keys, wrong types, out-of-range sizes or resolution and unsupported
mode/depth/background now fail naming the parameter, before a floating selection is
committed or a document added; omitted fields keep their defaults. The renderer tests
now pass numeric depths (strings had silently fallen back to 8-bit). Part of #507.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a Pen path is being drawn, Undo (its customisable shortcut or Edit > Undo) and Backspace/
Delete remove the last uncommitted anchor instead of undoing an earlier document step, as in
Photoshop; once the draft is empty, Undo works on the document again. Fixes#1466.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#1810. File > New remembers the size, resolution, mode, depth, background and display units its OK used (in the preferences' dialogs map, so they survive a restart) and starts from them next time, as Photoshop does; a clipboard image still sets the size, and corrupt remembered values are ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host lists fonts in fonts/manifest.txt beside index.html (craft-fonts' manifest format, paths
kept inside the site); the web shell lists those families in the font menus and fetches a family
when it is picked or a layout needs it, then re-renders the affected type layers without a history
step. No manifest changes nothing; package.sh copies one in with PHOTOCRAFT_WEB_FONTS_DIR. The
style menu also lists the weights of a variable font's wght axis.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adjust_surface now takes the layer's tiles and adjusts each one on its own rayon task, so a large layer needs one tile's buffers per thread instead of four full-layer copies. The result is bit-identical to the single pass for every adjustment kind, colour model and depth, with and without a soft selection (tiled_adjustment_matches_one_pass_over_the_content). Adds an invert scenario to large_image_bench.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CPU path drew the pixel grid over the whole view, including transparent areas, while the
GPU shader only lightens or darkens pixels with content. pixel_grid.rs applies the shader's
rule to the composite of the area in view, as merged segments cached per revision and padded
region (capped at 60k segments), sharing the zoom and strength constants with the shader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changed tiles are grouped by shared Arc and converted once each in parallel batches; a tile
shared by several coordinates (a solid fill, a cleared area, a non-zero default) is uploaded
once to a scratch tile and copied on the GPU, and new pages take the same path. 16-bit RGB and
gray tiles convert to RGBA16F through a lookup table. Select All + Fill on a 50 MP 16-bit page
refreshes in 15-19 ms instead of about 1.4 s. Fixes#1774.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in Photoshop CC 2019 and later, dragging an edge handle now scales both axes by the same
factor about the middle of the box (Alt: about the reference point), and Shift stretches the
one axis. Use Legacy Free Transform restores the old behaviour for edges as well as corners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Path Selection or Direct Selection and a path, Edit > Free Transform (and the Transform
items) frame the path's anchors and handles and commit one path.transform step instead of
moving pixels; it works on the Background too. Distort and Perspective, which an affine path
transform can't follow, report an error. Fixes#1770.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applying a comp after ordinary edits now backs up the edited layout instead of keeping
a stale one; cycling or reapplying unedited comps keeps the original backup. The check
runs only on comp application and respects each comp's capture flags. Fixes#919.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session::edit now reports empty damage when the step leaves every layer's pixels and the
document's compositing fields untouched (same_pixels: a bounded walk over the layer tree
comparing tiles by pointer), so a selection change no longer recomposites the whole canvas.
Adds a lasso_bench example. Fixes#1773.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Preferences › Interface › System Title Bar (Windows/Linux) the OS draws its own icon and title,
so the in-app bar drops the brand mark and centred title; the OS window title follows the active
file ("name — PhotoCraft"), sent only when it changes. macOS and the web build are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parse_spec reads only the spec's leading {…} object, so notes and → {…} results after
it no longer become junk fields. Selective Color's per-range arrays stop showing as a
number field, and its OK applies again (it failed since #1790 tightened the params).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
step_damage pairs layers by id and steps over a layer only one side has when it draws nothing
(an empty pixel layer or group in Normal/Pass Through, no masks, effects or Advanced Blending,
not a clipping base), so New Layer, New Group and their undo/redo report no damage. Adds the
P51 perf scenario (new layer in the 150-layer document). Fixes#1771.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filter that runs as a background job no longer flashes the unfiltered image after OK: the dialog's preview is marked committing and the canvas keeps showing it (canvas::committed_filter_preview) until the job lands, fails or is cancelled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in Photoshop, a new adjustment or fill layer with no selection or active path gets
an enabled, linked reveal-all mask (sparse, no canvas-sized allocation), ready to paint,
in the layer's single undo step. Fixes#1768.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#1829. A schema-built filter body sits in a scroll area capped to the window, so Flame's OK and Cancel stay on screen in a small window; a dialog the user drags is remembered per command (or per kind) in egui's persisted memory and reopens there, as Photoshop does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A complete Dutch catalog (nl.tsv) registered with the complete-menu languages (nl-NL and nl-BE
resolve to it) and in the coverage test, with the strings added on main since the PR opened and
Dutch bit-depth labels (8-bits, 16-bits).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit > Fill… with Contents: Pattern shows a swatch of the current pattern; a click opens a grid of every library pattern (current one outlined, names in tooltips and accessibility labels), as in Photoshop, instead of a dropdown of names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preset grid (Brush Preset picker and Brushes tab) wrapped with horizontal_wrapped, which
indented only the first row and left the spare width on the right. Rows are now laid out as
whole columns with the same centred margin (grid_columns, guarded against NaN/zero sizes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New command layer.toggleMaskOverlay, bound to \ as in Photoshop: shows the active layer's
mask as a 50% red rubylith over the composite and hides it again (view state, not a history
step; the mask stays the paint target). Disabled without a layer mask; a focused text field
keeps the key. Fixes#1765.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single click on a tab of a collapsed panel group (or a pick from its overflow menu) now
expands the group and shows that tab, as in Photoshop; double-click still collapses. The tab
strip reports clicks through StripOut/CardResponse. Tested in both themes, with the
workspace locked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export dialogs suggested a bare file name, so the save dialog opened wherever the OS chose.
pick_save now joins a bare suggestion onto the active document's folder (an explicit directory
or an untitled document is left as is), and the desktop dialog starts in the suggestion's folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Layer > Rasterize > Vector Mask multiplied the hard vector mask into the pixel mask, dropping
the vector mask's feather (and the pixel mask's feather or density). When any of those shape
the edge, the pixel mask now becomes exactly the combined mask the compositor showed, with
feather and density baked in. Tested at 8, 16 and 32 bits against the composite. Fixes#992.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a just-placed Smart Object's contents switched documents while the Place transform was
pending, and end_if_left cancelled it, deleting the placed layer from the parent. When the
active document is that layer's linked child, the transform session now just ends and the
layer stays. Fixes#1797.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Double-clicking the Hand tool's toolbar button selects it and runs View > Fit on Screen, as in
Photoshop; a single click only picks the tool, and with no document open the double-click is
harmless. Also credits the author in contributors/people.toml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a selection, the Crop tool's untouched default frame frames the selection's bounds, but
Enter on it did nothing (it was always treated as the no-op whole-canvas frame). It now only
skips the commit when the frame is the whole canvas. Tested at 8 and 16 bits. Fixes#1789.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the view flipped, to_doc returns the ruler's ends reversed, so the tick loop ran zero
times. ruler_range normalises the visible range to (min, max) for both rulers. Fixes#1814.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layer thumbnail drew the checkerboard over the whole square cell while the texture is the
document letterboxed into it, so tall (or wide) documents showed checker bars at the sides.
The checker and image are now drawn only over the document-shaped part (channels_panel::fit_thumb).
Fixes#1762.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bad control token, token plus token file, or a missing automation root printed an
error and exited 0, so a launcher believed the control server was up. Now exits 2,
like a bad --control port; subprocess tests cover the four paths. Fixes#1816.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit > Define Pattern… now opens a Pattern Name dialog, prefilled with the name the pattern would get anyway (pattern_cmds::default_name, now shared with the command so the two can't drift), as Photoshop does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A complete Ukrainian catalog (uk.tsv) registered with the complete-menu languages, with
one/few/many plurals (teens use many), uk-UA locale resolution, and the coverage test entry;
plus the strings added on main since the PR opened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five RAW readers cast the u32 Orientation to u16 before the 1..=8 check, so a LONG
65538 became 2 and rotated the image. A shared tiff::orientation checks the range on
the u32 first. Fixes#1817.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image.imageSize returns the document's size after the command instead of the requested
one, so Resample off (which changes the resolution only) no longer reports a size it
ignored. Tested at 8, 16 and 32 bits. Fixes#1815.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
u8::from_str_radix accepts a leading '+', so "+5" decoded as 0x05; unhex now requires
two ASCII hex digits per pair and fails as corrupt otherwise. Fixes#1818.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open dialogs (File > Open and the single-file pickers for Place, scripts, notes and presets) now carry the most recent file's folder as initial_dir, and the desktop shell hands it to rfd's set_directory. Flips UI-217-3 to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit › Preferences › General takes ⌘K and Edit › Search moves to ⌘F, as in Photoshop. Nothing
else was bound to ⌘F (Filter › Last Filter stays ⌘⌥F); the shortcut-capture tests follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HalftoneShape::from_id returns None for an unlisted id instead of silently falling
back to Round, and the command fails naming the valid shapes (part of #507).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Saturation slider's track runs grey to the hue at full saturation and the Lightness track black, the hue, white; both recolour as the Hue slider moves, as in Photoshop. Flips UI-217-16 to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cfg(test)-only test.panic command drives Session::execute into the catch_unwind
guard in jobs::dispatch; the test asserts an internal-error Err, an untouched document
and history, and a working session afterwards. REL-3 flips to done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Concurrent wgpu instances segfault in libvulkan with NVIDIA and lavapipe both installed; the three
GPU-harness tests in camera_raw_scope now hold a GPU_LOCK, like canvas_16f. Fixes#1855.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two results exactly one 8-bit step apart can land a few f32 ulps past 1/255 after premultiplying
and subtracting (NVIDIA, Hue/Saturation in adjustment_layers); allow 4 epsilon of slack. Fixes#1848.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>