Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# See the configuration reference at
# https://github.com/crate-ci/typos/blob/master/docs/reference.md

[default]
extend-ignore-re = ["colour-science"]

# Corrections take the form of a key/value pair. The key is the incorrect word
# and the value is the correct word. If the key and value are the same, the
# word is treated as always correct. If the value is an empty string, the word
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ You can find its changes [documented below](#023-2025-01-20).

This release has an [MSRV][] of 1.82.

### Added

* Support converting between color spaces without chromatic adaptation, thereby representing the same absolute color in the destination color space as in the source color space. ([#139][] by [@tomcur][])

**Note to `ColorSpace` implementers:** the `WHITE_POINT` associated constant is added to `ColorSpace`, defaulting to D65.
Implementations with a non-D65 white point should set this constant to get correct default absolute conversion behavior.
* Support manual chromatic adaptation of colors between arbitrary white point chromaticities. ([#139][] by [@tomcur][])

## [0.2.3][] (2025-01-20)

This release has an [MSRV][] of 1.82.
Expand Down Expand Up @@ -127,6 +135,7 @@ This is the initial release.
[#130]: https://github.com/linebender/color/pull/130
[#135]: https://github.com/linebender/color/pull/135
[#136]: https://github.com/linebender/color/pull/136
[#139]: https://github.com/linebender/color/pull/139

[Unreleased]: https://github.com/linebender/color/compare/v0.2.3...HEAD
[0.2.3]: https://github.com/linebender/color/releases/tag/v0.2.3
Expand Down
2 changes: 1 addition & 1 deletion color/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Simplifications include:
* Only handling 3-component color spaces (plus optional alpha).
* Choosing a fixed, curated set of color spaces for dynamic color types.
* Choosing linear sRGB as the central color space.
* Keeping white point implicit.
* Keeping white point implicit in the general conversion operations.

A number of other tasks are out of scope for this crate:
* Print color spaces (CMYK).
Expand Down
127 changes: 127 additions & 0 deletions color/src/chromaticity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2024 the Color Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{matdiagmatmul, matmatmul, matvecmul};

/// CIE `xy` chromaticity, specifying a color in the XYZ color space, but not its luminosity.
///
/// An absolute color can be specified by adding a luminosity coordinate `Y` as in `xyY`. An `XYZ`
/// color can be calculated from `xyY` as follows.
///
/// ```text
/// X = Y/y * x
/// Y = Y
/// Z = Y/y * (1 - x - y)
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Chromaticity {
/// The x-coordinate of the CIE `xy` chromaticity.
pub x: f32,

/// The y-coordinate of the CIE `xy` chromaticity.
pub y: f32,
}

impl Chromaticity {
/// The CIE D65 white point under the standard 2° observer.
///
/// This is a common white point for color spaces targeting monitors.
///
/// The white point's chromaticities are truncated to four digits here, as specified by the
/// CSS Color 4 specification, and following most color spaces using this white point.
pub const D65: Self = Self {
x: 0.3127,
y: 0.3290,
};

/// The CIE D50 white point under the standard 2° observer.
///
/// The white point's chromaticities are truncated to four digits here, as specified by the
/// CSS Color 4 specification, and following most color spaces using this white point.
pub const D50: Self = Self {
x: 0.3457,
y: 0.3585,
};

/// The [ACES white point][aceswp].
///
/// This is the reference white of [ACEScg](crate::AcesCg) and [ACES2065-1](crate::Aces2065_1).
/// The white point is near the D60 white point under the standard 2° observer.
///
/// [aceswp]: https://docs.acescentral.com/tb/white-point
pub const ACES: Self = Self {
x: 0.32168,
y: 0.33767,
};

/// Convert the `xy` chromaticities to XYZ, assuming `xyY` with `Y=1`.
pub(crate) const fn to_xyz(self) -> [f32; 3] {
let y_recip = 1. / self.y;
[self.x * y_recip, 1., (1. - self.x - self.y) * y_recip]
}

/// Calculate the 3x3 linear Bradford chromatic adaptation matrix from linear sRGB space.
///
/// This calculates the matrix going from a reference white of `self` to a reference white of
/// `to`.
pub(crate) const fn linear_srgb_chromatic_adaptation_matrix(self, to: Self) -> [[f32; 3]; 3] {
let bradford_source = matvecmul(&Self::XYZ_TO_BRADFORD, self.to_xyz());
let bradford_dest = matvecmul(&Self::XYZ_TO_BRADFORD, to.to_xyz());

matmatmul(
&matdiagmatmul(
&Self::BRADFORD_TO_SRGB,
[
bradford_dest[0] / bradford_source[0],
bradford_dest[1] / bradford_source[1],
bradford_dest[2] / bradford_source[2],
],
),
&Self::SRGB_TO_BRADFORD,
)
}

/// `XYZ_to_Bradford * lin_sRGB_to_XYZ`
const SRGB_TO_BRADFORD: [[f32; 3]; 3] = [
[
1_298_421_353. / 3_072_037_500.,
172_510_403. / 351_090_000.,
32_024_671. / 1_170_300_000.,
],
[
85_542_113. / 1_536_018_750.,
7_089_448_151. / 7_372_890_000.,
244_246_729. / 10_532_700_000.,
],
[
131_355_661. / 6_144_075_000.,
71_798_777. / 819_210_000.,
3_443_292_119. / 3_510_900_000.,
],
];

/// `XYZ_to_lin_sRGB * Bradford_to_XYZ`
const BRADFORD_TO_SRGB: [[f32; 3]; 3] = [
[
3_597_831_250_055_000. / 1_417_335_035_684_489.,
-1_833_298_161_702_000. / 1_417_335_035_684_489.,
-57_038_163_791_000. / 1_417_335_035_684_489.,
],
[
-4_593_417_841_453_000. / 31_461_687_363_220_151.,
35_130_825_086_032_200. / 31_461_687_363_220_151.,
-702_492_905_752_400. / 31_461_687_363_220_151.,
],
[
-191_861_334_350_000. / 4_536_975_728_019_583.,
-324_802_409_790_000. / 4_536_975_728_019_583.,
4_639_090_845_380_000. / 4_536_975_728_019_583.,
],
];

const XYZ_TO_BRADFORD: [[f32; 3]; 3] = [
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
];
}
Loading