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>
Retain the relative paper-white requirement and round-trip checks while validating finite, bounded endpoints and darker full-K ink.
Add an encoded and reparsed AToB1 regression with an independently calculated 100/87/74/61/48 lightness ramp, plus invalid endpoint cases.
Signed-off-by: FredApps <153173854+FredApps@users.noreply.github.com>
PAR_MIN_PIXELS / PAR_CHUNK_PIXELS (photocraft-cms) and OPENABLE
(photocraft-engine) are only used by native-only code (the rayon paths
and list_images). Give them the same cfg so wasm32 check and clippy
-D warnings are clean. No behaviour change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A DeviceLink's header PCS field holds its output colour space
(ICC.1:2010 §7.2.7), so a CMYK→CMYK link has a 4→4 AToB0. The LUT
channel check hard-coded 3 outputs and rejected such files as invalid,
so users were told a well-formed profile was corrupt and never reached
the "DeviceLink profiles cannot be used" error.
Check DeviceLink LUTs against the header's output space instead, and
only let a Lab DeviceLink through link_stages when its tables end in
3 channels, so a Lab→CMYK link gets the unsupported error rather than
building a mismatched transform.
Fixes#2337
Claude-Session: https://claude.ai/code/session_01X5k8jSoLtGswanmHBipHER
Co-authored-by: Leel Wasanatha <260480244+kavishkashehan726-sudo@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* OpenEXR/HDR export: linearize grayscale documents too
Fixes#2386. The EXR/HDR branch of export_flat converted RGB images to
linear sRGB but left gray ones as stored, so an sGray #808080 was written
as 0.502 instead of 0.216. Gray images are now converted from their
profile (sGray when untagged) to linear gray. An untagged grayscale
OpenEXR opens tagged linear gray (an EXR Y channel is linear luminance),
and the open policy keeps that profile without a mismatch prompt, as it
already does for linear sRGB.
* contributors: add itsF4LCON
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Color Lookup: keep the .cube input domain and reject the ones we cannot apply
DOMAIN_MIN/DOMAIN_MAX (and LUT_3D_INPUT_RANGE) were parsed and thrown
away, so a log-domain or extended-range look was applied to plain 0..1
inputs: silently wrong colours, exactly what the in-code comment
admitted. The domain now travels with the parsed LutFile (validated:
every component finite and min < max), and colorLookup refuses a
non-default domain with the reason instead of shifting the look -
the same unsupported-over-wrong-output call the codecs make. Honoring
a custom domain end to end means carrying it through ColorLookup
(doc, compose, gpu, PSD round trip) and stays open as the follow-up.
* tests: the colorLookup domain rejection needs a pixel layer active
The outer session of the test still had the adjustment layer active, so
the command was disabled before reaching the domain rejection - the
assertion only passed when the active layer happened to differ. Make it
deterministic with a fresh pixel layer.
* Color Lookup: bake a 1D .cube's input domain into the resample
The 1D branch of parse_cube still ignored DOMAIN_MIN/MAX and
LUT_1D_INPUT_RANGE. Since the 1D table is resampled onto a 0..1 cube anyway,
the domain is now applied there (saturating outside), and a degenerate
domain is rejected like the 3D one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cargo fmt after rebasing onto main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
connection() scaled the PCS by src.media_white() / dst.media_white() with
no check on the divisor, so a v4 profile whose wtpt tag holds zeros (parsed
and stored verbatim) gave a NaN scale, and every colour through a direct
absolute transform or a paper-simulating proof came out NaN. The transform
is now refused with CmsError::Invalid when either white has a zero,
negative or non-finite component; the other intents never divide by it and
are unchanged.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Opening any file Photoshop tagged with sRGB IEC61966-2.1 asked
"Embedded Profile Mismatch: sRGB IEC61966-2.1 vs sRGB IEC61966-2.1":
open_policy compared a hash of the ICC bytes, and Photoshop's v2 profile
(1024-entry tables) and the built-in v4 sRGB (parametric curves) are the
same colours in different bytes. 32-bit documents in Photoshop's linear
sRGB missed the stay-linear exception for the same reason.
Add Profile::same_colors: same colour space and every sample of a device
grid within dE76 0.5 through both (re-encoding costs 0.025; moving the
red primary 2% costs 1.0). Colours are compared rather than round-tripped
because CMYK -> PCS -> CMYK is not an identity even for one profile.
Of the 75 profile-tagged PSDs in the test corpora, all 75 prompted on
open; now 6 do (Adobe RGB, Gray Gamma 2.2, SWOP), each a real mismatch.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Real crash: a gray ICC profile whose A2B0 tag is stored as "mBA "
parsed fine, but re-encoding its RGB view (gray_as_rgb, used for gray
documents' composites) hit panic!("unsupported lutAtoB stage layout").
The ICC writer now leaves out a LUT its tag type can't store, and
gray_as_rgb drops the gray LUTs (one input channel) so the RGB view is
the matrix/TRC model it builds. Regression test
gray_profile_with_reversed_lut_type_reencodes.
- math::rgb_to_xyz_matrix returns None for dependent primaries.
- Builtin::profile indexes its cache by discriminant (tested); the shipped
CMYK profile falls back to synth::coated_cmyk() instead of expect.
- Two constant-only expects (Bradford inverse, built-in primaries) use the
documented narrow allow.
- cms denies unwrap/expect/panic/unimplemented/todo/unreachable.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rust 1.99's clippy adds chunks_exact_to_as_chunks and some_filter, which
broke the Clippy step on every CI runner (it uses the latest stable). All
sites are mechanical: as_chunks drops the remainder exactly like
chunks_exact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Debian/Ubuntu with colord installed, system_profiles panics on
/usr/share/color/icc/colord/x11-colors.icc ("unsupported ICC feature:
curve type \0\1\0\0"). That profile is a named-colour (nmcl) profile
whose B2A0 lutBToAType is spec-invalid: inputs=3, but the M-curve
offset (32) leaves room for one curve before the matrix offset (48),
and the 48-byte matrix overlaps the B curves at 64. The parser reads
the second M curve at offset 48 and correctly rejects it. Named-colour
profiles are never used for conversions (the test already skips them
after parsing), so skip them when they fail to parse instead of failing
the suite on a host file.
Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>