100 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 ebffaa6e42 i18n: drop duplicate Japanese rows; Serbian for the mask-to-selection items (#4793)
#4651 re-added Japanese rows that #3893 and earlier work already had, so
bundled_catalogs_are_consistent failed on duplicates. #4605's mask thumbnail
menu landed without its three Serbian strings.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:04:35 -04:00
Brandon ThomasandClaude Opus 5.5 68c624003a i18n(sr): translate the Soft Dark and Soft Light theme strings (#4727)
The Serbian catalog (#4436) and the Soft themes (#4378) landed together, so
sr.tsv lacked their four strings and the ui-egui i18n completeness tests failed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:50:26 -04:00
Brandon ThomasandClaude Opus 5.5 72b499a28f Tile grid overlay: gate rayon to native so the web build compiles (#4694)
#4217 used rayon's par_iter in ui-egui unconditionally, but rayon is a
native-only dependency there, so cargo xtask wasm failed on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:20:45 -04:00
Brandon ThomasandClaude Opus 5.5 184c226a76 Fix main: wasm size cap, Windows Export As test, filter preview window (scope and a crash), CLI duplicate font warnings (#4147)
* test(ui-egui): Export As artboards test accepts the Windows path separator

The test added in #3909 compared the second file's path with a literal
'/', but the exporter joins the sibling name with the platform separator,
so Windows CI saw '/out\boards-2-Capsule.jpg' and failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): only Filter menu dialogs get the in-dialog preview window

#3877 drew the preview window in every schema dialog with a Preview
checkbox, including Image > Rotation > Arbitrary. Photoshop's Rotate
Canvas and adjustment dialogs have no preview window, and the taller
dialog covered the canvas, which broke
arbitrary_rotation_preview::size_changing_rotation_preview_is_visible_and_preserves_the_panned_center
on main. The window and its reserved height now apply to filter.*
commands only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(web): opt-level z for large cold crates in the wasm release profile

The browser wasm sat at the 24 MiB gate. The document model, file
parsers (psd, format, affinity), serde_json, usvg and naga are off the
per-frame and per-pixel paths; opt-level "z" for them takes the
release wasm from 25,186,233 to 24,829,840 bytes (-356 KB) with no
behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): run prints each missing-font warning once

Since #3630 the headless open and save also report missing fonts, so
`run <file> --out` and `run --cmd ... --out` printed the same warning
two or three times and missing_typeface_is_reported_by_run_info_and_convert
failed on main. Warnings from the open and the save now go through the
same already-reported set as the per-command ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): the filter preview window never uploads a texture past the GPU limit

With low-resolution previews off, the preview window from #3877 uploaded
the whole document as one texture. egui panics on a texture bigger than
max_texture_side, so opening a filter dialog on a large image crashed
(full_res_preview::filter_dialog_previews_at_full_resolution_when_low_res_is_off).
The texture is now reduced to fit; the view maps through UV fractions,
so it still lines up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:48:28 -04:00
Brandon ThomasandClaude Opus 5.5 2db6b7d8f9 Release 0.8.1: rebuild with the fixed shared release workflow
The previous release build failed in craft-common-actions, not in this
repo: riscv64 packaging got --formats deb,rpm,tar (fixed in
craft-common-actions#3), Linux jobs lacked the ALSA headers
(craft-common-actions#5), and Windows signing needed the AZURE_* secrets
listed in the caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:11:02 -04:00
Brandon ThomasandClaude Opus 5.5 3d6050b931 Release workflow: pass the Azure signing secrets so Windows builds are signed (#3794)
Since the release workflow moved to storytold/craft-common-actions, it has
passed only the Apple and WINDOWS_CERTIFICATE secrets. The signing job reads
values from the release environment, but only for names the caller lists, so
the AZURE_* names that Trusted Signing uses came through empty and Windows
builds would ship unsigned. Same fix as storytold/craft-launcher#174.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:08:34 -04:00
Brandon ThomasandClaude Opus 5.5 b564f2ca9c fix(layers): identify the Background by structure, never by its English name (#4036)
Photoshop's Background is the bottom layer of a file without transparency: a pixel layer that is
transparency- and position-locked, whatever the file's language calls it ("Hintergrund", "背景",
"Arrière-plan"). We matched the name "Background" in compose (deep / shallow knockout), the
engine (cut/clear, merge visible, transforms, warps, distort, erasers, styles, masks, crops,
straighten, smart-object unpack) and the UI (Properties, layer row, tab title), so a localized
file's deep knockout revealed transparency instead of the Background.

- `Document::background` / `background_mut` / `is_background` and `Layer::has_background_traits`
  in the doc model replace every name check; `extra_cmds::background_to_layer` is the one
  "Background → Layer 0" conversion.
- PSD import marks the bottom record position-locked when it is transparency-protected and has
  no transparency channel, with or without an `lspf` block. Affinity import and HDR Toning now
  lock the Background's position like every other place that creates one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:04:33 -04:00
Brandon ThomasandClaude Opus 5.5 6547ca0b84 fix(psd): Multichannel alpha channels stay alpha; a save keeps resources and global blocks (#4031)
- DisplayInfo kinds 0/1 (selected / masked areas) now import as alpha channels instead of black
  spot inks; a Multichannel composite still prints them as black ink, so the look is unchanged.
- Saving a Multichannel PSD writes global light, grid and guides, saved / work / clipping paths,
  EXIF and every other imported resource (print settings, IPTC, layer-id seed, alpha ids while
  they match the channels), except the ones that only describe layers, plus the document-level
  tagged blocks (4-byte padded). Path writing and global-block padding are shared with the
  layered writer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:02:10 -04:00
Brandon ThomasandClaude Opus 5.5 27f56eb762 feat(compose): Lab documents evaluate Invert, Posterize, Threshold, Brightness/Contrast and Gradient Map on L, a, b (#4038)
In Lab documents Photoshop evaluates these adjustments on the Lab channels,
not on the screen RGB colour:

- Invert and Posterize act on L, a and b alike (normalised L/100,
  (a+128)/255, (b+128)/255; Invert is n -> 1 - n).
- Brightness/Contrast (modern and legacy) applies its tone curve to L only;
  a and b are unchanged.
- Threshold splits on L alone and outputs neutral black or white.
- Gradient Map is driven by L (Reverse uses 1 - L).

Adjustment layers (CPU compositor) and destructive Image > Adjustments both
go through the new `adjust::apply_in`; the GPU compositor defers these
adjustments to the CPU in Lab documents. Levels and Curves keep their Lab
records; other adjustments stay on the screen colour.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:20:44 -04:00
Brandon ThomasandClaude Opus 5.5 0d6e658e73 PSD: rasterized fill and smart layers stay rasterized after save and reopen (#4034)
* fix(psd): rasterized fill and smart layers stay rasterized after save and reopen

Layer > Rasterize > Fill Content / Smart Object swapped the layer's content
to pixels but kept the fill (SoCo/GdFl/PtFl) or placed-layer (SoLd/PlLd/SoLE)
blocks read from the PSD. Written back, they made the reopened file a fill
layer or smart object again and discarded the pixels. Photoshop writes none
of them for a pixel layer.

- Rasterize drops those blocks from the layer.
- The PSD exporter never writes fill, placed, type (TySh), shape-content
  (vscg/vstk), adjustment or CgEd blocks for a raster layer, whichever
  command made it one; a vector mask (vmsk/vsms with its vogk) stays.

Tests: synthetic fill + smart object rasterize/save/reopen, the exporter
backstop with stale blocks, and Photoshop-authored corpus files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(psd): keep a pixel layer's vstk when it has no vector mask, as Photoshop does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:11:33 -04:00
Brandon ThomasandClaude Opus 5.5 6f1c4481bf fix(psd): type layer bytes match Photoshop (TySh bounds, AntiAlias, version info) (#4041)
- TySh: the four values after the warp descriptor are big-endian f32, not
  i32. Written as Photoshop writes them: zeros for unwarped type, the
  text-space bounds for warped type (corpus: warp-arc-50.psd, unwarped
  text oracles).
- EngineData AntiAlias numbers as Photoshop writes them, on write and on
  read (when there is no AntA descriptor): 0 None, 1 Crisp, 2 Strong,
  3 Smooth, 4 Sharp (corpus text/antialias-*.psd). Platform modes, which
  no corpus file confirms, write Smooth and keep their exact mode in AntA.
- Resource 1057: writer "Adobe Photoshop", reader "Adobe Photoshop 2026",
  no NUL terminator, byte for byte what all 258 Photoshop 2026 oracles
  write.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:06:46 -04:00
Brandon ThomasandClaude Opus 5.5 32552a61a1 fix(liquify): dab spacing runs on along the whole stroke, so pointer speed no longer changes the warp (#2606) (#4047)
Liquify dabs were spaced a fifth of the brush radius apart and restarted
at every pointer event, so a fast mouse (few far-apart events) and a slow
one (many close ones) warped the same path differently, and a slow drag
piled up one dab per event. Dabs now fall every twentieth of the radius
by distance travelled along the whole stroke, carried from one segment
to the next, as in Photoshop: any sampling of a path gives the same
field. The stationary tools' per-dab strength is scaled to the new
spacing so their effect per distance travelled is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:56:09 -04:00
Brandon ThomasandClaude Opus 5.5 f86517a029 fix(psd): write descriptor ids warp, list and time as length-4 strings (#4039)
A descriptor class or key id is a u32 length and bytes, where length 0
means a 4-byte code follows. Photoshop stores `warp`, `list` and `time`
as length-4 strings and treats the code form as a different key, so the
warps of new smart objects and type layers and regenerated layer comp
lists were not recognised. In the corpus, `warp` appears 741 times as a
string and never as a code, `list` 5/0, `time` 7/0.

`Id::new` now makes these three names string ids, as keys and class ids.
Reading is byte-based and accepts both forms; a code read from a file is
written back as a code.

Tests: exact bytes for each name as class and key, both forms read,
fresh placed-layer warp (SoLd/PlLd) and layer comp list bytes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:50:36 -04:00
Brandon ThomasandClaude Opus 5.5 62346f7fb6 feat(save): File › Save keeps a JPEG's quality and a WebP's lossy/lossless encoding (#2399) (#4032)
Opening a JPEG now estimates the quality it was saved at from its quantisation
tables (the Annex K tables of ITU-T T.81 scaled by the IJG quality curve; exact
for such tables, the closest fit otherwise). Opening a WebP records whether it
is lossless (VP8L) or lossy (VP8), and a lossy one's quality from its base
quantiser index. The settings live on the document (`Document::file_encoding`,
saved in .pcraft) and a save that asks for no quality writes the file back the
same way, as Photoshop's Save does: File › Save, Save As and Save a Copy start
there instead of the fixed codec defaults (quality 90, WebP always lossless).
An explicit quality still wins; new documents and other formats keep the
defaults. Hostile or truncated DQT/VP8 headers give no estimate, never a panic.

Refs #2399

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:45:50 -04:00
Brandon ThomasandClaude Opus 5.5 3d0c819d46 Release prep: 0.8.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:44:37 -04:00
Brandon ThomasandClaude Opus 5.5 dd708f4ce4 feat(psd): Gradient Map stops in CMYK, Lab, Gray and HSB import and round-trip (#4033)
Photoshop writes a Gradient Map's colour stops in the document's own colour
space (CMYK, Lab, Grayscale) or HSB. PhotoCraft only read RGB stops, so such
layers imported as unsupported and did nothing.

- Decode every plain PSD colour space (RGB, HSB, CMYK, Lab, Grayscale) in
  `grdm` stops; CMYK converts through the document's own CMYK profile
  (photocraft-cms), Lab through D50 Lab. Book colours and unknown ids stay
  unsupported (kept verbatim).
- Saving keeps the file's own `grdm` block, so stops keep their colour space;
  toggling Reverse or Dither patches just those flags in place.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:43:27 -04:00
Brandon ThomasandClaude Opus 5.5 7926161595 Release prep 0.7.0: Linux riscv64 tarball + version bump (#2965)
* Release prep 0.7.0: add Linux riscv64 tar.gz to the release; bump to 0.7.0

- release.yml: new linux-riscv64 job, ported from GridCraft: cross-compiled on ubuntu-24.04
  against ports.ubuntu.com riscv64 libs, tar.gz only, photocraft-cli --version smoke-tested
  under QEMU. The draft-release job now needs it, so SHA256SUMS.txt covers it.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support.
- linux_libs: search /usr/lib/riscv64-linux-gnu on riscv64 (was the x86_64 triplet).
- README and docs/releasing.md list the riscv64 tarball.
- Version 0.6.0 -> 0.7.0 (cargo xtask version set), Cargo.lock refreshed; contributors refreshed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* canvas: crisp elliptical marquee ants again in an unrotated view

#2732 (a27d7175) built the elliptical marquee's live ants in document space and snapped every
vertex to the half-pixel grid on its own. Near the edge midpoints the nearly flat arcs became
1 px stairs, whose antialiased steps are grey, so the ants no longer contrast on white or black:
drag_preview_canvas's marquee test fails on main (ubuntu and windows). In an axis-aligned view
(rotation a multiple of 90 degrees) draw the ellipse inscribed in the snapped screen box again,
as before #2732; rotated views keep the mapped ellipse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:36:30 -04:00
Brandon ThomasandClaude Opus 5.5 9c90365c45 ui-egui tests: restore the joined helper (fixes ui-egui test build after #2924 and #2497 both removed one) (#2947)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:46:16 -04:00
Brandon ThomasandClaude Opus 5.5 e1a13013b6 ui-egui tests: drop the shadowing joined closure (fixes clippy dead-code on main) (#2924)
#2782 and #2853 both fixed the save-dialog test's path expectations; together they left a
top-level joined() helper shadowed by an identical local closure, so clippy -D warnings fails
with "function joined is never used". The test now uses the documented helper.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:34:51 -04:00
Brandon ThomasandClaude Opus 5.5 939c8f2c70 Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#2696)
Progress docs per craftrules standards/progress-docs.md: target-app parity (merges parity-estimate.md), gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and UI, brush, format, hardware and localization parity docs. docs/parity.md is now docs/parity-checklist.md.

The Windows test failure (file_dialog::tests::save_dialogs_remember_where_each_document_was_last_saved) is pre-existing on main: the test hard-codes POSIX paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:09:04 -04:00
Brandon ThomasandClaude Opus 5.5 2b79c7c25d automation: test that root look-alike siblings and sub/../.. climbs are refused (#2216 follow-up) (#2528)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:53:35 -04:00
Brandon ThomasandClaude Opus 5.5 29d5f791d5 Regenerate docs/scorecard.md (TOOL-213-19 from #2193) (#2404)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:20:57 -04:00
Brandon ThomasandClaude Opus 5.5 fc42f81f42 Web: leave the JPEG XL DNG decoder out of the wasm (25.1 MB -> 24.3 MB) (#2233)
* Web: leave the JPEG XL DNG decoder out of the wasm (0.88 MB, 23.1 MiB now)

The 0.6.0 web build went over packaging/web/package.sh's 24 MiB gate (26,980,503 bytes on
782bcb67). Today's landings added 1.34 MB; the biggest single item was jxl-oxide (#2047, JPEG
XL-compressed DNG): ~0.6 MB of jxl_* code plus brotli-decompressor and tables, 0.88 MB after
wasm-opt. 0c72d954 brought the wasm back to 25,144,413 bytes (21 KB under the gate) by deflating
catalogs and fonts; this takes it to 24,262,088 bytes (23.14 MiB, 904 KB of headroom).

jxl-oxide is now a native-only dependency of photocraft-raw. On wasm32 a JPEG XL DNG segment
returns RawError::Unsupported ("can't be opened in the browser version of PhotoCraft; open it
in the desktop app"), like the other cfg-gated web features; every other DNG compression and
raw format still opens in the browser. Native builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* xtask i18n-coverage: read the deflated catalog! registry entries

0c72d954 changed the LANGUAGES registry from include_str!("xx.tsv") to catalog!("xx"), so
`cargo xtask i18n-coverage` failed with "language `ja` has no include_str! catalog". Parse the
new form (the macro argument is the catalog's file stem).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:40:35 -04:00
Brandon ThomasandClaude Opus 5.5 0c72d95425 Web: store translation catalogs and bundled fonts deflated, unpacked on first use (wasm back under the 24 MiB gate)
The 0.6.0 web build was 26.9 MB, over packaging/web/package.sh's 24 MiB gate (Cloudflare's per-file
cap is 25 MiB, #198). The i18n .tsv catalogs (1.85 MB -> 0.64 MB) and the Inter/JetBrains Mono fonts
(1.52 MB -> 0.73 MB) are now deflated by build.rs and inflated on first use, so wasm is 25,141,080
bytes. No visible change. A catalog that fails to inflate falls back to English, a font that fails
falls back to egui's default font; neither panics. Headroom is only ~25 KB: the next size cut is
needed soon (fetching catalogs on demand would save another ~0.64 MB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:11:13 -04:00
Brandon ThomasandClaude Opus 5.5 d3fa5e4c6f Bump version to 0.6.0 (from 0.5.0) for release; refresh contributors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:02:23 -04:00
Brandon ThomasandClaude Opus 5.5 69b1379781 prefs_ui: declare recovery_tests after the production code (fixes prefs_usage on main) (#2194)
#2115 put `#[cfg(test)] mod recovery_tests;` at the top of prefs_ui.rs, and the
prefs_usage test reads each file only up to its first test module, so every preference
read in prefs_ui.rs looked unread.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:16:52 -04:00
Brandon ThomasandClaude Opus 5.5 11a2b72455 Prefs audit: Export Clipboard reads as wired again (#2069)
The prefs_usage scan treats everything after a file's first #[cfg(test)] as test code; the channel-tint tests from #1986 sat above export_os_clipboard and hid its read. The test module moves to the end of ui-egui's lib.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:23:11 -04:00
Brandon ThomasandClaude Opus 5.5 9b0c6c4d74 PSD import: a pattern-filled shape opens as a pattern fill layer (#2059)
Since #1917 the exporter writes pattern fill pixels; a vector-masked pattern fill then reopened as a
shape layer covering the canvas, failing the corpus round-trip floor (adjustment-fillers.psd).
Pattern-filled plain shapes now import as pattern fill layers with their vector mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:59:48 -04:00
Brandon ThomasandClaude Opus 5.5 ee3ebfa748 Smart contents test: match the refused path by file name (Windows CI red since #1882) (#2042)
The refusal message Debug-quotes the path, which doubles backslashes on Windows, so
contains(path) never matched there. Matching the unique file name keeps the check.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:32:39 -04:00
Brandon ThomasandClaude Opus 5.5 9b1c916f42 Japanese: command names match the menu terms (#1860)
About 50 command names from #1669 now use the same word as their menu item (ゆがみ, 演算, ぼかし(ガウス) …).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:57:56 -04:00
Brandon Thomas 74112064cc Merge main; de: Photoshop's German eyedropper terms (Aufnahmebereich, 1 Pixel, 3 x 3 Pixel Durchschnitt) 2026-10-09 09:32:53 -04:00
Brandon ThomasandClaude Opus 5.5 2e41caf897 ATTRIBUTION: keep the PngSuite row and one pixls row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:29:58 -04:00
Brandon Thomas 4b7db4088b Merge remote-tracking branch 'origin/main' into pr1636ff 2026-10-09 09:29:34 -04:00
bb3108cfd2 CI: no macOS on PRs or main; macOS is built and tested by releases (#1820)
The org has 5 macOS slots in total (GitHub Team), shared by every repo's CI and
release builds, and PR CI kept them full, so release builds queued behind it.
macOS now runs only in release.yml; a release that fails on macOS is fixed and
rebuilt.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:51:18 -04:00
Brandon ThomasandClaude Opus 5.5 6e558d2fc9 i18n(el): translate the saved New Document preset strings (main red since #1516 + #1562) (#1620)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:57:06 -04:00
Brandon ThomasandClaude Opus 5.5 44d7902f08 i18n(el): translate the screen color picker strings (main red since #1232 + #1562) (#1618)
#1232 added four tl! strings while #1562 was in review, so every_tl_literal_is_translated
failed for el once both landed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:45:50 -04:00
Brandon ThomasandClaude Opus 5.5 0b5803500c Regenerate docs/scorecard.md and docs/parity.md after the latest community merges (#1530)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:24:37 -04:00
Brandon ThomasandTanish Manku f5a5a5c9ee Surface Blur: 4x to 20x faster on 16-bit and 32-bit layers (ranked window sums) (#1529)
The 8-bit sliding histogram (#631) can't serve 16-bit or float layers: a 16-bit threshold spans
thousands of levels and floats have none. The weight `1 - |v - v0| / t` is linear on each side of
the centre value, so the weighted window sum only needs the count, Σv and Σv² of the samples within
the threshold below and above it. A Fenwick tree over the window's distinct values holds those and
slides along the row, so a pixel costs O(radius · log n) instead of O(radius²), for any depth,
with the direct sum's own weight test deciding which samples count. Used from radius 10, where it
overtakes the direct sum; non-finite samples keep the direct sum.

Co-authored-by: Tanish Manku <tmanku.dev@gmail.com>
2026-10-08 21:23:19 -04:00
Brandon ThomasandClaude Opus 5.5 5341bff255 Swatches import/export and the Export As test use the async file dialog (#1528)
#1071 replaced Services::pick_open/pick_save with the async file_dialog
service while #886 (Swatches) and #1394 (Export As scaling test) still used
the old fields, so ui-egui stopped compiling once all three were on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:18:04 -04:00
Brandon ThomasandClaude Opus 5.5 7a9a2684a1 Toolbar test: qualify egui::Vec2 (main fails to compile ui-egui tests) (#1519)
#1376's test used the module-level Vec2 import that #886 dropped; each
compiled alone, but not together.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:49:21 -04:00
Brandon ThomasandClaude Opus 5.5 ed6f989bc6 Regenerate docs/scorecard.md and docs/parity.md after the latest community merges (#1518)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:47:09 -04:00
Brandon ThomasandClaude Opus 5.5 9d3447b153 Regenerate docs/scorecard.md and docs/parity.md after the latest community merges (#1489)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:34:20 -04:00
Brandon ThomasandClaude Opus 5.5 ffe59b4247 Preferences: show Zoom Clicked Point to Center now that the Zoom tool reads it (#1480)
#1241 wired tools.zoomClickedPointToCenter, so prefs_usage's
every_preference_is_read_or_hidden fails while it stays hidden.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:09:47 -04:00
Brandon ThomasandClaude Opus 5.5 fa09fcb218 Regenerate docs/scorecard.md (139 preferences after #1016) (#1435)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:28:28 -04:00
Brandon ThomasandClaude Opus 5.5 6957d7c546 i18n(pl): translate the strings added alongside the Polish catalog (#1426)
The Polish catalog (#1192) landed next to #1016 (System title bar), #1309
(Camera Raw first) and #1293 (document tab overflow), whose new strings it
lacked, so the every-language translation tests fail.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:08:03 -04:00
Brandon ThomasandClaude Opus 5.5 dd555217ac Slices test: as_chunks for the fixed 3-wide rows (clippy chunks_exact_to_as_chunks) (#1374)
Follow-up to #991: Rust 1.99's clippy flags chunks_exact with a constant
size, which turns the workspace clippy gate red.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:06:06 -04:00
bce7e54a63 Release workflow: only signing and draft-release jobs use the release environment (#1220)
version, linux and web read no secrets. With environment: release they were refused on a branch,
so a branch dispatch of release.yml built nothing. Now a dry run builds every unsigned platform;
the signed jobs are still refused and the draft-release job needs them, so a branch can't create
a release. Matches craftrules release/playbook.md §2.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:52:09 -04:00
35f7e99965 README Downloads: where to get it first, then builds with Windows before macOS (#1219)
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:50:10 -04:00
d2a9295050 README: Downloads section listing every release build, grouped by OS (#1210)
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:24:06 -04:00
Brandon ThomasandClaude Opus 5.5 5896f0b63e Preferences: colour settings take a typed #rrggbb value (#668 item 2) (#1001)
* Preferences: colour settings take a typed #rrggbb value (#668)

The canvas colour (and every colour preference) showed its hex value as a
label next to the swatch. It's now an editable field: a valid value sets the
colour, a partial one is kept while typing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:01:43 -04:00
Brandon ThomasandClaude Opus 5.5 4c4c58d6e3 Clone Stamp / Healing Brush: the source crosshair shows only while painting (#668) (#969)
Setting the source with Alt-click left a + on the canvas until the next stroke.
As in Photoshop, the crosshair now shows where the stroke samples only while
the stroke is painted.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:01:38 -04:00
Brandon ThomasandClaude Opus 5.5 3d003bd9d0 Windows: selection and other crosshair tools stay visible over grey (#737) (#954)
Windows' crosshair cursor inverts the pixels under it, so it vanishes over
mid-grey (the pasteboard, many photos). On Windows the canvas now draws a
black-and-white crosshair itself and hides the system one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:01:33 -04:00
Brandon ThomasandClaude Opus 5.5 d64f809e4d Layer via Copy, Copy and Cut read only the selected area: 33x faster on 24 MP (#668) (#985)
lift() converted the whole layer to a format with alpha before reading the
selection's rectangle, so on a 24 MP Background every Layer via Copy cost about
a second whatever the selection's size, and it sampled the selection mask one
pixel at a time. It now reads just that rectangle and the mask's rectangle.

6000x4000 Background, 2000x1500 selection, release: Layer via Copy 984 ms ->
30 ms. Select All then Layer via Copy: 212 ms.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:17:48 -04:00
Brandon ThomasandClaude Opus 5.5 1ae11a4bb1 Shape tools: Shift and Alt pressed during the drag constrain it, as in Photoshop (#668) (#984)
Shape drags only read the modifiers held at the press, so pressing Shift
mid-drag (the usual way) didn't square a rectangle or keep a polygon's
proportions, and Alt mid-drag didn't draw from the centre. They now follow
the modifiers held at each pointer event, like the marquees.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:17:44 -04:00
Brandon ThomasandClaude Opus 5.5 74c7980146 Type: Cmd/Ctrl+T while typing shows or hides the Character panel (#668) (#983)
It passed through to Edit > Free Transform, which started transforming the
layer being typed into. As in Photoshop, it now toggles the Character panel
and editing continues.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:17:39 -04:00
Brandon ThomasandClaude Opus 5.5 dd963b4b44 Crop tool starts with a frame around the canvas (or the selection), as in Photoshop (#668) (#971)
Picking the Crop tool showed nothing until a frame was drawn. It now frames the
whole canvas, or the selection's bounds when there is one, and frames again
after a crop is cancelled or committed. Like Photoshop, a drag inside the
untouched frame draws a new one, its handles resize it, and committing it
does nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:17:34 -04:00
Brandon ThomasandClaude Opus 5.5 997acf5885 Layers panel: drop layers on the footer's New Layer, New Group and Delete buttons (#736) (#965)
As in Photoshop, dragging a layer row onto New Layer duplicates it, onto New
Group groups it and onto Delete deletes it. A row that is part of the
selection carries the whole selection; any other row goes alone. The button
under the pointer is highlighted during the drag.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:17:29 -04:00
Brandon ThomasandClaude Opus 5.5 eec4af6551 Fix main: classify the Direct Selection path commands; Czech for "{n} layer" (#1074)
Two tests on main fail on every OS:
- automation: path.moveAnchors/moveHandle/bendSegment/convertPoint (#865) take
  a document vector path ("name"/"layer"), not a host file, so they join the
  list of vector-path commands the filesystem-param audit skips.
- xtask i18n: the plural "{n} layer|{n} layers" key reached every complete
  catalog but cs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:36:08 -04:00
Brandon ThomasandClaude Opus 5.5 e694208a06 Slice, layer comp and type style ids out of u32 range are rejected, not wrapped onto a real item (#914) (#930)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:13:41 -04:00
Brandon ThomasandClaude Opus 5.5 5bfadbadd0 Save for Web: slices never overwrite each other, and overlapping slices keep their HTML cells (#908, #909) (#929)
Output file names are made unique (case-insensitively; spacer.gif reserved when
writing HTML). In the HTML table a slice shows only where no later slice covers
it; that visible part is cut into rectangles, each its own cell and image, so a
later overlapping slice's link, text and image are no longer dropped.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:12:27 -04:00
Brandon ThomasandClaude Opus 5.5 bdd0141cf3 Filter Gallery: Angled Strokes paints strokes instead of a smear; Rough Pastels' texture shows in the dark areas (#879) (#942)
Angled Strokes was two diagonal box blurs, so it read as motion blur and
Sharpness barely registered. It now repaints in discrete, staggered strokes
(each takes the colour at its middle), light and dark areas in opposite
directions, and Sharpness spans a visible range. Rough Pastels drowned the
image in an even canvas relief; its chalk strokes are now discrete too, and
the texture shows mostly where the image is dark (the chalk is thick in bright
areas), as Photoshop describes it. A test checks every slider of both changes
the result and that both stay tile-independent at every extreme.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:03:09 -04:00
Brandon ThomasandClaude Opus 5.5 e040376e15 Release CI: macOS job on macos-15-xlarge
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:07:53 -04:00
Brandon ThomasandClaude Opus 5.5 d55da47e8d Slice Options and Divide Slice are one undo step, and a rejected one changes nothing (#910, #911, #912) (#928)
An auto slice used to be promoted in its own history step before the command
validated its options, so a rejected slice.set still promoted (and an off-canvas
rect was committed before the error), slice.divide took two undos, and
slice.set folded into any earlier explicit Promote. The promotion now happens
inside the command's own edit, which is all-or-nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:03:55 -04:00
Brandon ThomasandClaude Opus 5.5 34a87cedea actionlint: declare the org macos-release runner label (#927)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:03:51 -04:00
Brandon ThomasandClaude Opus 5.5 1d8a32ed83 Release CI: macOS job on the org macos-release runner
The standard macOS runners are shared with all CI and queue for an hour or
more. The macos-release runner (GitHub-hosted M2 Pro) is in a runner group
usable only by release.yml on the release branch, with a monthly spend cap
on the actions_macos_xl SKU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:54:51 -04:00
Brandon ThomasandClaude Opus 5.5 44303db546 About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#898)
* About: contributors and models tabs compiled in from contributors.json (grab bag + table, sortable, name toggle)

Help > About PhotoCraft gets tabs About · Contributors · Models, following the craftrules
standard (standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables (nothing read at run time; the web build has them too). Each contributor line:
@username, opt-in display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last
commit. Sortable by every column; alphabetical ignores case and the leading @. The tab is the
dialog field `tab`, so agents can switch it with ui.dialog.set. New labels are translated in
all eleven catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors docs: craftrules is two levels up (../../craftrules)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: credits labels for catalogs added on main since this branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate after rebase onto main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:22:11 -04:00
Brandon ThomasandClaude Opus 5.5 5c94c5e2b6 rustfmt crates/engine/tests/hdr_gamma_direction.rs (Format red on main since #801) (#842)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:09:41 -04:00
Brandon ThomasandClaude Opus 5.5 25f211500b i18n(ko): drop the duplicate Ambient and Warmth rows (main red since #811 + #824) (#834)
Both PRs added Korean labels for Relight's options, so ko.tsv listed each twice and
i18n::tests::bundled_catalogs_are_consistent failed on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:02:45 -04:00
Brandon ThomasandClaude Opus 5.5 83ace1138e rustfmt crates/ui-egui/src/canvas.rs (Format red on main since #711) (#819)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 17:17:38 -04:00
Brandon ThomasandClaude Opus 5.5 d7df9d8aa6 Rename PrintCraft to PdfCraft (storytold/pdfcraft) (#766)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:40:36 -04:00
Brandon ThomasandClaude Opus 5.5 7055f0c51b README: remove a stray "ArtCraft" line before Star history (#636)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:12:51 -04:00
Brandon ThomasandClaude Opus 5.5 2cd470c08e monitor_profile test: Unix only (it runs /bin/sleep and /bin/sh); fixes Windows CI after #591 (#633)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:59:59 -04:00
Brandon ThomasandClaude Opus 5.5 da9adf82bc rustfmt discard_ui.rs (CI Format step red since #533) (#597)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 06:55:10 -04:00
Brandon ThomasandClaude Opus 5.5 ba9272be7d Alt+scroll zooms the canvas in 5% steps around the pointer (#293) (#371) (#371)
Merged with #367: the wheel also navigates while hovering the canvas scrollbars.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:22:47 -04:00
Brandon ThomasandClaude Opus 5.5 0940f82d34 Alt+right-drag resizes the brush and changes its hardness (#297) (#370) (#370)
Merged with #360's right-click layer menu: Cmd/Ctrl+right-click lists layers, Alt+right-drag resizes, plain right-click keeps the brush picker / erase.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:22:35 -04:00
Brandon Thomas fbcd0c238e Canvas scrollbars, and Preferences > Tools > Overscroll works (#300) (#367) (#367) 2026-10-06 18:22:23 -04:00
Brandon Thomas 0da7d4b0f5 Right-click on the canvas lists the layers under the pointer (#307) (#360) (#360) 2026-10-06 18:22:11 -04:00
Brandon Thomas 90a46d3f89 Stamp Visible (Cmd+Alt+Shift+E) and Stamp Down; Alt+Merge keeps the originals (#217) (#356) (#356) 2026-10-06 18:21:59 -04:00
Brandon Thomas c161eaec73 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out (#353) (#353) 2026-10-06 18:21:46 -04:00
Brandon Thomas 0349b0c0f1 Harden EXIF orientation: limits after rotation, fallible allocation, strict IFD parsing (#345) (#345) 2026-10-06 18:21:33 -04:00
Brandon ThomasandClaude Opus 5.5 909efc0f6d Background jobs in the app: progress, Cancel, open-as-job (part of #210) (#373)
Re-lands #290, which was merged into its stacked base branch (jobs/engine-210) instead of main.
Same tree as the gated #290 commit 39152db.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:44:40 -04:00
Brandon ThomasandClaude Opus 5.5 3b7870bee1 Background jobs: engine job API, progress and cancel (part of #210) (#289)
* Test: retry the GPU startup sentinel check while a forked child briefly shares the lock

monitor_profile tests spawn osascript in the same binary; between fork and exec the child holds
a copy of the marker file descriptor, so flock reports WouldBlock and begin() returns Busy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Background jobs: engine job API, progress and cancel (part of #210) (#289)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:41:38 -04:00
Brandon Thomas a0d49de047 Menus stay above the taskbar; oversized windows open maximized (#315) (#343) (#343) 2026-10-06 11:30:10 -04:00
Brandon Thomas f8563856a3 Menus: no menu or submenu covers the menu bar (#319) (#337) (#337) 2026-10-06 11:29:57 -04:00
Brandon Thomas dd2f566daa macOS CLI: verify the shipped CLI is Developer ID signed and notarized in the release workflow (#308) (#320) (#320) 2026-10-06 10:48:05 -04:00
Brandon Thomas ae9e1896cf Keyboard Shortcuts: Ctrl+F records as Ctrl+F, not Ctrl+ControlLeft (#292) (#323) (#323) 2026-10-06 10:47:50 -04:00
Brandon Thomas 48e6e668b9 Layers panel: rename commits on click elsewhere, Enter or Tab; one at a time (#314) (#326) (#326) 2026-10-06 10:47:33 -04:00
Brandon Thomas b6e9ec97e5 Cmd+Enter (Ctrl+Enter) loads the path as a selection (#306) (#329) (#329) 2026-10-06 10:47:11 -04:00
Brandon Thomas c046337ddf Move tool: Auto-Select on by default; a click picks and drags the layer under the pointer (#296) (#330) (#330) 2026-10-06 10:46:55 -04:00
Brandon Thomas e8677ff851 Windows on ARM64: release builds, PE header check, install test on ARM64 hardware (#333) (#333) 2026-10-06 10:09:55 -04:00
Brandon Thomas 44bf13e890 PSD export: 32-bit files open in Photoshop (HDR toning Color Mode Data, #291) (#327) (#327) 2026-10-06 09:23:20 -04:00
Brandon Thomas 7ee4bb2858 Apply EXIF orientation on open, write Orientation = 1 on export (#285) (#318) (#318) 2026-10-06 08:20:36 -04:00
Brandon Thomas eaf92dcd9d Smart Objects and Smart Filters survive PSD save and open (#288) (#288) 2026-10-06 07:46:37 -04:00
Brandon Thomas a96a621dee FreeBSD release tarball and README platform lines (#284) (#284) 2026-10-05 20:54:00 -04:00
Brandon Thomas 5ada60c35b Compositor: effect maps never block inside parallel tiles (fixes #276 deadlock) (#281) (#281) 2026-10-05 20:07:16 -04:00
Brandon Thomas 3423864502 Fast Select › Modify › Smooth and Feather (part of #211) (#280) (#280) 2026-10-05 19:59:11 -04:00
Brandon Thomas 7db46219ce Pencil tool, Edit › Fill… dialog and "Rasterize?" prompt (#213) (#279) (#279) 2026-10-05 19:48:03 -04:00
Brandon Thomas c08322b7bc PSD manual and optical kerning: import, export, render, edit (#206) (#259) (#259) 2026-10-05 19:31:53 -04:00
Brandon Thomas 352c849139 Shortcuts: rebindable fill keys, D/X and held temporary tools (#249) (#275) (#275) 2026-10-05 18:50:47 -04:00
Brandon Thomas 1520d1c527 Selection drag previews: regression tests for #253 (visible mid-drag, 1x and 2x) (#274) (#274) 2026-10-05 18:29:06 -04:00