Commit Graph
65 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 16000b01ef Address CodeRabbit review: batch 17 findings
Engine: droplet dependency capture skips disabled steps; a nested Batch
that was cancelled or could not write its error log fails the caller;
runDroplet forwards overrideOpen; actions.rename follows named Batch
steps; Batch steps inside a script count against MAX_SCRIPT_STEPS;
output claims compare destination identity; Save and Close never
replaces a read-only source. ATN: Md must be a BlnM enumeration,
adjustment-layer names must round-trip, Calculations exports a missing
blending as Multiply, Apply Image refuses unnamed-layer transparency.
Filters: Shape Blur records its resolved geometry, reserves its kernel
memory first, and reports a preparation failure as an error, not a
cancel; Crop keeps an artboard preset when the size is unchanged and
fits the board on a turned crop.

Codecs/cms/algo: zero-axis FITS image HDUs are empty; a GIF walk that
breaks off before the trailer fails closed; two-value .3dl shaper rows
are applied; fragmented custom kernels are refused and their work polls
cancellation.

UI: Conditional branches play through panel playback; paused actions
refuse to resume after being replaced; Playback Options OK validates
its fields; transform double-click ignores a hidden pivot; path boxes
snap to a stationary selection; modal blocking uses the effective tool;
a picker-consumed release clears snap lines and ends a Move drag; the
Crop preset control shows Selection and checks only it; Crop box undo
restores frame_from_selection; Bosnian fill labels.

