Port behavior and regression coverage into current modules; preserve current upstream behavior.
Integrated bounded atomic CSH set parsing, unique shape/group naming and native Shapes panel import with inline-data authorization guards; preserved duplicate-name, inner-hole, malformed-second-record and preferences-reload regressions. Combined independent catalog additions and retained current parity metadata.
The PR read and wrote Halftone Pattern as `Sz ` and `Ptrn` (enum `HlfT`:
`Dt `/`Crc `/`Ln `). The reference application's Filter Gallery uses `HlSz`
for the size and a `ScrT` enum (`ScrD` dot, `ScrC` circle, `ScrL` line), so a
Halftone Pattern saved there still opened as an opaque filter, and one we saved
lost its size and pattern there without a warning. The tests built their fixture
with the same keys as the code, so they passed either way.
Now the keys match, a default Halftone Pattern (HlSz 1, Cntr 5, ScrD) opens as
{size 1, contrast 5, dot} (fails before this commit), and descriptors with the
old keys stay verbatim. The docs no longer claim reopen checks in the reference
application.
Co-authored-by: denstu473 <63150090+denstu473@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with our fix-up branch pr-8885-fixup (8f6863f53) on the author's head 996b55eb4.
Conflicts resolved:
- crates/io/src/tiff_layers.rs: #9148's tiff_source (an empty LMsk gets Photoshop's 14 bytes)
gives way to #8885's tiff_image_source, which writes the same bytes for an empty or short
LMsk; #9148's test stays.
- crates/io/src/flat.rs: #8776's DDS alpha step and #8885's TIFF channel stamp both run.
- crates/io/Cargo.toml, Cargo.lock: #8402's zune dev-deps and #8885's tiff dev-dep.
- crates/io/tests/it/tiff_layers.rs, docs: unions; docs/scorecard.md kept as is, regenerated at
the batch gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Earlier PhotoCraft versions kept the 1053 of a Photoshop file they opened
and wrote it back unchanged, while storing spot channels added there
straight. A transparent Photoshop document carries 1053 = [0], so a white
plate added to it in 0.16.0 or earlier sat behind a 1053, and the rule
read that file in Photoshop's polarity: the added spot opened inverted.
A 1053 that lists fewer channels (non-zero ids; 0 is the transparency's)
than the file has after its transparency now counts as kept, with the
PhotoCraft 1057 and DisplayInfo spot conditions unchanged. Planes the
kept 1053 lists stay in Photoshop's polarity (they are the Photoshop
file's own); the ones after them are read straight. Photoshop and this
version write one id per extra channel, so their files are unaffected.
Known limit, as before: channels reordered there, or as many deleted as
added, can't be told apart from the Photoshop file's own.
The earlier versions are 0.1.0 to 0.16.0, not 0.11.1: every release
since writes the same 1057 and straight spot planes without a 1053.
Co-authored-by: jasallen <615848+jasallen@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psd_import.rs keeps main's warning for an incomplete merged extra channel
next to the PR's Photoshop spot polarity. people.toml takes main's entries
plus jasallen's own. Docs take both sides' status lines and revision rows;
docs/scorecard.md is regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photoshop reads and writes IFF; PhotoCraft opened Amiga IFF/ILBM files
but had no writer. Saving as .iff now writes a FORM ILBM with a BMHD and
a ByteRun1-compressed BODY of 24 bitplanes (red, green and blue, least
significant bit first), one plane row at a time. An image with alpha
also gets a mask plane (masking 1): alpha of 128 or more is opaque, and
when any pixel is not fully opaque the export warns that alpha is
reduced to on/off transparency. Gray is expanded to RGB, 16-bit and
float images are reduced to 8 bits, and images wider or taller than
65535 pixels are refused.
The reader also opens 24-bitplane ILBMs, so a saved file reopens with
the same pixels. A transparent color index in a 24-bitplane file is
refused as unsupported.
IFF leaves ASYMMETRIC_EXCEPTIONS, Save As offers Amiga IFF, and the
codecs README, file-format parity and G10 are updated.
docs/file-format-parity.md: main's PSD row with the round-trip count
258/258. docs/scorecard.md: regenerated with `cargo xtask scorecard`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR fixes the stale Photoshop corpus count (256 -> 258, the pinned
manifest xtask/photoshop-corpus.sha256 lists 258 PSDs) in
docs/development.md and docs/scorecard.md. The same stale number was
left in the io corpus test comment, docs/file-format-parity.md,
docs/roadmap.md ("258/256" round-trip floor) and ATTRIBUTION.md.
Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A layered TIFF carried an empty `LMsk` block in its ImageSourceData
(tag 37724). Photoshop rejects that: it reports a problem reading the
layer data and opens only the composite. Photoshop writes 14 bytes there
itself (overlay colour space 0, colour 65535/0/0/0, opacity 50, kind 128).
The TIFF writer now uses those bytes when the model's global layer mask is
empty. PSD output is unchanged; an empty block is fine there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings split/tiff-channels-io at f53aa0b9 (PR 1 and PR 2 on main
1695f323). Resolutions:
- PsdExportOptions: main's read_linked_files kept; PR 3's
photoshop_spot_polarity switch stays removed, as this branch does.
- crates/io/src/lib.rs: main's import_psd(), calling
psd_to_document_decoded without the removed PsdImportOptions.
- docs: PR 3's and main's text, with this branch's PSD statements
re-applied (dated 2026-10-23). docs/scorecard.md is regenerated next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings split/tiff-extra-samples-codecs at e6fca054, which merged main
1695f323. Conflicts:
- crates/io/src/lib.rs: main moved PSD import into import_psd(); it now
passes PsdImportOptions::default().
- PsdExportOptions (psd_export.rs, tiff_layers.rs): main's new
read_linked_files next to this branch's photoshop_spot_polarity.
- docs: main's newer status lines and rows, with this branch's TIFF
statements re-applied (dated 2026-10-23). docs/scorecard.md is main's
and is regenerated after PR 2 is merged too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves docs/file-format-parity.md: main's newer status line and
revision rows, with this branch's MTrn entry on top (dated 2026-10-23).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main merged #5940, #6472, #7838 and #7922 directly. Kept from the batch:
the snapshot/History-follow identity at the history limit (#5940,
c7ef3676a; main still had the 3-tuple HistorySpot) and the strokeless
shape at a 0 px width step (#6472, 962539607; main still added the
stroke). Main already carries the other two fix-ups (34c9d0ea4, dff8bfd0e).
Overlaps with main resolved:
- #5842 vs main's #5568: one path, apply_color, tool-gated as #5842
has it, with #5568's float fill, kept fill opacity and lock skip;
shape_fill_ui's duplicate foreground_changed/gesture dropped; #5568's
tests run with a shape tool.
- #6230 vs main's #5180: one Type tool path click (path_target), no
second type.attachToPath; vertical type does not start type on a path,
as main has it; main's path_at/path_cursor and canvas arm dropped.
- #5452 moved the shape swatch to shape_paint_ui.rs: main's hex field
(#6201) ported there.
- type_extra_cmds::with_text takes main's dpi form, pub(crate) for
type_area_cmds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- gpu: the test modules share one GPU_LOCK, and timing and vector_mask
take it too, so one process drives one wgpu device at a time.
- ui-egui: the wgpu tests in pattern_preview_canvas, adjust_preview_gpu,
marquee_autoscroll and layout_perf take gpu_lock().
- affinity compound_limits: the child process selects its test by the
module path (compound_limits::<name>); with --exact, the bare name
matches no test and the child passes without running the check.
- xtask scorecard reads crates/io/tests/it/corpus.rs.
- Docs and comments name the new test paths and the --test it filters.
Merges main (8f09d122d). Conflicts: the replay_save doc comment (main added
file.export.exportAs replay) and the status lines and history rows of
docs/file-format-parity.md and docs/gaps.md; both sides kept.
The docs called FILE-215-19 done and said only Save As and Save a Copy ask.
Photoshop's JPEG Options also has Matte, the progressive scan count
(Scans 3-5) and the size preview, none of which is offered, and Baseline
Optimized writes the same file as Baseline for RGB (4:2:0). The row is now
partial with those gaps named, and the docs say File > Save asks too.
docs/scorecard.md is regenerated.
Adds jpeg_options_ui::tests::the_answer_reaches_the_encoder. Every UI test
used a fake exporter, so dropping `opts.encode.jpeg_format` from
ExportSettings::io_options (the desktop and web save path) left them all
green; this test writes a real JPEG through io_options and fails without it.
Co-authored-by: Michael Higgins <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 32-bit branch cut the slider to floor(|s| * 255) / 255, which only matches
Photoshop near 25 and 50 %: measured at every whole percent from -100 to +100
on a 32-bit document, Photoshop lowers by 1 - floor(|s| * 256) / 256 and raises
by 255 / (255 - floor(s * 254)) taken to 1/256 and capped at 32767/256 (201 of
201 exact). +100 gave 512x where Photoshop gives 128x, +90 9.8x for 9.44x.
It also kept the HSL saturation unclipped, which is inf at HSL lightness 1 and
0 and negative above 1: (2, 0, 0), red at +1 stop, turned NaN with any edit,
Hue +40 alone included, where Photoshop gives (2, 1.333, 0). The 32-bit branch
now scales the chroma around the lightness instead (same result in gamut),
on the CPU and in the shader, in whole numbers on both. Colorize clamps its
saturation before the 32-bit scaling, so a PSD's out-of-range value renders
the same on the GPU.
Tests (fail without this): thirty_two_bit_saturation_steps_match_photoshop,
thirty_two_bit_hue_saturation_keeps_bright_colours, the GPU parity
hdr_hue_saturation_bright_colours. Photoshop-oracle floor 188 -> 190 of 258,
measured after merging main.
Co-authored-by: Timothy Daly <100327160+timothydaly1@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolutions: CLI usage, flag lists and flag parsing carry both --tiff-compression (t15) and
--jpeg-format (t16); file.saveACopy, batch, imageProcessor and layersToFiles param specs carry
both tiffCompression and jpegFormat; file_cmds keeps tiff_compression_param and
jpeg_format_param; atn_cmds keeps flag_set/applies_at_canvas_bounds (t15) and UNHOLDABLE (t16),
edit.transform uses the pixel_target guard (t15) with layer_ref(p)? (t16); ui-egui open paths
build ImportOptions (t16) and apply raw_defaults (t15); docs headers/rows unioned; atn tests keep
both sides; tr.tsv "Keep Using CPU" kept once.
crates/io/tests/corpus.rs: kept main's pass_floor 188 (the PR's 187 would lower it; not re-measured here)
docs/file-format-parity.md: kept main's header and PSD/PSB rows; added the PR's change-log row without its floor figure
docs/gaps.md: kept main's header and evidence line; added the PR's change-log row without its floor figure
docs/scorecard.md: ours
crates/io/tests/corpus.rs: kept main's pass_floor 188 (TAKE-FIX: drop the PR's 186 hunk)
docs/file-format-parity.md: kept main's header and PSD/PSB rows; added the PR's change-log row without its floor figure
docs/gaps.md: kept main's header and evidence line; added the PR's change-log row without its floor figure
docs/scorecard.md: ours
Conflicts, both sides kept: main's .act swatches next to the batch's .asl
styles (Open filters, preset extensions), main's Render smart filters next
to the batch's Distort ones (53 mapped), parity doc status lines.
docs/scorecard.md regenerated.
Follow-ups the merge needed: the Distort smart filter readers pass their
enum type to main's stricter enum_name (the type their writers use), and
the CMYK effect tests set main's new StrokeFx overprint and Bevel
gloss_anti_alias.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve catalog and parity-document conflicts without dropping either side.
Preserve ACT swatch routing alongside CSH shapes and add an ACT extension regression.
Photoshop 27.11 stores spot ink 0 = solid in every PSD it saves (its 7
spot PSDs store the 100/25/75/0 % columns as 0/191/64/255) and numbers
the extra channels 3, 4, 5 in resource 1053, after 0 for the merged
transparency. PhotoCraft's PSDs stored the ink straight and wrote no 1053.
- PSD save and open use Photoshop's polarity through
channel_map::photoshop_spot_plane; alpha channels stay straight and the
in-memory convention (1 = solid ink) is unchanged.
- A fresh 1053 is written with the extra channels; a kept one is never
written next to it.
- psd_import::earlier_photocraft_spot_planes: a PSD whose 1077 marks a
spot, with no 1053 and the exact 1057 PhotoCraft 0.1.0-0.7.0 (CS6, NUL)
or 0.8.2-0.11.1 (2026) wrote, is read straight, so it keeps its ink.
- Part 3's switch (PsdExportOptions::photoshop_spot_polarity,
PsdImportOptions) is gone: PSD and the layered TIFF share one path.
- PSD keeps main's empty LMsk and layer flags; Multichannel is unchanged.
- Docs: file-format parity, gaps G10, target-app parity, FILE-215-17 note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Zer0C00L01 <12090083+Zer0C00L01@users.noreply.github.com>
crates/ui-egui/src/lib.rs: replay_save doc comment names both Export As and JPEG; replay_save uses the PR's FormatChoices::from_step, after main's export_dialog::replay branch.
docs/file-format-parity.md: header keeps main's text plus the JPEG Options item (last updated 2026-10-19); TIFF row is main's, JPEG row is the PR's; change-log rows once each, newest first.
docs/gaps.md: header keeps main's text plus the G10 item (last updated 2026-10-19).
Conflicts:
docs/gaps.md: header line is ours plus the PR's missing change item (later Last updated date); change-log rows kept both, newest first
docs/file-format-parity.md: same
Conflicts:
docs/gaps.md: header line is ours plus the PR's missing change item (later Last updated date); change-log rows kept both, newest first
docs/file-format-parity.md: same
apps/photocraft/src/services.rs: SAVE_FILTERS keeps the DDS entry from main and the PR's Cineon entry.
crates/codecs/README.md: Cineon table row from the PR (read and write), then main's DICOM and Amiga IFF rows.
crates/codecs/src/format.rs: caps keep the PR's Cineon (read and write) and main's Dicom and Iff rows.
crates/codecs/src/lib.rs: encode keeps the PR's Cineon arm and main's Dicom and Iff arms.
docs/file-format-parity.md: header kept from main with the Cineon write item added; Cineon / DPX row from the PR, DICOM row from main; the PR's change-log line added once at the top.
docs/gaps.md: header kept from main with the Cineon write item added; Missing list without 'Cineon write'; the PR's Cineon progress line replaces main's read-only one; the PR's change-log line added once at the top.
docs/file-format-parity.md: header line kept from main with the animated WebP item added in front; BMP row from main; WebP row from main plus the PR's animation sentence; the PR's change-log line added once, above the first 2026-10-14 row.
apps/photocraft-web/src/web.rs: OPEN_EXTS kept main's list (with the DICOM extensions) and added iff, lbm, ilbm.
apps/photocraft/src/services.rs: OPEN_EXTS kept main's list (with the DICOM extensions) and added iff, lbm, ilbm.
crates/codecs/README.md: kept both the DICOM and the Amiga IFF table row and section.
crates/codecs/src/format.rs: kept both the Dicom and the Iff variants in every match, detect runs the DICOM check then the IFF check, Format::ALL length re-counted (22).
crates/codecs/src/lib.rs: one use line importing dicom and iff; both decode and encode arms kept.
docs/gaps.md: header line kept from main, with the PR's 'Cineon and Amiga IFF read' wording.
crates/engine/src/file_cmds/tests.rs: both sides' appended tests kept whole.
docs/file-format-parity.md: header line keeps ours plus the PR's change item, date 2026-10-18; TIFF row has both notes; change-log rows kept both, newest first.
apps/photocraft-web/src/web.rs: OPEN_EXTS kept main's list and added dcm, dicom, dc3 after cin.
apps/photocraft/src/services.rs: OPEN_EXTS kept main's list and added dcm, dicom, dc3 after cin.
Integration: resolve with main: .asl joins main's longer File › Open lists (xcf, xcfgz, gz); each language keeps main's new strings and #4049's Styles strings; docs status lines joined.