At the history limit a new state can keep the count and the name, so a
selected snapshot stayed selected after it. The newest past state now
belongs to HistorySpot, used by both checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PhotoCraft reads GIMP's .xcf (and .xcf.gz) files, read only, through a new
reader in photocraft-io written from the public XCF specification: versions
0 to 26, uncompressed, RLE and zlib tiles, 8/16/32-bit integer and
16/32/64-bit float precisions (linear precisions get a linear profile),
pixel layers at any position, groups by item path with pass-through,
layer masks applied or disabled, opacity, visibility, locks, colour tags,
the 2.10 and legacy blend modes mapped to Photoshop's (modes without an
equivalent open as Normal with a note), the ICC profile, resolution,
guides, the comment, extra channels and the saved selection; indexed
images open as RGB. Text, vector and link layers open as their pixels;
layer effects, paths and floating selections are reported.
Fixtures are written by GIMP 3.2 itself (scripts/xcf_fixtures.py), each
with GIMP's merged rendering as a PNG. In gamma-encoded blend and
composite spaces our composite matches GIMP within 0.5/255; GIMP's
linear-light default, its Clip to backdrop and its clamp-after-alpha
for unbounded modes are documented and measured in docs/xcf.md.
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
Photoshop's Create Rectangle switches only the right-clicked field
(Width 35.28 mm, Height still 100 px), lists the six length units
without Percent, and opens in pixels again after OK. The PR shared one
unit between both fields, offered Percent, and saved the unit on every
OK, so after one use with the rulers in px the dialog stayed in px even
after the rulers changed to cm, though no unit was ever picked.
Now each field has its own widthUnit / heightUnit (set only by the
menu, never remembered), and the menu is rulers::unit_menu_of with
LENGTH_UNITS. The new test right-clicks the real field and clicks the
menu item through egui input.
Co-authored-by: Korg1ch <57859048+Korg1ch@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel scrolled with `scroll_to_rect(current, Some(Align::BOTTOM))` on every revision
change. In egui an explicit alignment always realigns, and the revision also changes when
nothing in the history does (selecting a layer, a channel eye). So clicking a visible older
state slid it to the bottom under the pointer, Undo hid the greyed redo states, a layer click
threw a user who had scrolled up back to the bottom, and selecting a snapshot scrolled it out
of view.
Now the panel follows the history position (document, past/redo counts, current name, and the
newest past state, which also changes at the state limit), skips it while a snapshot is
selected, and scrolls without alignment, so only a hidden current row moves into view. New
tests: a clicked visible state stays put, Undo keeps the redo state in view, a layer click keeps
the scroll, a selected snapshot stays in view, a new state at the limit is revealed; the first
three fail on the PR head, the others without the snapshot guard / the newest-state key.
Co-authored-by: nrq6993-a11y <342501791+nrq6993-a11y@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The width field reports a change for an arrow-key step that clamps at
0 px. With the stroke now kept at width 0, Down in the options-bar or
Properties width field of a shape without a stroke added an invisible
0 px stroke (bg colour in the options bar, black in Properties), which
enabled Copy Stroke and exported strokeEnabled to PSD. Skip the edit
when the shape has no stroke and the width is still 0.
Test: arrow_down_at_zero_width_keeps_a_strokeless_shape_strokeless.
Co-authored-by: denstu473 <63150090+denstu473@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace whole-region Vec<f32> read_region/write_region in CutParts::new
with tile-parallel cut_piece + try_fill_surface/try_clear_surface,
sharing interior Arc<Tile> allocations in O(1) and activating the
solid_tile hole-fill fast path on Background and mask targets.
- Cache piece_bounds on CutParts so CutParts::place avoids scanning tile
content bounds on every drag frame.
- Parallelize composite_over across top tiles with Rayon and share fully
opaque top tiles by Arc<Tile> clone in O(1), preserving COW tile
identity for GPU canvas uploads.
- Parallelize Surface::translated across target tiles with Rayon and
prune cleared tiles in-place inside edit_tiles.
- Skip zero-offset floating preview generation on pointer-down in
move_ui::floating_doc.
- Exclude SnapKind::Selection when moving selected pixels with the Move
tool so small initial drags do not snap back to (0, 0).
- Keep navigator_plain active during Move/Brush/Patch drags on a GPU
canvas so navigator_gpu is not evicted into a synchronous full-document
CPU thumbnail composite on the first drag frame.
- Parallelize row normalization in compose::render_reduced_in_bands and
add a direct read_rgba_into fast path for unmasked Normal layers over
an empty backdrop.
Main's quiltWarp support (#6913) rewrote the same placed-warp code the
cylinder warp touches. Kept main's split-warp read and write
(warp_head, quiltWarp, sold_warp) and added the cylinder to it:
warp_head writes `warpValues` for a cylinder, placed_frame frames a
cylinder's mesh as well as any valid custom mesh, quad_points uses
perspective_frame of the warp the block stores, and parse_smart_perspective
reads `quiltWarp` before `warp`, as parse_placed_warp does, so a projective
placement is framed by the warp that is imported.
Co-authored-by: alvarohn <32224796+alvarohn@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#7125 keeps existing type's pixel size across Image Size, so the #6491 test's 12 pt layer, made before the change to 144 ppi, became 6 pt. The test now creates its layer at 144 ppi; its assertions are unchanged.