Tests: timing asserts moved to an ignored test, plus the review's
coverage nitpicks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:37:38 +01:00
yoriswind 6c831b7569 fix: retain LUT shapers and resolve edited Shape Blur geometry 2026-11-04 12:34:44 -08:00
yoriswind 0681361e6e Preserve Batch sources and cancellable action filters
Resolve recorded layer targets per Batch input and refuse destructive EXR Save and Close output. Prepare custom Shape Blur kernels in cancellable jobs, retaining Smart Filter progress and geometry. Preserve supported ATN descriptor metadata, defaults, channel sources and three-value LUT shapers; cover rebuilt naming rules, non-identity Custom kernels and float lookup dither.
2026-11-02 09:37:49 -08:00
Hector VillarrealandClaude Opus 5.5 07dda13742 Color Lookup: the .3dl header tests use a plain header word
Main now reads `Mesh 4 12` as a declared 17³ cube (#9073, #9078), so the
preamble tests put a non-declaring header line in front of their 4-node
tables. They still check that a header is skipped and the rows after it are
read strictly: three_dl_mesh_preamble_is_accepted,
three_dl_malformed_first_token_is_refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:00:34 -06:00
Hector VillarrealandClaude Opus 5.5 c2c685a4bb Color Lookup: a .3dl row starting like a number is never a header
CodeRabbit (discussion_r4368798643): a shaper such as `0oops 114 455 1023`
failed to parse as a number, so it was skipped as a header and the table
loaded without its shaper. A header is now `3DMESH` or a line whose first
token starts with a letter and isn't a number; anything else is read
strictly and a bad token is refused.

Test: three_dl_malformed_first_token_is_refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:19 -06:00
Hector VillarrealandClaude Opus 5.5 17326e5e21 Color Lookup: a .3dl shaper too fine to keep is refused
CodeRabbit finding on #9170 ("Refuse non-even shapers when the aligned bake
grid exceeds MAX_SIZE"), as @bommerts asked too ("clear refusal if the
requested precision cannot be retained"): past MAX_SIZE the bake fell back
to a coarser grid, which can land only on a bent shaper's unbent knots and
render the table alone. Such a shaper is refused. New test:
three_dl_shaper_too_fine_to_keep_is_refused (a 257-point shaper bent at odd
knots loaded unshaped without the fix; a 17-point shaper on a 17³ table
still loads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:58:56 -06:00
Hector VillarrealandClaude Opus 5.5 909ded33f8 Color Lookup: a .3dl shaper starting with NaN or inf isn't a header
CodeRabbit finding on #9170 ("Reject shaper rows that begin with NaN or
inf"): lines starting with a letter were skipped as headers, so a shaper
beginning with NaN or inf was dropped before the non-finite check and the
table loaded alone. A header is now a line whose first word isn't a number
(3DMESH, Mesh 4 12); NaN and inf parse as numbers, so such a shaper is
read and refused. New test: three_dl_shaper_starting_non_finite_is_refused
(the files loaded without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:58:56 -06:00
Hector VillarrealandClaude Opus 5.5 068791475e Color Lookup: a non-finite .3dl shaper is refused
CodeRabbit finding on #9170 ("Reject non-finite shapers instead of silently
dropping them"), which @bommerts' refusal control agrees with: a NaN or inf
shaper was dropped and the table rendered alone, breaking apply-or-refuse.
apply_shaper refuses it. New test: three_dl_non_finite_shaper_is_refused
(NaN, inf and -inf shapers loaded without the fix); the PR's own NaN case
now expects refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:58:29 -06:00
Hector VillarrealandClaude Opus 5.5 e370386753 Color Lookup: the 3DMESH preamble of a .3dl still reads
Review by @bommerts on #9170: strict numeric parsing (999a2c7be) refused the
standard 3DMESH / Mesh 4 12 preamble, because 3DMESH starts with a digit, so
every headered .3dl failed with "bad line 3DMESH". The 3DMESH line is
skipped as a header; rows after it stay strict. New test:
three_dl_mesh_preamble_is_accepted (bad line 3DMESH without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:58:29 -06:00
Hector VillarrealandClaude Opus 5.5 cba7ddc4f2 Color Lookup: a malformed or second .3dl shaper row is refused
Review by @bommerts on #9170: a shaper row with a token that isn't a number
(0 341 junk 682 1023) had the token dropped, which can turn a bent shaper
into an even ramp and lose it, and a second shaper row was ignored. Every
token of a numeric line must parse, and only one shaper row ahead of the
table is read. New test: three_dl_malformed_shaper_rows_are_refused (both
files loaded without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:58:10 -06:00
Hector VillarrealandClaude Opus 5.5 42ddf561cc Color Lookup: a .3dl shaper keeps its knots over any table size
Review by @bommerts on #9170: baking at max(table nodes, shaper points) only
kept the shaper's breakpoints when the two grids lined up. Shaper
0 0 512 1023 over a 5³ table read 0.0834 at 1/3, where the shaper is 0
(about 21 8-bit levels off). The bake grid now holds both the table's nodes
and the shaper's knots (lcm of their intervals, up to MAX_SIZE), so an
identity table carries the curve exactly. New test:
three_dl_shaper_knots_survive_a_nonaligned_table (0.0834 without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:57:09 -06:00
Hector VillarrealandClaude Opus 5.5 216b53301f Color Lookup: an extreme .3dl shaper can't bake NaN into the table
CodeRabbit finding on #9170 ("Check the baked table for non-finite values"):
a finite but extreme shaper such as 0 1e308 -1e308 0 overflowed to infinity
when interpolated over the resampled grid, and infinity times 0 put NaN in the
table. The shaper is scaled to 0..1 before interpolating, and the baked table
is checked like any parsed one. New test:
three_dl_extreme_shaper_bakes_only_finite_values (NaN without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:57:09 -06:00
Hector VillarrealandClaude Opus 5.5 759edaa3c7 Color Lookup: a shaper finer than the .3dl table keeps its curve
CodeRabbit finding on #9170 ("Refuse a non-even shaper that has more points
than the 3D table"): the shaper was baked by reading it only at the table's
own nodes, so a 2x2x2 table kept just its two ends and lost the midtones.
Instead of refusing the file (Photoshop loads it), the table is now baked
with as many nodes as the shaper has points, up to MAX_SIZE. New test:
three_dl_shaper_finer_than_the_table_keeps_its_curve (grey 128 stays 128
without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:57:09 -06:00
Hector VillarrealandClaude Opus 5.5 594e0e54ec Color Lookup: a bent float .3dl shaper is applied
CodeRabbit finding on #9170 ("Fix the even-ramp tolerance for shapers with
scale == 1.0"): the even-ramp check allowed one integer code (1.0) even for a
float shaper in 0..1, so every float shaper passed as even and was dropped.
A float shaper is now held to one 10-bit step. New test:
three_dl_float_shaper_is_applied (grey 128 stays 128 without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:57:09 -06:00
Hector VillarrealandClaude Opus 5.5 0baeea93b0 Color Lookup: apply a .3dl's shaper before the table (#8982)
A .3dl's first row is a shaper: Photoshop applies it as a 1-D curve to
each input channel before the 3-D table. PhotoCraft skipped the row and
treated every shaper as the even ramp, so a file with a bent shaper
rendered as if it had none (grey 128 stayed 128 where Photoshop gives 71
for the shaper 0 114 455 1023, and 64 for a 17-point squared one).
parse_3dl now keeps the shaper and, when it isn't the even ramp, bakes it
into the table: each node becomes the table's trilinear value at the
shaped input. Even ramps (the usual case) are left untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:57:09 -06:00
FredApps e8692b87a4 Merge approved PhotoCraft PR #9418: Color Lookup: honour the .3dl Mesh header's cube edge and output depth (#9078, #9073) 2026-10-29 03:29:52 +01:00
David Mann 7e4ceba5df Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/cms/src/lutfile.rs
2026-10-26 14:26:50 +01:00
Dylan M. Taylor 3877bb1dbe chore: resolve SonarCloud maintainability issues (#9044)
* chore: resolve SonarCloud maintainability issues

* fix: format imports and fix effect_entry argument type

* fix: restore test closure and wildcard import that broke compilation

* refactor: split composite_to_surface to reduce cognitive complexity
2026-10-25 19:38:31 -04:00
highperfocused da4b838e54 Merge upstream/main into fix/3dl-mesh-header
Keep the Mesh declaration's cube edge and output range (#9078) alongside
main's header-aware inference (#9121): a declared range wins for in-range
node codes, and anything else falls back to the depth inference, where a
header (the 3DMESH magic included) allows a 10-bit reading.

main's three_dl_without_a_header test used 'Mesh 1 10' as an arbitrary
header line on a 2-cube; that is now a 3-cube declaration, so it uses the
bare 3DMESH magic as its header.
2026-10-25 15:44:14 +01:00
berceandBrandon Thomas 05c75fe855 fix(cms): read UTF-16 LUT files by their byte-order mark (#9129)
A UTF-16 .look that Photoshop loads was read as UTF-8 and failed with "no <size> in .look", in Color Lookup and in PSD import. Decode FF FE / FE FF as UTF-16 before picking the format.

Fixes #9118

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:31:58 -04:00
DuBearteandClaude Opus 5.5 6f1705a32b fix(cms): headerless .3dl tables read on at least the 12-bit scale (#9121) (#9215)
A table with no header peaking at 255 or 1023 was scaled to full range,
so white came out 255 where Photoshop gives 16 or 64. Without a header,
the inferred output depth now starts at 12 bits.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:17:39 -04:00
DuBearteandClaude Opus 5.5 7e8386c94c fix(cms): ignore tags inside .look XML comments (#9109) (#9210)
A comment holding <size>/<data> text was read as the LUT, so a commented
inverse .look rendered as identity. Drop comments before finding tags.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:17:34 -04:00
highperfocused 8929175c08 fix(cms): reject malformed Mesh declarations and honour the declared range (#9078)
A `Mesh` line is now a declaration only when it carries exactly two integer
fields after the keyword; tokens like `Mesh invalid 1 12` no longer recover a
declaration from numbers in the wrong positions and fall back to headerless
inference. Under a valid declaration the node codes are integers in the declared
output range, so the declared range is used for every in-range value — including
a small max, where code 1 is 1/declared of full output, not full output itself
(#9073). Only a node beyond the declared range still falls back to max-value
inference.
2026-10-25 13:44:30 +01:00
highperfocused 788c8956c0 Color Lookup: honour the .3dl Mesh header's cube edge and output depth (#9078, #9073)
The Lustre `3DMESH` / `Mesh <inBits> <outBits>` header declares the cube edge
(2^inBits + 1) and the output code range (up to 2^outBits - 1); the parser
skipped every text line, so it inferred both from the data. That rejected a
3-squared cube whose three-value shaper row counted as a 28th node, and
divided 10-bit codes under a 12-bit declaration by 1023 instead of 4095.
Photoshop 27.11 accepts and renders both (measured by the reporters).
2026-10-25 12:17:57 +01:00
David Mann feb20ebc60 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/gpu/src/plan.rs
#	crates/io/src/adjust_map.rs
2026-10-24 02:59:10 +02:00
SimoneandLucenx9 4092fe79c9 fix(cms): reject malformed hex data in .look LUTs (#8657)
Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com>
2026-10-23 17:40:16 -04:00
Dylan M. Taylor 78ae50f7b2 security: bisect flat-table and gradient-stop lookups; charge ICC curve samples; bound PSD planes, RAW ljpeg retention and TIFF dump preview 2026-10-23 14:41:53 -04:00
92262d5950 fix(cms): confirm ICC profile identity by bytes, not the 64-bit hash alone (#8863)
Profile::content_hash is a fixed-key 64-bit SipHash of the ICC bytes, so a
crafted profile can share another profile's hash (about 2^32 work). Two
places took an equal hash as proof of an identical profile:

- transform::cached keyed its process-wide cache on the two hashes and the
  options, so a colliding profile was handed the other profile's cached
  transform and converted with the wrong colours.
- Profile::same_colors returned true on equal hashes, so a colliding
  profile was taken for the other one and a needed conversion was skipped.

Add Profile::same_bytes, which compares the ICC bytes (an Arc pointer check
first, then the hash, then the bytes). same_colors uses it instead of the
hash. Each transform cache entry now keeps the ICC bytes of its two
profiles; a hit counts only when they match, and a colliding pair gets its
own transform without replacing the cached entry. The transform is built
after the cache lock is released, as before.

The regression tests give Display P3 the content hash of sRGB (a stand-in
for a found collision): before the fix, same_colors called them the same
and the cache returned sRGB's transform for P3; now both see P3's colours.

Fixes #8613

Co-authored-by: John Turnbach <jturnbach@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:54:42 -04:00
David Mann c16f899467 Merge origin/main into fix/6544-lut-format-roundtrip
# Conflicts:
#	crates/format/tests/it/roundtrip.rs
2026-10-23 00:06:48 +02:00
aiongg ffa400c754 Keep tests correct and docs current with one test binary per crate
- 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.
2026-10-22 16:55:34 +00:00
aiongg a6428d3dfb Build each crate's integration tests as one binary
Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs; helper modules under tests/common move with them.

These keep their own binary (own process):
- engine panic_hunt: a process-wide panic hook counts every panic.
- engine text_gamma: changes the process-wide text gamma.
- engine smart_filter_stages, mask_cache_purge: assert on global caches.
- codecs pnm_peak_memory: reads the process's peak RSS.
- photocraft mac_menu_appkit: harness = false.
2026-10-22 16:55:34 +00:00
aiongg 934eb71d84 Integration: merge main
crates/engine/src/file_cmds.rs: batch keeps the save-point closure (takes the output path) and also copies the session's authorize hook into each scratch session, as main now does.
crates/raw/src/sensor.rs: both sides cap the memory of retained tiles; kept the existing retained_segment_bytes check (checked multiplication, same limit) and dropped main's duplicate inline loop.
2026-10-22 15:39:51 +00:00
aiongg aba7e03d60 Merge PR #8083: Write DOMAIN_MIN/DOMAIN_MAX in the .cube writer
crates/cms/src/lutfile.rs: both PRs added a test at the same place; kept both whole, each with its own closing brace
2026-10-22 15:37:38 +00:00
aiongg 0974ee5394 Merge PR #8023: Keep '#' inside quoted .cube TITLE when parsing 2026-10-22 15:37:38 +00:00
Dylan M. Taylor 9e8a3eecf0 security(cms): bound inverse-curve and CLUT interpolation work
Two per-pixel/per-sample amplifiers driven by a crafted ICC profile:

- table_inverse recomputed a sampled curve's flat-run edge indices by
  scanning the whole table on every call, and transform construction
  inverts each destination curve OUT_TABLE times. Compute the edges once
  per curve via a new Curve::inverse_table batch method.
- eval_multilinear enumerated 2^inputs cube corners over every declared
  input, including single-grid-point (degenerate) dimensions that
  contribute nothing. Interpolate over the effective dimensions only, so
  a 15-channel CLUT of one-point dims no longer does 2^15 iterations per
  pixel.

Findings:
- acfbe3a3-ee58-4b7e-b72a-5a4877dd15c9 (CWE-407, repeated flat-run scans)
- 9fcc72e7-46d6-4f75-9333-cb14a65eeafd (CWE-407, degenerate-dim corner blowup)
2026-10-22 07:11:02 -04:00
David Mann 14b928e8a8 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-21 10:56:06 +02:00
Higgs32584andClaude Opus 5.5 b0acb119d6 Merge upstream main into fix/6752-heic-hlg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:19:06 -04:00
NetterSilverandClaude Haiku 5.5 67314ff606 Write DOMAIN_MIN/DOMAIN_MAX in the .cube writer
Found while testing crates/cms/src/lutfile.rs:227 (write_cube)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 00:46:33 +02:00
NetterSilverandClaude Haiku 5.5 b85b638e77 Keep '#' inside quoted .cube TITLE when parsing
Found while testing crates/cms/src/lutfile.rs:111 (parse_cube) and :227 (write_cube)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 18:15:46 +00:00
ha1des 472a8d6f29 Preserve near-black ICC colors on the GPU canvas 2026-10-20 14:18:00 +02:00
David MannandClaude Opus 5.5 47063c29c5 Merge main into fix/6544-lut-format-roundtrip
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 18:39:12 +02:00
Michael Higgins d356513052 Merge branch 'storytold:main' into fix/6752-heic-hlg 2026-10-16 20:27:09 -04:00
Higgs32584andClaude Opus 5.5 41bc07d8f5 HEIF: read the nclx colour description; HLG/PQ HEICs open as linear BT.2020 32-bit (#6752)
The colr nclx primaries and transfer were dropped, so a 10-bit BT.2020 HLG
HEIC opened as if its samples were sRGB. photocraft-heif now returns the
nclx (inherited from grid tiles like the ICC profile), codecs decodes PQ and
HLG to linear light in 32-bit float (BT.2100 inverse OETF + OOTF at 1000
cd/m2, 203 cd/m2 diffuse white = 1.0), and io tags the document with the
matching profile from the new photocraft_cms::builtin::cicp_rgb. BT.709/sRGB
SDR files are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:24:47 -04:00
wwew e33384c9ba docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
David MannandClaude Opus 5.5 750a91c8b2 Color Lookup: PSD save keeps a .3dl/.look LUT file as loaded (#6544)
A Color Lookup layer embeds its LUT file in the PSD (LUT3DFileData,
LUTFormat, LUT3DFileName). Photoshop stores the file as loaded and tags
its format; it never converts it. PhotoCraft kept only the parsed table,
so every save re-encoded it as .cube with LUTFormatCUBE while keeping the
original name: a layer loaded from "Film.3dl" was saved as .cube data
under a .3dl name.

The layer now keeps the file it was loaded from (its format and bytes,
`ColorLookup::source`), set when a PSD is read and when the colorLookup
command loads `data`/`file`, and cleared when a built-in look or "none"
replaces the table. A PSD save writes those bytes and format back
unchanged, but only while they still parse to the layer's table, so the
stored file can never go stale. Otherwise (a table made in PhotoCraft,
or one that no longer matches) it falls back to .cube as before and
swaps a .3dl/.look extension in the name for .cube.

The new field is `#[serde(default)]` and skipped when empty, so older
.pcraft files load unchanged. lutfile gains Format::detect/parse_as so
the recorded format is exactly the one the parser used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 20:59:24 +02:00
wwew 98fc6ab072 fix(cms): write the product name as PhotoCraft in built-in profile descriptions (#5614) 2026-10-14 15:06:35 -07:00
Elvis33LEandBrandon Thomas aa44eafe2c ACEScg: File Handling option opens colour-less EXR renders in the ACES working space (#4908)
* ACEScg: File Handling option opens colour-less EXR renders in the ACES working space

Our render pipelines write ACEScg (AP1 primaries, ACES white ~D60,
linear) and — like most — state no colour space in the EXR header, so
io's linear-sRGB default misrepresented them (saturated hues shifted).
- cms: AcesCg joins the built-in profiles, generated from the published
  AP1 primaries and ACES white point with a linear TRC (id "aces-cg",
  aliases acescg).
- engine: File Handling gets "Open color-less EXR as ACEScg". When on,
  an EXR that io tagged with the linear-sRGB default opens as ACEScg
  instead; embedded or EXIF-declared profiles stay untouched.
Translated in every catalog. ACES view transforms (OCIO-based display)
remain future work; this covers the document's source colours.

* scorecard: 26 of 158 (main's #3472 preference added one too)

* Regenerate docs/scorecard.md after merging main

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:46:27 -04:00
Michael HigginsandClaude Opus 5.5 b059a6d8b3 ICC parser: checked arithmetic on offsets and counts from the file (#4976) (#5227)
Offsets and counts stored in a profile reach u32::MAX, which is
usize::MAX on 32-bit targets such as wasm32. Several places added to or
multiplied them before any bounds check, so a malformed profile could
panic (overflow checks on) or read from a wrapped offset (checks off).

- curv: reject an over-long count before computing the element length.
- mAB/mBA: the matrix and CLUT read through one bounds-checked slice at
  the stored offset instead of adding to the offset.
- mluc: the record offset is computed with checked arithmetic.
- XYZ: one bounds-checked slice instead of off + 4 and off + 8.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:45:46 -04:00
4062d40070 Color: Dot Gain 20%-compatible default working gray, as in Photoshop (#4610)
* feat(color): Dot Gain 20%-compatible default working gray, as in Photoshop

Photoshop's default working gray is Dot Gain 20%, and its RGB -> Grayscale
measured in Photoshop 27.11 (default Color Settings) is D50 luminance
encoded with gamma 1.732: sRGB 64 / 128 / 192 grey and pure red become
46 / 105 / 177 / 107. PhotoCraft defaulted to sGray, which kept the sRGB
value (64 / 128 / 192 / 130), up to 24 levels off.

Adds the built-in "Dot Gain 20%-compatible (gamma 1.732, Photocraft)"
gray profile (id dot-gain-20-compat; our own fitted curve, not Adobe's
profile, the same gamma PhotoCraft already uses for gray adjustments) and
makes it the default working gray and the fallback for untagged gray
documents. sGray and Gray Gamma 2.2 stay selectable.

This changes a default: untagged gray documents now convert and display
as Photoshop shows them (midtones lighter than under sGray); documents with
an embedded profile are unchanged.

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

* Status bar test: the default working gray is now Dot Gain 20%-compatible

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

* .atn test: #202020 in the Dot Gain 20%-compatible working gray

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 02:45:09 -04:00
HEOJUNFOandClaude Opus 5.5 bb17dd555c Color: convert input-only embedded profiles to the working space on open (#3876) (#3900)
An iPhone photo embeds Apple's Wide Color Sharing Profile, an input-class
profile with only AToB tables. Preserve kept it as the document's space, so
every composite into the document failed with "RGB profile without
matrix/TRC or BToA tags". Such a profile can't be a destination, so open
converts from it to the working space whatever the policy (Off still
discards it) and the report says inputOnly.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 09:57:11 -04:00