Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
667d140
feat(icc): build ICC profiles from named colour spaces and CICP
justin13888 Sep 9, 2026
2771400
fix(icc): adapt built-in colorants to the PCS D50 the profile declares
justin13888 Sep 9, 2026
f562452
docs(icc): record the built-in profile constructors and their curve c…
justin13888 Sep 9, 2026
9b12bba
refactor(icc): fold the curveless transfer arm into the wildcard
justin13888 Sep 10, 2026
6592e65
fix(icc)!: build CICP profiles from the transfer H.273 defines
justin13888 Sep 10, 2026
d250db5
docs(icc): correct the transfer table, the fan-in figure and the PQ l…
justin13888 Sep 10, 2026
f868da1
docs(icc): state the grey gamma limit instead of linking a private const
justin13888 Sep 10, 2026
ae43874
fix(icc): bound the grey gamma by what the kTRC actually encodes
justin13888 Sep 10, 2026
5b3735b
test(icc): pin the complement of the encodable transfer code points
justin13888 Sep 10, 2026
569cc54
docs(icc): stop calling the full-range flag a conformance requirement
justin13888 Sep 10, 2026
5b7fde5
docs(icc): quantify the second sanctioned reading of the BT.709 curve
justin13888 Sep 10, 2026
6d6a234
style(icc): reformat the transfer code-point sweep assertion
justin13888 Sep 10, 2026
3d56d3d
style(icc): write the saturation bound at its shortest exact decimal
justin13888 Sep 10, 2026
5fb03d3
fix(icc)!: decline a CICP triple that does not signal full range
justin13888 Sep 10, 2026
c4020c7
fix(icc): describe a grey profile by the gamma its kTRC holds
justin13888 Sep 10, 2026
1b45aa4
docs(icc): cite cicpType's own clause and quote the tag's mid-grey va…
justin13888 Sep 10, 2026
5a9cc0c
docs(icc): cite the clauses ICC.1:2022 gives the colorant and chad tags
justin13888 Sep 10, 2026
a5c8873
docs(icc): check the transfer-axis claim about gamut-color in a doctest
justin13888 Sep 10, 2026
d0e3211
docs(icc): correct the CIE D50 tristimulus this crate deliberately av…
justin13888 Sep 10, 2026
ce65dd7
test(icc): guard the sampled PQ curve at the quantum it claims
justin13888 Sep 10, 2026
96bddf0
docs(icc): say where a SourceProfile bundle and its profile diverge
justin13888 Sep 10, 2026
bfc41a3
docs(icc): name the colorimetry this crate does restate, and its gates
justin13888 Sep 10, 2026
288b2a7
docs(icc): cite cicpTag's own clause for the rule that decides the flag
justin13888 Sep 10, 2026
9baf399
docs(icc): disclose the rendering intent every constructor writes
justin13888 Sep 10, 2026
4379973
docs(icc): list thiserror among the crate's dependencies
justin13888 Sep 10, 2026
a34093d
test(icc): gate the two cross-crate figures the docs still quoted
justin13888 Sep 10, 2026
a8ad48c
test(icc): gate the SourceProfile divergence figures the docs quote
justin13888 Sep 25, 2026
b4dda22
test(icc): have lcms2 check every built profile's white point, curves…
justin13888 Sep 25, 2026
047903b
fix(icc): build narrow-range CICP signalling whose range is on Y/Cb/Cr
justin13888 Sep 25, 2026
fda06ac
docs(icc): say what actually declines a transfer code point
justin13888 Sep 25, 2026
8dccb8f
Merge remote-tracking branch 'origin/master' into feat/424-icc-builti…
justin13888 Sep 25, 2026
c3af92b
style(icc): format the lcms2 cicp synthesiser comparison
justin13888 Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Dependency edges (a crate depends on those to its right):
- **gamut-ifd** — TIFF/IFD container core (byte order, field types, IFD read/write); a
low-level container primitive (sibling to bitstream), shared by `gamut-tiff` and EXIF
metadata. ← core. Optional `bigtiff` feature adds 64-bit BigTIFF. Per-format metadata
crates (**gamut-exif** ← ifd; **gamut-icc**; **gamut-xmp**; **gamut-iptc** ← xmp) and the
crates (**gamut-exif** ← ifd; **gamut-icc** ← color; **gamut-xmp**; **gamut-iptc** ← xmp) and the
**gamut-metadata** facade (← exif/xmp/icc/iptc) layer on top under the `metadata` feature;
format crates consume the facade for embedded metadata.
- **gamut-cmm** — ICC colour management module (epic #323): the transform engine — a
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/gamut-icc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ categories.workspace = true
workspace = true

[dependencies]
# Colorimetry for the built-in profile constructors (`src/builtin.rs`): the CICP code-point
# chromaticity tables, the RGB->XYZ / Bradford derivations, and the ST 2084 transfer. The edge
# points this way because gamut-color is the primitive (fan-in 10 without this edge) and has no
# need of a profile
# serializer; see `STATUS.md`, "Built-in profiles".
gamut-color.workspace = true
gamut-core.workspace = true
md-5.workspace = true
thiserror.workspace = true
Expand Down
51 changes: 47 additions & 4 deletions crates/gamut-icc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ the format crates can read, preserve, and embed accurate color characterization.
- **Clean-slate from the spec.** Implemented from **ICC.1:2022** (profile v4.4, equivalent to
ISO 15076-1; [`../../references/icc`](../../references/icc)), with v2 read support since most
embedded profiles are still v2.
- **Dependency-light.** An ICC profile needs neither IFD nor XML machinery, so this crate builds
only on [`gamut-core`](../gamut-core) plus [`md-5`](https://crates.io/crates/md-5) (the §7.2.18
profile-ID digest) — distinct from CICP color signaling, which lives in
[`gamut-color`](../gamut-color).
- **Dependency-light.** An ICC profile needs neither IFD nor XML machinery, so the whole
dependency list is [`gamut-core`](../gamut-core), [`gamut-color`](../gamut-color) (the
colorimetry behind the built-in profile constructors below),
[`md-5`](https://crates.io/crates/md-5) (the §7.2.18 profile-ID digest) and
[`thiserror`](https://crates.io/crates/thiserror).

## Usage

Expand All @@ -32,6 +33,48 @@ let serialized = profile.to_bytes()?; // spec-valid bytes, ready to re-embed
# Ok(()) }
```

### Built-in profiles, and CICP → profile

Constructing a profile is the other direction. `IccProfile::builtin` emits a spec-valid v4
matrix/TRC display profile for a named space, and `IccProfile::from_cicp` does the same from the
H.273 code-point triple AVIF, HEIC and JXL usually signal instead of embedding a profile:

```rust
use gamut_icc::{BuiltinProfile, Cicp, IccProfile};

let p3 = IccProfile::builtin(BuiltinProfile::DisplayP3).expect("a modelled space");
assert!(p3.validate().is_empty());
let bytes = p3.to_bytes()?; // ready to embed

// BT.2020 primaries + PQ, as an AVIF `colr` box would signal them. The matrix coefficients are
// deliberately not carried into the profile: ICC.1:2022 §10.3 requires zero for an RGB profile.
// The range flag is read against the matrix: narrow range on Y/Cb/Cr de-matrixes to full-scale
// RGB and builds, while narrow range on the RGB samples themselves (matrix 0) is declined.
let signalled = Cicp {
colour_primaries: 9,
transfer_characteristics: 16,
matrix_coefficients: 9,
video_full_range_flag: 0,
};
assert!(IccProfile::from_cicp(signalled).is_some());
assert!(IccProfile::from_cicp(Cicp { matrix_coefficients: 0, ..signalled }).is_none());
# Ok::<_, gamut_icc::IccError>(())
```

The named spaces are sRGB, linear sRGB, Display P3 and BT.2100 PQ, plus
`IccProfile::gray_with_gamma` for a monochrome profile. Their primaries and white point are read
from [`gamut-color`](../gamut-color) rather than restated here, so the two cannot drift; Adobe RGB
and ProPhoto RGB have no code point on either CICP axis and are declined rather than approximated.
`from_cicp` reaches further on the transfer axis — the BT.709 family (H.273 code points 1, 6, 14
and 15), linear, sRGB and PQ all have an ICC tone-curve encoding — because what an ICC tag can
encode is not the same set as what gamut-color can evaluate. Every constructor returns an `Option`
and declines signalling it cannot describe — including a narrow-range `VideoFullRangeFlag` under
RGB matrix coefficients (0, 8, 16, 17), which is a scaling of the RGB samples that a full-scale
matrix/TRC profile does not perform and that de-matrixing does not remove. Under luma–chroma
coefficients the narrow range is on Y/Cb/Cr, de-matrixed RGB is full scale, and the profile is
built with a full-range `cicpType`. `STATUS.md` tabulates the curve chosen per transfer and records why
each field is rewritten or refused.

**Every ICC.1:2022 §10 element type decodes semantically** — the `XYZType`, curve, and text types;
the `lut8`/`lut16`/`lutAToB`/`lutBToA` transforms; `namedColor2Type`; the measurement/signalling
types (`chromaticityType`, `cicpType`, `measurementType`, `viewingConditionsType`, `dataType`); the
Expand Down
Loading
Loading