Resolves the append-only conflicts in the ko, zh-Hans and zh-Hant catalogs
(keeps main's Keyboard Shortcuts set rows and this branch's Make Selection
row) and picks up main's CI fix (#5941) so the PR no longer inherits a red main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011cC1iWzaGcNtdsW8LXuKQh
- `edit.transform.warp` / `layer.smartObjects.warp`: `style: "cylinder"`
with its seven `values` (document px; depths in half axes), or a full
`warp` object whose mesh is rebuilt from its values. Anything that
isn't a horizontal cylinder without distortion is refused, never a
silent no-op; on type it is refused like a custom mesh.
- On a smart object the cylinder is stored in source space: still a
cylinder (so it goes to PSD as one) when the placement only scales and
moves, its mesh as a custom warp otherwise.
- docs: file-format-parity (PSD row, history) and gaps G1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuC9R3K5QeMBrDkixa1jnt
A `warpCylinder` placed layer was read as having no warp: it showed
Photoshop's cached pixels on open, but Replace Contents, Relink and Edit
Contents drew the contents flat, and a regenerated SoLd wrote warpNone.
- Import reads the seven `warpValues` (a list of doubles or unit floats)
of a horizontal cylinder without perspective into Warp::cylinder.
Vertical cylinders and ones with perspective, never measured, stay
unreadable as before, as do broken values.
- Like a custom warp's (#3677), the cylinder's control-point box is what
the placement quad frames, for affine corners and, new for this style,
projective ones (`nonAffineTransform`): perspective_frame and
corners_perspective_in. Custom warps keep the source rect there.
- Export writes `warpCylinder` with its values back and frames the same
box, so a moved or replaced layer reopens where it was. An unchanged
layer is still written verbatim.
Tests: synthetic SoLd parsing (both value encodings, the refused
variants), the quad framing the control box (affine and projective),
SoLd and PlLd round trips, and a PSD export/import whose re-render
hides the contents wrapped round the back (fails before the change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuC9R3K5QeMBrDkixa1jnt
Transform Warp's Cylinder (`warpCylinder`) stores seven `warpValues`
instead of a bend. WarpStyle gains Cylinder (parsed, named, not one of
the fifteen bend presets and not listed in the Warp menus yet), and
Warp::cylinder builds its mesh: four patches across (back, front left,
front right, back) and one down, in the warp's value space like a
custom mesh, so map, to_mesh and is_identity treat it as one. Warp keeps
the values for writing them back (serde: omitted when empty).
The model and its table of shape factors were measured against
Photoshop 2026's renders by the reporter of #4112 (not from Adobe's
code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuC9R3K5QeMBrDkixa1jnt
0.8.1's Web job failed: photocraft-web's wasm was 25,587,059 bytes, over
packaging/web/package.sh's 24 MiB gate. #4253 merged with #4147 brings
it to 24,161,781.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combined with #4147's opt-level "z" list. Conflict in Cargo.toml's
wasm-release profile: kept #4147's photocraft-psd = "z" and dropped
#4253's "s" entry for it (one table per package). Measured with trunk
--release: main 184c226a is 25,235,857 bytes (over the 25,165,824 gate);
with this merge, 24,161,781.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS and Windows clippy rejected it as dead code (-D warnings): its only caller in main.rs is
#[cfg(target_os = "linux")]. Also puts read_rendering_prefs' doc line back above it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
- #1599 tests that ui_set's description names every field ui.set accepts; #1611 adds
`rotation` to ui.set, so the description now names it.
- #1611's el.tsv row for "Pick screen color" duplicates the one #1618 added on main.
- docs/scorecard.md regenerated with cargo xtask scorecard after merging both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
Rendering set the thread-local LAB_MIX to the document's value around each
tile, then set it to false. A nested rayon job (a gradient fill, say) can
let the same thread render another tile in the middle of an outer one;
clearing the flag there made the rest of the outer Lab tile mix in sRGB.
The issue's reproduction (a 4096 px Lab document with a gradient fill and
two 50 % fills, 60 renders, RAYON_NUM_THREADS=4) showed whole 256 px tiles
differing between identical renders.
psblend::with_lab_mix sets the flag, runs the closure, and restores the
previous value with a drop guard, as with_cmyk_space does for the CMYK
space. The three set/clear pairs in render_tiled_with use it.
The new test sets the flag, renders Lab and RGB documents on the single-
and multi-tile paths, and checks the flag is what it was. It failed before
the fix. The issue's reproduction, run twice each way on macOS with four
threads: before, 6 and 2 of 60 renders had one whole tile wrong (65536 px);
after, 0 of 60 both times.
The margin Modify pads a canvas-edge selection with was twice the radius
for every operation, so Feather 1000 added 2002 px a side. It now pads by
each operation's reach (Feather 1.5 r, the others r) and refuses a padded
mask over 2^30 pixels instead of aborting on the allocation. The new
dialog option is translated in every shipped language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
1000000 % on any layer could ask the transform for an image millions of
pixels wide, more than can be allocated. The W and H fields now reach as
far as the box stays within 300000 px (the document limit), at most
1000000 %, and a linked change keeps the other side within its limit too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
The Photopea screenshots, their ATTRIBUTION row and the example that made
them documented the bug for review; the unit tests in crates/text cover
the behaviour, so they are not needed in the tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA