diff --git a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json index 9344cbe1..6f16630c 100644 --- a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json +++ b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json @@ -1 +1 @@ -{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"6f24c596c8c29e4116bc1f19ba70390f90f2a606ae617eb04c42dd4a1da15343","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"2cf6589a15d2669aca9f1f5a287841805c0fe7293074530d70a1cb803d235c7d","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"e361cf144e0630a4f2ff52261e6515c9394188fc1351c97b98ea9c0ec87ec39d"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"33b959f11366415143b5b03fcfe370d1fb7e61e46ed05349ed17560d10663ff7"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"37cff33755c5a700853ccbc08bd539d2235325b9eb208be696444799d4cf819e"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"7e10638e8da68dc1279f078e0a0daa5caf10af403eb00c1cbbd5506190e74d9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"a58acbae5b06a8cd9a45adf93ddfbdfb569c5916b60140bd4046182aab2a9518"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"cb52f917d260ee580cd5cb78666ed5d3f1fe9351e9874b51250d1e8571a48c73"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"f1d6c7a66885326caea2f7f469061c723b826ff99b294324e5a478724f8981f6"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"0755210e3e591d7049f293a0f0b7647681631f32feee5ad7d3b3309cffca8f9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"6cd73600267b148c3e9ecc8d2d623f7f8576aed0e1c4c7c50071e297810b8d4b"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"5a5badfdd164d88aceaee64c4fe519a5151661242f4c4b7982bce816a8b85516"} +{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"5835c6225252df5184803b548ba151654a05db53ae9bc19b2a9be5999a868311","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"4a762a7fd0a658286288ef651cbaf53940e4676f47c63c970799736650a62e21","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"e361cf144e0630a4f2ff52261e6515c9394188fc1351c97b98ea9c0ec87ec39d"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"33b959f11366415143b5b03fcfe370d1fb7e61e46ed05349ed17560d10663ff7"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"37cff33755c5a700853ccbc08bd539d2235325b9eb208be696444799d4cf819e"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"7e10638e8da68dc1279f078e0a0daa5caf10af403eb00c1cbbd5506190e74d9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"9ad998d3b7ac01a03afa398a6750ab71dc1278da991202933cf9006c3cedf5f5"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"cb52f917d260ee580cd5cb78666ed5d3f1fe9351e9874b51250d1e8571a48c73"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"f1d6c7a66885326caea2f7f469061c723b826ff99b294324e5a478724f8981f6"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"0755210e3e591d7049f293a0f0b7647681631f32feee5ad7d3b3309cffca8f9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"6cd73600267b148c3e9ecc8d2d623f7f8576aed0e1c4c7c50071e297810b8d4b"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"f546925d6dedf81bcb78ac9f7342ef8ab3ac93e61cfda5016783c3f733c0f9dd"} diff --git a/crates/labcolors-core/src/lcs_occurrence.rs b/crates/labcolors-core/src/lcs_occurrence.rs index 32eb9147..ea6a9295 100644 --- a/crates/labcolors-core/src/lcs_occurrence.rs +++ b/crates/labcolors-core/src/lcs_occurrence.rs @@ -10,7 +10,7 @@ //! arbitrary second context. use crate::Srgb8; -use crate::spaces::cam16::forward_correlates_v1; +use crate::spaces::cam16::{forward_correlates_v1, ucs_j, ucs_m}; use crate::spaces::oklab::xyz_d65_to_oklab_v1; use crate::spaces::srgb::xyz_d65_from_srgb8_v1; use crate::spaces::vc::{Cam16SurroundV1, ViewingConditions}; @@ -702,17 +702,26 @@ pub enum Cam16ViewReleaseId { LiEtAl2017Cie248ForwardV1, } +/// Formula and operation-order release of the rectangular CAM16-UCS view. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Cam16UcsViewReleaseId { + LiEtAl2017Cam16UcsV1, +} + /// Typed release discriminator used only to qualify derivation errors. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum AppearanceViewReleaseIdV1 { Oklab(OklabViewReleaseId), Cam16(Cam16ViewReleaseId), + Cam16Ucs(Cam16UcsViewReleaseId), } pub(crate) const OKLAB_VIEW_RELEASE_V1: OklabViewReleaseId = OklabViewReleaseId::Ottosson20210125XyzD65V1; pub(crate) const CAM16_VIEW_RELEASE_V1: Cam16ViewReleaseId = Cam16ViewReleaseId::LiEtAl2017Cie248ForwardV1; +pub(crate) const CAM16_UCS_VIEW_RELEASE_V1: Cam16UcsViewReleaseId = + Cam16UcsViewReleaseId::LiEtAl2017Cam16UcsV1; /// Finite binary64 coordinate with canonical positive zero. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -746,6 +755,10 @@ pub enum AppearanceViewFieldV1 { Cam16M, Cam16S, Cam16Hue, + Cam16UcsJPrime, + Cam16UcsMPrimeIntermediate, + Cam16UcsAPrime, + Cam16UcsBPrime, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -758,6 +771,11 @@ pub enum AppearanceStateDerivationErrorV1 { field: AppearanceViewFieldV1, reason: NumericDomainError, }, + InconsistentDerivedHueState { + release: Cam16UcsViewReleaseId, + hue: HueState, + m_prime_bits: u64, + }, } /// Rectangular Oklab geometry of one admitted XYZ(D65) stimulus. @@ -834,6 +852,38 @@ impl Cam16ViewV1 { } } +/// Rectangular CAM16-UCS coordinates derived from one admitted CAM16 view. +/// +/// This is a coordinate view of one occurrence, not a pairwise difference +/// calibration, universal perceptual scale, editable colour or inverse route. +/// Its polar magnitude is an intermediate only; the stored coordinates are the +/// published rectangular `(J', a', b')` form. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Cam16UcsViewV1 { + release: Cam16UcsViewReleaseId, + j_prime: FiniteNonNegative, + a_prime: FiniteCoordinate, + b_prime: FiniteCoordinate, +} + +impl Cam16UcsViewV1 { + pub(crate) const fn release(self) -> Cam16UcsViewReleaseId { + self.release + } + + pub(crate) fn j_prime(self) -> f64 { + self.j_prime.get() + } + + pub(crate) fn a_prime(self) -> f64 { + self.a_prime.get() + } + + pub(crate) fn b_prime(self) -> f64 { + self.b_prime.get() + } +} + /// One-way, derived appearance snapshot of exactly one occurrence. /// /// Canonical LCS identity remains [`LcsOccurrence`] (`sample × context`). This @@ -876,6 +926,15 @@ impl AppearanceState { pub(crate) const fn cam16(self) -> Cam16ViewV1 { self.cam16 } + + /// Derive the optional-cost rectangular CAM16-UCS view from this state's + /// already admitted CAM16 coordinates. + /// + /// Keeping the rescale lazy avoids an `ln` plus polar-to-rectangular + /// trigonometry for evaluators which request only Oklab or CAM16. + pub(crate) fn cam16_ucs(self) -> Result { + derive_cam16_ucs_view_v1(self.cam16) + } } fn view_numeric_error( @@ -951,3 +1010,48 @@ fn derive_cam16_view_v1( hue, }) } + +fn derive_cam16_ucs_view_v1( + cam16: Cam16ViewV1, +) -> Result { + let release = AppearanceViewReleaseIdV1::Cam16Ucs(CAM16_UCS_VIEW_RELEASE_V1); + let j_prime = FiniteNonNegative::new(ucs_j(cam16.j())).map_err(|reason| { + view_numeric_error(release, AppearanceViewFieldV1::Cam16UcsJPrime, reason) + })?; + let m_prime = FiniteNonNegative::new(ucs_m(cam16.m())).map_err(|reason| { + view_numeric_error( + release, + AppearanceViewFieldV1::Cam16UcsMPrimeIntermediate, + reason, + ) + })?; + + let [a_prime, b_prime] = if m_prime.get() == 0.0 { + // Rectangular zero has no angular dependency. This branch also keeps + // both coordinates canonical positive zero for exact black. + [0.0, 0.0] + } else { + let HueState::Defined(hue) = cam16.hue() else { + return Err( + AppearanceStateDerivationErrorV1::InconsistentDerivedHueState { + release: CAM16_UCS_VIEW_RELEASE_V1, + hue: cam16.hue(), + m_prime_bits: m_prime.get().to_bits(), + }, + ); + }; + let radians = hue.degrees().to_radians(); + [m_prime.get() * radians.cos(), m_prime.get() * radians.sin()] + }; + + Ok(Cam16UcsViewV1 { + release: CAM16_UCS_VIEW_RELEASE_V1, + j_prime, + a_prime: FiniteCoordinate::new(a_prime).map_err(|reason| { + view_numeric_error(release, AppearanceViewFieldV1::Cam16UcsAPrime, reason) + })?, + b_prime: FiniteCoordinate::new(b_prime).map_err(|reason| { + view_numeric_error(release, AppearanceViewFieldV1::Cam16UcsBPrime, reason) + })?, + }) +} diff --git a/crates/labcolors-core/src/lcs_occurrence_tests.rs b/crates/labcolors-core/src/lcs_occurrence_tests.rs index 7f9659d9..d51ab4e4 100644 --- a/crates/labcolors-core/src/lcs_occurrence_tests.rs +++ b/crates/labcolors-core/src/lcs_occurrence_tests.rs @@ -5,14 +5,14 @@ use crate::lcs_occurrence::{ ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1, AdaptingLuminanceCdM2, AppearanceContextFieldV1, AppearanceContextId, AppearanceContextSchemaReleaseId, AppearanceState, AppearanceStateDerivationErrorV1, AppearanceViewFieldV1, AppearanceViewReleaseIdV1, - BackgroundLuminanceRatio, CAM16_VIEW_RELEASE_V1, ColorSignal, ColorimetricFrameId, - ColorimetricFrameReleaseId, ColorimetricTransformReleaseId, HueAngle, HueState, - IEC_SRGB_D65_XYZ_FRAME_V1, LcsOccurrence, MUTATION_SENTINEL_XYZ_FRAME_V1, + BackgroundLuminanceRatio, CAM16_UCS_VIEW_RELEASE_V1, CAM16_VIEW_RELEASE_V1, ColorSignal, + ColorimetricFrameId, ColorimetricFrameReleaseId, ColorimetricTransformReleaseId, HueAngle, + HueState, IEC_SRGB_D65_XYZ_FRAME_V1, LcsOccurrence, MUTATION_SENTINEL_XYZ_FRAME_V1, ModeledTristimulusDerivationV1, NumericDomainError, OKLAB_VIEW_RELEASE_V1, ObserverProfileId, OccurrenceFormationError, ReferenceWhiteId, SurroundProfileId, TristimulusComponentV1, TristimulusDomainErrorV1, TristimulusSample, TristimulusScale, derive_modeled_tristimulus_v1, }; -use crate::spaces::cam16::{ForwardCacheGuard, forward, forward_correlates_v1}; +use crate::spaces::cam16::{ForwardCacheGuard, forward, forward_correlates_v1, ucs_j, ucs_m}; use crate::spaces::oklab::{srgb_linear_to_oklab, xyz_d65_to_oklab_v1}; use crate::spaces::srgb::{D65_WHITE, srgb_linear_from_srgb8, srgb_to_xyz}; use crate::spaces::vc::ViewingConditions; @@ -385,6 +385,15 @@ fn appearance_state_is_one_way_views_of_the_same_occurrence_with_separate_releas assert_eq!(state.occurrence(), occurrence); assert_eq!(state.oklab().release(), OKLAB_VIEW_RELEASE_V1); assert_eq!(state.cam16().release(), CAM16_VIEW_RELEASE_V1); + assert_eq!( + state.cam16_ucs().unwrap().release(), + CAM16_UCS_VIEW_RELEASE_V1, + ); + assert_eq!( + state.occurrence().sample().frame().observer(), + ObserverProfileId::Cie1931TwoDegreeV1, + ); + assert_eq!(state.occurrence().context(), occurrence.context()); let direct = xyz_d65_to_oklab_v1(occurrence.sample().xyz()); assert_eq!( @@ -447,6 +456,11 @@ fn context_changes_only_the_contextual_cam16_view() { assert_eq!(average.oklab(), dim.oklab()); assert_ne!(average.cam16().j().to_bits(), dim.cam16().j().to_bits()); assert_ne!(average.cam16().m().to_bits(), dim.cam16().m().to_bits()); + assert_ne!( + average.cam16_ucs().unwrap().j_prime().to_bits(), + dim.cam16_ucs().unwrap().j_prime().to_bits(), + ); + assert_ne!(average.cam16_ucs().unwrap(), dim.cam16_ucs().unwrap()); assert_ne!(average.occurrence(), dim.occurrence()); } @@ -475,6 +489,29 @@ fn every_registered_surround_maps_to_its_exact_cam16_kernel_tuple() { panic!("chromatic fixture must have hue under {surround:?}"); }; assert_eq!(actual_hue.degrees().to_bits(), expected.h.to_bits()); + + let actual_ucs = AppearanceState::derive_v1(occurrence) + .unwrap() + .cam16_ucs() + .unwrap(); + let expected_j_prime = ucs_j(expected.j); + let expected_m_prime = ucs_m(expected.m); + let expected_radians = expected.h.to_radians(); + assert_eq!( + [ + actual_ucs.j_prime(), + actual_ucs.a_prime(), + actual_ucs.b_prime(), + ] + .map(f64::to_bits), + [ + expected_j_prime, + expected_m_prime * expected_radians.cos(), + expected_m_prime * expected_radians.sin(), + ] + .map(f64::to_bits), + "CAM16-UCS must be assembled from the same CAM16 view under {surround:?}", + ); } } @@ -496,6 +533,11 @@ fn exact_zero_coordinate_has_no_invented_hue() { assert_eq!(state.cam16().m().to_bits(), 0); assert_eq!(state.cam16().s().to_bits(), 0); assert_eq!(state.cam16().hue(), HueState::UndefinedExact); + let ucs = state.cam16_ucs().unwrap(); + assert_eq!( + [ucs.j_prime(), ucs.a_prime(), ucs.b_prime()].map(f64::to_bits), + [0; 3], + ); } #[test] @@ -581,6 +623,20 @@ fn cam16_release_pins_a_full_external_correlate_vector() { "CAM16 {name} drifted: actual={actual} expected={expected}", ); } + + // The same independent reference vector projected through Li et al. 2017 + // CAM16-UCS. These are coordinates of this occurrence, not a distance claim. + let ucs = state.cam16_ucs().unwrap(); + for (name, actual, expected, tolerance) in [ + ("J'", ucs.j_prime(), 36.503_620_495_334_07, 0.02), + ("a'", ucs.a_prime(), 10.042_750_480_031_993, 0.1), + ("b'", ucs.b_prime(), -43.950_878_225_240_46, 0.1), + ] { + assert!( + (actual - expected).abs() < tolerance, + "CAM16-UCS {name} drifted: actual={actual} expected={expected}", + ); + } } #[test] @@ -591,7 +647,10 @@ fn full_appearance_state_derivation_is_allocation_free() { crate::test_support::measured_allocations(|| AppearanceState::derive_v1(occurrence)); assert_eq!(allocations, 0); - assert!(derived.is_ok()); + let derived = derived.unwrap(); + let (ucs, ucs_allocations) = crate::test_support::measured_allocations(|| derived.cam16_ucs()); + assert_eq!(ucs_allocations, 0); + assert!(ucs.is_ok()); } #[test] @@ -664,4 +723,66 @@ fn appearance_state_bypasses_active_xyz_only_cache_for_each_context_without_allo }; assert_eq!(dim_hue.degrees().to_bits(), expected_dim.h.to_bits()); assert_eq!(dark_hue.degrees().to_bits(), expected_dark.h.to_bits()); + + let expected_ucs = |expected: crate::spaces::cam16::Cam16CorrelatesV1| { + let m_prime = ucs_m(expected.m); + let radians = expected.h.to_radians(); + [ + ucs_j(expected.j), + m_prime * radians.cos(), + m_prime * radians.sin(), + ] + .map(f64::to_bits) + }; + let actual_ucs = |view: crate::lcs_occurrence::Cam16UcsViewV1| { + [view.j_prime(), view.a_prime(), view.b_prime()].map(f64::to_bits) + }; + assert_eq!( + actual_ucs( + AppearanceState::derive_v1(dim_occurrence) + .unwrap() + .cam16_ucs() + .unwrap(), + ), + expected_ucs(expected_dim), + ); + assert_eq!( + actual_ucs( + AppearanceState::derive_v1(dark_occurrence) + .unwrap() + .cam16_ucs() + .unwrap(), + ), + expected_ucs(expected_dark), + ); +} + +#[test] +fn occurrence_views_have_no_ambient_preset_or_client_semantic_route() { + let source = include_str!("lcs_occurrence.rs"); + for forbidden in [ + "ViewingConditions::srgb()", + "ViewingConditions::dim_surround()", + "ForwardCacheGuard", + "PairFill", + "Glow", + "LabUI", + "Compatibility", + "Legacy", + ] { + assert!( + !source.contains(forbidden), + "occurrence/view foundation must not contain `{forbidden}`", + ); + } + for required in [ + "ViewingConditions::from_semantic_inputs_v1", + "forward_correlates_v1(occurrence.sample().xyz(), &vc)", + "derive_cam16_ucs_view_v1(self.cam16)", + ] { + assert!( + source.contains(required), + "explicit derived-view route must retain `{required}`", + ); + } } diff --git a/crates/labcolors-core/src/release_registry.rs b/crates/labcolors-core/src/release_registry.rs index e101b737..9c3b4a24 100644 --- a/crates/labcolors-core/src/release_registry.rs +++ b/crates/labcolors-core/src/release_registry.rs @@ -9,8 +9,9 @@ use crate::lcs_occurrence::{ ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1, AdmittedSrgb8TristimulusBindingV1, - AppearanceContextSchemaReleaseId, CAM16_VIEW_RELEASE_V1, Cam16ViewReleaseId, - ColorimetricFrameId, IEC_SRGB_D65_XYZ_FRAME_V1, OKLAB_VIEW_RELEASE_V1, OklabViewReleaseId, + AppearanceContextSchemaReleaseId, CAM16_UCS_VIEW_RELEASE_V1, CAM16_VIEW_RELEASE_V1, + Cam16UcsViewReleaseId, Cam16ViewReleaseId, ColorimetricFrameId, IEC_SRGB_D65_XYZ_FRAME_V1, + OKLAB_VIEW_RELEASE_V1, OklabViewReleaseId, }; use crate::output_projection::{ CSS_COLOR_4_OKLCH_D65_FROM_MODELED_SRGB8_SOLID_V1, CssOklchHueSerializationReleaseIdV1, @@ -24,9 +25,11 @@ pub(crate) const RELEASE_REGISTRY_SCHEMA_VERSION_V1: u16 = 1; // key. The byte order is documented by `RegisteredReleaseDescriptorV1` below. const RELEASE_REGISTRY_CANONICAL_BYTES_V1: &[u8] = concat!( "labcolors.release-registry.canonical-binary.v1\0", - "\0\x01\0\x05", + "\0\x01\0\x06", "\x01\x01\0\x26cam16-li-et-al-2017-cie-248-forward-v1", "\x01\x02\x01\x01\x01\x02\x02\x02\0\0\0\0\0\0\0", + "\x01\x01\0\x26cam16-ucs-li-et-al-2017-rectangular-v1", + "\x01\x02\x01\x01\x04\x05\x05\x05\x03\x01\0\0\0\0\0", "\x01\x01\0\x24oklab-ottosson-2021-01-25-xyz-d65-v1", "\x01\x01\0\x01\x01\x01\x01\x01\0\0\0\0\0\0\0", "\x01\x01\0\x27polar-from-ottosson-2021-01-25-oklab-v1", @@ -37,7 +40,7 @@ const RELEASE_REGISTRY_CANONICAL_BYTES_V1: &[u8] = concat!( ) .as_bytes(); -const RELEASE_REGISTRY_FNV1A32_V1: u32 = 1_293_630_307; +const RELEASE_REGISTRY_FNV1A32_V1: u32 = 540_606_852; /// The disjoint kinds whose availability is reported by this registry. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -76,6 +79,7 @@ impl ReleaseRegistryAvailabilityV1 { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) enum RegisteredColorReleaseIdV1 { Cam16View(Cam16ViewReleaseId), + Cam16UcsView(Cam16UcsViewReleaseId), OklabView(OklabViewReleaseId), OklchView(OklchViewReleaseId), CssColor4OklchD65FromModeledSrgb8Solid(OutputProjectionReleaseIdV1), @@ -84,9 +88,10 @@ pub(crate) enum RegisteredColorReleaseIdV1 { impl RegisteredColorReleaseIdV1 { pub(crate) const fn class(self) -> ReleaseRegistryClassV1 { match self { - Self::Cam16View(_) | Self::OklabView(_) | Self::OklchView(_) => { - ReleaseRegistryClassV1::AppearanceView - } + Self::Cam16View(_) + | Self::Cam16UcsView(_) + | Self::OklabView(_) + | Self::OklchView(_) => ReleaseRegistryClassV1::AppearanceView, Self::CssColor4OklchD65FromModeledSrgb8Solid(_) => { ReleaseRegistryClassV1::OutputProjection } @@ -99,6 +104,9 @@ impl RegisteredColorReleaseIdV1 { Self::Cam16View(Cam16ViewReleaseId::LiEtAl2017Cie248ForwardV1) => { "cam16-li-et-al-2017-cie-248-forward-v1" } + Self::Cam16UcsView(Cam16UcsViewReleaseId::LiEtAl2017Cam16UcsV1) => { + "cam16-ucs-li-et-al-2017-rectangular-v1" + } Self::OklabView(OklabViewReleaseId::Ottosson20210125XyzD65V1) => { "oklab-ottosson-2021-01-25-xyz-d65-v1" } @@ -171,6 +179,7 @@ pub(crate) enum RegistryAdmittedDomainV1 { FiniteNonNegativeXyzStimulusV1, FiniteOklabRectangularViewV1, ModeledIec61966Srgb8OccurrenceV1, + FiniteCam16ViewV1, } impl RegistryAdmittedDomainV1 { @@ -179,6 +188,7 @@ impl RegistryAdmittedDomainV1 { Self::FiniteNonNegativeXyzStimulusV1 => 1, Self::FiniteOklabRectangularViewV1 => 2, Self::ModeledIec61966Srgb8OccurrenceV1 => 3, + Self::FiniteCam16ViewV1 => 4, } } } @@ -190,6 +200,7 @@ pub(crate) enum RegistryCoordinateUnitsV1 { Cam16CorrelatesUnitlessHueDegreesV1, OklchCoordinatesUnitlessHueDegreesV1, CssColor4OklchPercentLightnessNumericChromaHueDegreesV1, + Cam16UcsJPrimeAPrimeBPrimeUnitlessV1, } impl RegistryCoordinateUnitsV1 { @@ -199,6 +210,7 @@ impl RegistryCoordinateUnitsV1 { Self::Cam16CorrelatesUnitlessHueDegreesV1 => 2, Self::OklchCoordinatesUnitlessHueDegreesV1 => 3, Self::CssColor4OklchPercentLightnessNumericChromaHueDegreesV1 => 4, + Self::Cam16UcsJPrimeAPrimeBPrimeUnitlessV1 => 5, } } } @@ -210,6 +222,7 @@ pub(crate) enum RegistryAchromaticLawV1 { HueUndefinedExactlyWhenCam16MIsZeroV1, HueUndefinedExactlyWhenOklabAAndBAreZeroV1, ExactSourceGreyOrRectangularOriginSerializesHueZeroV1, + RectangularChromaOriginExactlyWhenCam16MIsZeroV1, } impl RegistryAchromaticLawV1 { @@ -219,6 +232,7 @@ impl RegistryAchromaticLawV1 { Self::HueUndefinedExactlyWhenCam16MIsZeroV1 => 2, Self::HueUndefinedExactlyWhenOklabAAndBAreZeroV1 => 3, Self::ExactSourceGreyOrRectangularOriginSerializesHueZeroV1 => 4, + Self::RectangularChromaOriginExactlyWhenCam16MIsZeroV1 => 5, } } } @@ -230,6 +244,7 @@ pub(crate) enum RegistryReferenceIdentityV1 { LiEtAl2017Cie248Cam16ForwardV1, Ottosson20210125OklabPolarV1, CssColor4OklchD65V1, + LiEtAl2017Cam16UcsCoordinatesV1, } impl RegistryReferenceIdentityV1 { @@ -239,6 +254,7 @@ impl RegistryReferenceIdentityV1 { Self::LiEtAl2017Cie248Cam16ForwardV1 => 2, Self::Ottosson20210125OklabPolarV1 => 3, Self::CssColor4OklchD65V1 => 4, + Self::LiEtAl2017Cam16UcsCoordinatesV1 => 5, } } } @@ -297,18 +313,26 @@ impl OutputProjectionDependencyGraphV1 { pub(crate) enum ReleaseDependencyGraphV1 { DirectV1, OklchPolarFromOklabV1(OklabViewReleaseId), + Cam16UcsRectangularFromCam16V1(Cam16ViewReleaseId), CssColor4OklchD65V1(OutputProjectionDependencyGraphV1), } impl ReleaseDependencyGraphV1 { - /// Fixed canonical fields: - /// `[graph, source-binding, Oklab, Oklch, number, hue-policy, gamut-policy]`. + /// Fixed seven-byte tagged union. The graph tag determines its payload: + /// + /// - direct: six zero bytes; + /// - Oklch: `[0, Oklab, 0, 0, 0, 0]`; + /// - CSS output: `[source-binding, Oklab, Oklch, number, hue, gamut]`; + /// - CAM16-UCS: `[CAM16, 0, 0, 0, 0, 0]`. const fn canonical_fields(self) -> [u8; 7] { match self { Self::DirectV1 => [0; 7], Self::OklchPolarFromOklabV1(OklabViewReleaseId::Ottosson20210125XyzD65V1) => { [1, 0, 1, 0, 0, 0, 0] } + Self::Cam16UcsRectangularFromCam16V1( + Cam16ViewReleaseId::LiEtAl2017Cie248ForwardV1, + ) => [3, 1, 0, 0, 0, 0, 0], Self::CssColor4OklchD65V1(graph) => [ 2, match graph.modeled_source_binding { @@ -420,6 +444,19 @@ const CAM16_VIEW_DESCRIPTOR_V1: RegisteredReleaseDescriptorV1 = RegisteredReleas dependencies: ReleaseDependencyGraphV1::DirectV1, }; +const CAM16_UCS_VIEW_DESCRIPTOR_V1: RegisteredReleaseDescriptorV1 = RegisteredReleaseDescriptorV1 { + release: RegisteredColorReleaseIdV1::Cam16UcsView(CAM16_UCS_VIEW_RELEASE_V1), + context_requirement: ReleaseContextRequirementV1::ConsumesAppearanceContextV1( + AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1, + ), + admitted_frame: RegistryAdmittedFrameV1::Cie1931TwoDegreeXyzIecD65RelativeY1V1, + admitted_domain: RegistryAdmittedDomainV1::FiniteCam16ViewV1, + coordinate_units: RegistryCoordinateUnitsV1::Cam16UcsJPrimeAPrimeBPrimeUnitlessV1, + achromatic_law: RegistryAchromaticLawV1::RectangularChromaOriginExactlyWhenCam16MIsZeroV1, + reference_identity: RegistryReferenceIdentityV1::LiEtAl2017Cam16UcsCoordinatesV1, + dependencies: ReleaseDependencyGraphV1::Cam16UcsRectangularFromCam16V1(CAM16_VIEW_RELEASE_V1), +}; + const OKLAB_VIEW_DESCRIPTOR_V1: RegisteredReleaseDescriptorV1 = RegisteredReleaseDescriptorV1 { release: RegisteredColorReleaseIdV1::OklabView(OKLAB_VIEW_RELEASE_V1), context_requirement: ReleaseContextRequirementV1::NoAppearanceContextConsumptionV1, @@ -503,8 +540,9 @@ impl ReleaseRegistryRecordV1 { // Canonical order is class tag, then release-key bytes. An unavailable class // has exactly one keyless and descriptor-less row. -const RELEASE_REGISTRY_RECORDS_V1: [ReleaseRegistryRecordV1; 5] = [ +const RELEASE_REGISTRY_RECORDS_V1: [ReleaseRegistryRecordV1; 6] = [ ReleaseRegistryRecordV1::Registered(CAM16_VIEW_DESCRIPTOR_V1), + ReleaseRegistryRecordV1::Registered(CAM16_UCS_VIEW_DESCRIPTOR_V1), ReleaseRegistryRecordV1::Registered(OKLAB_VIEW_DESCRIPTOR_V1), ReleaseRegistryRecordV1::Registered(OKLCH_VIEW_DESCRIPTOR_V1), ReleaseRegistryRecordV1::DifferenceCalibrationUnavailable, diff --git a/crates/labcolors-core/src/release_registry_tests.rs b/crates/labcolors-core/src/release_registry_tests.rs index 1f1c40d0..a89329d6 100644 --- a/crates/labcolors-core/src/release_registry_tests.rs +++ b/crates/labcolors-core/src/release_registry_tests.rs @@ -1,6 +1,7 @@ use crate::lcs_occurrence::{ - ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1, AppearanceContextSchemaReleaseId, CAM16_VIEW_RELEASE_V1, - IEC_SRGB_D65_XYZ_FRAME_V1, OKLAB_VIEW_RELEASE_V1, + ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1, AppearanceContextSchemaReleaseId, + CAM16_UCS_VIEW_RELEASE_V1, CAM16_VIEW_RELEASE_V1, IEC_SRGB_D65_XYZ_FRAME_V1, + OKLAB_VIEW_RELEASE_V1, }; use crate::output_projection::{ CSS_COLOR_4_OKLCH_D65_FROM_MODELED_SRGB8_SOLID_V1, CssOklchHueSerializationReleaseIdV1, @@ -26,7 +27,7 @@ fn descriptor(release: RegisteredColorReleaseIdV1) -> RegisteredReleaseDescripto } #[test] -fn registry_contains_only_the_four_implemented_releases() { +fn registry_contains_only_the_five_implemented_releases() { let releases: Vec<_> = release_registry_records_v1() .iter() .filter_map(|record| record.release()) @@ -35,6 +36,7 @@ fn registry_contains_only_the_four_implemented_releases() { releases, [ RegisteredColorReleaseIdV1::Cam16View(CAM16_VIEW_RELEASE_V1), + RegisteredColorReleaseIdV1::Cam16UcsView(CAM16_UCS_VIEW_RELEASE_V1), RegisteredColorReleaseIdV1::OklabView(OKLAB_VIEW_RELEASE_V1), RegisteredColorReleaseIdV1::OklchView(OKLCH_VIEW_RELEASE_V1), RegisteredColorReleaseIdV1::CssColor4OklchD65FromModeledSrgb8Solid( @@ -101,6 +103,44 @@ fn appearance_descriptors_pin_only_code_owned_domain_units_hue_and_reference_fac ); assert_eq!(cam16.dependencies(), ReleaseDependencyGraphV1::DirectV1); + let cam16_ucs = descriptor(RegisteredColorReleaseIdV1::Cam16UcsView( + CAM16_UCS_VIEW_RELEASE_V1, + )); + assert_eq!( + cam16_ucs.context_requirement(), + ReleaseContextRequirementV1::ConsumesAppearanceContextV1( + AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1, + ), + ); + assert_eq!( + cam16_ucs.context_requirement().schema_release(), + Some(AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1), + ); + assert_eq!( + cam16_ucs.admitted_frame().frame(), + IEC_SRGB_D65_XYZ_FRAME_V1, + ); + assert_eq!( + cam16_ucs.admitted_domain(), + RegistryAdmittedDomainV1::FiniteCam16ViewV1, + ); + assert_eq!( + cam16_ucs.coordinate_units(), + RegistryCoordinateUnitsV1::Cam16UcsJPrimeAPrimeBPrimeUnitlessV1, + ); + assert_eq!( + cam16_ucs.achromatic_law(), + RegistryAchromaticLawV1::RectangularChromaOriginExactlyWhenCam16MIsZeroV1, + ); + assert_eq!( + cam16_ucs.reference_identity(), + RegistryReferenceIdentityV1::LiEtAl2017Cam16UcsCoordinatesV1, + ); + assert_eq!( + cam16_ucs.dependencies(), + ReleaseDependencyGraphV1::Cam16UcsRectangularFromCam16V1(CAM16_VIEW_RELEASE_V1), + ); + let oklab = descriptor(RegisteredColorReleaseIdV1::OklabView(OKLAB_VIEW_RELEASE_V1)); assert_eq!( oklab.context_requirement(), @@ -217,6 +257,7 @@ fn registered_rows_have_stable_exact_release_keys() { keys, [ "cam16-li-et-al-2017-cie-248-forward-v1", + "cam16-ucs-li-et-al-2017-rectangular-v1", "oklab-ottosson-2021-01-25-xyz-d65-v1", "polar-from-ottosson-2021-01-25-oklab-v1", "css-color-4-oklch-d65-from-modeled-iec61966-srgb8-solid-v1", @@ -231,9 +272,11 @@ fn canonical_bytes_pin_schema_rows_descriptors_dependencies_and_order() { release_registry_canonical_bytes_v1(), concat!( "labcolors.release-registry.canonical-binary.v1\0", - "\0\x01\0\x05", + "\0\x01\0\x06", "\x01\x01\0\x26cam16-li-et-al-2017-cie-248-forward-v1", "\x01\x02\x01\x01\x01\x02\x02\x02\0\0\0\0\0\0\0", + "\x01\x01\0\x26cam16-ucs-li-et-al-2017-rectangular-v1", + "\x01\x02\x01\x01\x04\x05\x05\x05\x03\x01\0\0\0\0\0", "\x01\x01\0\x24oklab-ottosson-2021-01-25-xyz-d65-v1", "\x01\x01\0\x01\x01\x01\x01\x01\0\0\0\0\0\0\0", "\x01\x01\0\x27polar-from-ottosson-2021-01-25-oklab-v1", @@ -301,5 +344,5 @@ fn digest_names_its_non_cryptographic_algorithm_and_covers_descriptor_bytes() { digest.value(), crate::fnv1a_32(release_registry_canonical_bytes_v1()), ); - assert_eq!(digest.value(), 1_293_630_307); + assert_eq!(digest.value(), 540_606_852); } diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index 1529800f..9e0702ff 100755 --- a/scripts/verify_point_support_surplus.py +++ b/scripts/verify_point_support_surplus.py @@ -58,7 +58,7 @@ SOURCE_BINDING_LAW = "point-support-rust-whole-file-semantic-cone-v2" SOURCE_BINDING_DOMAIN = b"labcolors.point-support.rust-whole-file-semantic-cone.v2" EXPECTED_SOURCE_CAPSULE_SHA256 = ( - "2cf6589a15d2669aca9f1f5a287841805c0fe7293074530d70a1cb803d235c7d" + "4a762a7fd0a658286288ef651cbaf53940e4676f47c63c970799736650a62e21" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"