A Move-tool click on the pasteboard with Auto-Select only cleared the layer
selection (#4991) when nothing was hit there, but `layers_hit` was not clipped
to the canvas: fill layers and pixels hanging past the edge were hit on the
pasteboard, so the click selected a layer that isn't drawn there instead.
Outside the canvas of a document without artboards, nothing is hit now, and
the layer marquee is clipped to the canvas the same way. Artboard documents
keep picking over the pasteboard, where they draw their layers (#5712).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stream PSD user and real masks into tile rows
* Correct streamed-mask fixture metadata and test lint issues
* Budget PSD pattern expansion and bound multichannel conversion
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* Bound PSD import pixel storage before decoding
* Include ZIP inflater workspace in PSD preflight
* PSD import: budget unknown-mode layers, stop after a fatal layer error, keep best-effort for non-allocation limits (#8593 review)
* Stream PSD user and real masks into tile rows
* Correct streamed-mask fixture metadata and test lint issues
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
The dialog grows to fit its contents, and the preview panes were sized in
fractional points, so they came out a fraction of a pixel wider than the room
given. Every repaint (any pointer move) widened the dialog a little and the
fitted zoom followed. The preview area and pane sizes are now floored to whole
physical pixels.
The new test moves the pointer over the preview at 5 scales x 3 screens x 3
views: 15 of the 45 cases drifted before the fix, none after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Bound PSD import pixel storage before decoding
* Include ZIP inflater workspace in PSD preflight
* PSD import: budget unknown-mode layers, stop after a fatal layer error, keep best-effort for non-allocation limits (#8593 review)
A guide on the same device pixel as a selection edge hid it, at some zoom
levels and not others: the ants were painted in paint_canvas and the guides
after them in paint_primary_overlays. The ants are still traced in
paint_canvas from the frame's snapshot, but returned as shapes and painted
right after the grid and guides; a secondary view paints them where it did.
Free Transform's carried ants come from transform_tool::selection_ants
(was draw_selection), which returns shapes instead of painting.
Test: canvas::guide_ants_tests checks the paint order at three display
scales and nine zoom levels; it fails without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared Swatches / Gradients / Patterns footer drew New Group as
`folder-plus` and New as `plus` at 24 pt, while the Layers footer uses
`folder` and `square-plus` at 26 pt. Both footers now take shared
`icons::FOOTER_*` constants, so they can't drift apart again; a test
checks the glyphs are bundled (a misspelt name draws the fallback square).
The footer grows from 28 to 30 pt, still under the 32 pt first-frame
guess; the #4790 footer-fit test passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- free_group's brush check builds the set of group names once instead of
scanning every preset for each numbered candidate.
- reimporting_a_gradient_file_asks_before_touching_its_group covers the
gradient branch of the prompt: no prompt for a new group, Cancel imports
nothing, Import as New Group adds "Fire (2)", Replace swaps "Fire" alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a group-name clash the status read "Imported 2 brushes from Kit (2)",
naming the destination group as if it were the file. Keep the translated
"… from {file}" message and add the group only when it differs:
"Imported 2 brushes from Kit into “Kit (2)”".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Importing an .abr or .grd whose name matched an existing group silently
deleted that group's user presets (edited or moved-in ones included); the
preset libraries are outside document history, so it could not be undone.
- brush.presets.importAbr / gradient.presets.importGrd: `replace` now
defaults to false; a taken group name imports beside it as "Kit (2)",
"Kit (3)"…, and the result's `group` names the one it landed in.
- UI: when the group already holds the user's presets, a prompt asks
Import as New Group (default) / Replace (can't be undone) / Cancel.
Agents drive it with ui.dialog.set {"choice":"replace"} / confirm / cancel.
- The status bar names the group the file landed in.
Fixes#10640
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layers panel: draw artboard rows like Photoshop, not like folders
An artboard's row looked exactly like a layer group's: the same folder
thumbnail, font and height, so boards read as folders and it was hard to see
where one ended and the next began (#10218).
Artboard rows now have no thumbnail, a bold name, a shorter row (never taller
than a layer's) and a faint band from the theme's hover colour. Plain groups
and layers are unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* Artboard rows: keep mask thumbnails, paint the band under label colours
Review follow-ups:
- A board with a layer or vector mask kept no mask thumbnails, so clicking,
dragging and loading its mask from the row stopped working. It now keeps a
full-height row with its mask thumbnails; only the board's own thumbnail is
left out, and the targeted mask still gets its brackets.
- The band was painted over the eye column's label colour and the dividers,
so a board's label colour looked different from other rows'. It is now
painted first.
Adds a_masked_artboard_row_keeps_its_mask_thumbnail, which failed before.
Co-Authored-By: Claude <noreply@anthropic.com>
* Artboard rows: retarget the board, keep the mask chain off the triangle
Review follow-ups:
- With a board's mask targeted, nothing on the row targeted the board again:
a board has no thumbnail to click. A click on its row, off the masks, now
targets the board.
- On a masked board the mask chain started inside the disclosure triangle's
hit area, so a click there toggled the mask link instead of opening or
closing the board. The row now leaves the gap a layer thumbnail would.
Adds a_masked_artboard_row_keeps_the_triangle_clear_and_retargets_the_board,
which failed before on each point.
Co-Authored-By: Claude <noreply@anthropic.com>
* Artboard rows: compact only for unmasked boards
Review follow-up. Leaving the thumbnail off a masked board kept costing what
the thumbnail does on any masked layer: clicking back from the mask, ending
the mask view, the target brackets and the spacing of the mask's chain, each
patched one at a time.
A board with a mask now keeps its usual row, thumbnail and all, so all of it
works as on any masked layer; only unmasked boards get the compact heading
row. This drops those special cases.
Tests: a masked board keeps its thumbnail and mask thumbnail with the chain
clear of the triangle, and its thumbnail ends the mask view and targets the
board (both failed before).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Artboards share one canvas, so a layer dragged from one board onto another in
the Layers panel (layer.moveTo, including the Alt-drag copy) changed parent but
kept its old canvas position, outside the board it now belonged to, and the
board clipped it away.
apply_move now shifts a layer that lands on a different board by the offset
between the two boards' origins, through move_layers (the Move tool's path).
Reordering within a board, leaving or joining a board from the top level,
position-locked layers and moving a board itself are unchanged. The Layers
panel's drop preview uses apply_move, so it shows the same result.
Locks are read before the drop as well as after, so a layer whose position
was locked only through the board it left stays put (review follow-up).
Co-authored-by: Claude <noreply@anthropic.com>
Its two new commits merge main (8a87a457a, 422ca7bed), both already in the batch.
Their catalog resolutions place the "Edit Brush…" row differently; every row they
hold is already in the batch's catalogs, so the tree is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author's main merge brings one new test (full coverage keeps Threshold over
extreme float channels) and rewords the #9483 tone comment; its main side
(b1cfff9d3) is already in the batch. Docs keep the batch's newer status lines and
revision rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
752daed44 makes nested SVG images share one XML depth budget in the bounded
parser. The batch's fix-up 1acb1f464 already does that (`xml_left` passed down
to each nested image), and also leaves an image the drawing has no depth left
for out instead of failing the drawing, so the tree keeps the batch's svg.rs and
its test `bounded_embedded_svg_gets_only_the_nesting_depth_the_drawing_left` (511 + 511 levels).
866b9d0e4 merged an older main than the batch has; the batch keeps its own,
reviewed resolutions of those files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Channel targeting routes an allow-list of pixel-editing paint commands, not every
`paint.*` id: the Mixer Brush's Alt-click load and Symmetry no longer follow a colour
set or get refused with "Rasterize the layer" on a fill layer. Patch and Content-Aware
Move stay routed (#9274).
- Native vector save-back checks the preview SVG as strictly as stored previews are
checked on load and PSD export, so a preview with an unreadable embedded image is
refused instead of stored and later dropped (#8571).
- .pcraft blobs read their zstd frame with framing headroom, so an incompressible
preview right at its 16 MiB limit still loads; the limit still bounds the data.
- Levels/Curves recall (Alt+shortcut) decides the combined Lab record from the target
the dialog captured, as remembering does, so a viewed layer mask keeps its settings
beside a Lab colour set (#9274).
Each comes with a test that fails without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since main's #3464 a Pen draft carries the owner its first click captured
(document, revision, target path). The test built a draft with no owner,
so finishing it took the target from the current UI state, where path.set
had left the old work path selected, and appended to it: the selection
became 0,0 170x80 instead of the triangle plus the square. The test now
captures the owner as main's Ctrl+Enter test does, with the work path not
selected in the Paths panel (the Pen then starts a new work path).
Co-authored-by: mahefanante <42338306+mahefanante@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#4035's only code change makes Rasterize Layer Style separate its RGB bake
through the document's own CMYK profile, but none of its tests ran that
command (they cover Flatten, Stamp and Merge, which already worked), so
removing the change left every test green.
New test: a tagged CMYK document's layer gets a drop shadow that falls off
the canvas and is rasterized; the inks must come back within the profile's
round trip. Without #4035's change they move by 35 levels (0.137) and the
test fails; with it they pass at 8 and 16 bits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>