Skip to content

Commit

Permalink
Merge pull request #412 from Kirk-Fox/master
Browse files Browse the repository at this point in the history
Add DCI-P3, DCI-P3+, and Display P3
  • Loading branch information
Ogeon authored Aug 17, 2024
2 parents fab4412 + 068eccc commit 5476be6
Show file tree
Hide file tree
Showing 3 changed files with 369 additions and 0 deletions.
2 changes: 2 additions & 0 deletions palette/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
pub use self::adobe::AdobeRgb;
pub use self::gamma::{F2p2, Gamma};
pub use self::linear::Linear;
pub use self::p3::{DciP3, DciP3Plus, DisplayP3};
pub use self::rec_standards::{Rec2020, Rec709};
pub use self::srgb::Srgb;

pub mod adobe;
pub mod gamma;
pub mod linear;
pub mod p3;
pub mod rec_standards;
pub mod srgb;

Expand Down
314 changes: 314 additions & 0 deletions palette/src/encoding/p3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
//! The P3 color space(s) and standards.

use core::marker::PhantomData;

use crate::{
encoding::{FromLinear, IntoLinear, Srgb},
luma::LumaStandard,
num::{Powf, Real},
rgb::{Primaries, RgbSpace, RgbStandard},
white_point::{Any, WhitePoint, D65},
Mat3, Xyz, Yxy,
};

/// The theatrical DCI-P3 standard.
///
/// This standard uses a gamma 2.6 transfer function and a white point of ~6300K that
/// matches the color of xenon bulbs used in theater projectors
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DciP3;

impl<T: Real> Primaries<T> for DciP3 {
fn red() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.680),
T::from_f64(0.320),
T::from_f64(0.209492),
)
}
fn green() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.265),
T::from_f64(0.690),
T::from_f64(0.721595),
)
}
fn blue() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.150),
T::from_f64(0.060),
T::from_f64(0.068913),
)
}
}

impl<T: Real> WhitePoint<T> for DciP3 {
fn get_xyz() -> Xyz<Any, T> {
Xyz::new(
T::from_f64(0.314 / 0.351),
T::from_f64(1.0),
T::from_f64(0.335 / 0.351),
)
}
}

impl RgbSpace for DciP3 {
type Primaries = DciP3;
type WhitePoint = DciP3;

#[rustfmt::skip]
#[inline(always)]
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
0.4451698, 0.2771344, 0.1722827,
0.2094917, 0.7215953, 0.0689131,
0.0000000, 0.0470606, 0.9073554,
])
}

#[rustfmt::skip]
#[inline(always)]
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
2.7253940, -1.0180030, -0.4401632,
-0.7951680, 1.6897321, 0.0226472,
0.0412419, -0.0876390, 1.1009294,
])
}
}

impl RgbStandard for DciP3 {
type Space = DciP3;
type TransferFn = P3Gamma;
}

impl LumaStandard for DciP3 {
type WhitePoint = DciP3;
type TransferFn = P3Gamma;
}

/// The Canon DCI-P3+ color space and standard.
///
/// This standard has the same white point as [`DciP3`], but has a much wider gamut and
/// no standardized transfer function (left to user preference). The generic `F` in
/// this struct represents the chosen transfer function.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DciP3Plus<F>(PhantomData<F>);

impl<T: Real, F> Primaries<T> for DciP3Plus<F> {
fn red() -> Yxy<Any, T> {
Yxy::new(T::from_f64(0.740), T::from_f64(0.270), T::from_f64(0.203986))
}
fn green() -> Yxy<Any, T> {
Yxy::new(T::from_f64(0.220), T::from_f64(0.780), T::from_f64(0.882591))
}
fn blue() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.090),
T::from_f64(-0.090),
T::from_f64(-0.086577),
)
}
}

impl<F> RgbSpace for DciP3Plus<F> {
type Primaries = DciP3Plus<F>;
type WhitePoint = DciP3;

#[rustfmt::skip]
#[inline(always)]
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
0.5590736, 0.2489359, 0.0865774,
0.2039863, 0.8825911, -0.0865774,
-0.0075550, 0.0000000, 0.9619710,
])
}

#[rustfmt::skip]
#[inline(always)]
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
1.9904035, -0.5613959, -0.2296619,
-0.4584928, 1.2623460, 0.1548755,
0.0156321, -0.0044090, 1.0377287,
])
}
}

impl<F> RgbStandard for DciP3Plus<F> {
type Space = DciP3Plus<F>;
type TransferFn = F;
}

impl<F> LumaStandard for DciP3Plus<F> {
type WhitePoint = DciP3;
type TransferFn = F;
}

/// A gamma 2.6 transfer function used by some P3 variants
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct P3Gamma;

impl<T> IntoLinear<T, T> for P3Gamma
where
T: Real + Powf,
{
#[inline]
fn into_linear(encoded: T) -> T {
encoded.powf(T::from_f64(2.6))
}
}

impl<T> FromLinear<T, T> for P3Gamma
where
T: Real + Powf,
{
#[inline]
fn from_linear(linear: T) -> T {
linear.powf(T::from_f64(1.0 / 2.6))
}
}

/// The Display P3 standard.
///
/// This standard uses the same primaries as [`DciP3`] but with a [`D65`] white point
/// and the [`Srgb`] transfer function.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DisplayP3;

impl<T: Real> Primaries<T> for DisplayP3 {
fn red() -> Yxy<Any, T> {
Yxy::new(T::from_f64(0.680), T::from_f64(0.320), T::from_f64(0.22900))
}
fn green() -> Yxy<Any, T> {
Yxy::new(T::from_f64(0.265), T::from_f64(0.690), T::from_f64(0.69173))
}
fn blue() -> Yxy<Any, T> {
Yxy::new(T::from_f64(0.150), T::from_f64(0.060), T::from_f64(0.07927))
}
}
impl RgbSpace for DisplayP3 {
type Primaries = DisplayP3;
type WhitePoint = D65;

#[rustfmt::skip]
#[inline(always)]
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
0.4866327, 0.2656632, 0.1981742,
0.2290036, 0.6917267, 0.0792697,
0.0000000, 0.0451126, 1.0437174,
])
}

#[rustfmt::skip]
#[inline(always)]
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
Some([
2.4931808, -0.9312655, -0.4026597,
-0.8295031, 1.7626941, 0.0236251,
0.0358536, -0.0761890, 0.9570926,
])
}
}

impl RgbStandard for DisplayP3 {
type Space = DisplayP3;
type TransferFn = Srgb;
}

impl LumaStandard for DisplayP3 {
type WhitePoint = D65;
type TransferFn = Srgb;
}

#[cfg(test)]
mod test {
#[cfg(feature = "approx")]
mod conversion {
use crate::{
convert::IntoColorUnclamped,
encoding::p3::{DciP3, DciP3Plus, DisplayP3, P3Gamma},
matrix::{matrix_inverse, rgb_to_xyz_matrix},
rgb::{Primaries, RgbSpace},
white_point::{Any, WhitePoint, D65},
Xyz,
};

#[test]
fn rgb_to_xyz_display_p3() {
let dynamic = rgb_to_xyz_matrix::<DisplayP3, f64>();
let constant = DisplayP3::rgb_to_xyz_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn xyz_to_rgb_display_p3() {
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DisplayP3, f64>());
let constant = DisplayP3::xyz_to_rgb_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn rgb_to_xyz_dci_p3() {
let dynamic = rgb_to_xyz_matrix::<DciP3, f64>();
let constant = DciP3::rgb_to_xyz_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn xyz_to_rgb_dci_p3() {
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DciP3, f64>());
let constant = DciP3::xyz_to_rgb_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn rgb_to_xyz_dci_p3_plus() {
let dynamic = rgb_to_xyz_matrix::<DciP3Plus<P3Gamma>, f64>();
let constant = DciP3Plus::<P3Gamma>::rgb_to_xyz_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn xyz_to_rgb_dci_p3_plus() {
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DciP3Plus<P3Gamma>, f64>());
let constant = DciP3Plus::<P3Gamma>::xyz_to_rgb_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn primaries_display_p3() {
let red: Xyz<Any, f64> = DisplayP3::red().into_color_unclamped();
let green: Xyz<Any, f64> = DisplayP3::green().into_color_unclamped();
let blue: Xyz<Any, f64> = DisplayP3::blue().into_color_unclamped();
// Compare sum of primaries to white point.
assert_relative_eq!(red + green + blue, D65::get_xyz(), epsilon = 0.00001)
}

#[test]
fn primaries_dci_p3() {
let red: Xyz<Any, f64> = DciP3::red().into_color_unclamped();
let green: Xyz<Any, f64> = DciP3::green().into_color_unclamped();
let blue: Xyz<Any, f64> = DciP3::blue().into_color_unclamped();
// Compare sum of primaries to white point.
assert_relative_eq!(red + green + blue, DciP3::get_xyz(), epsilon = 0.00001)
}

#[test]
fn primaries_dci_p3_plus() {
let red: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::red().into_color_unclamped();
let green: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::green().into_color_unclamped();
let blue: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::blue().into_color_unclamped();
// Compare sum of primaries to white point.
assert_relative_eq!(red + green + blue, DciP3::get_xyz(), epsilon = 0.00001)
}
}
}
53 changes: 53 additions & 0 deletions palette/src/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,59 @@ pub type AdobeRgb<T = f32> = Rgb<encoding::AdobeRgb, T>;
/// create a value and use it.
pub type AdobeRgba<T = f32> = Rgba<encoding::AdobeRgb, T>;

/// Non-linear DCI-P3, an RGB format used for digital movie distribution.
///
/// This is an RGB standard with a color gamut wider than that of [`Srgb`] and a
/// white point similar to that of a film projector's xenon bulb.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type DciP3<T = f32> = Rgb<encoding::DciP3, T>;

/// Non-linear Canon DCI-P3+, an RGB format with a very wide gamut.
///
/// This is an RGB standard with a color gamut much wider than that of [`Srgb`].
/// It uses the same white point as [`DciP3`], but uses a user-defined transfer
/// function, represented here by the generic `F`.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type DciP3Plus<F, T = f32> = Rgb<encoding::DciP3Plus<F>, T>;

/// Non-linear Display P3, an RGB format used developed by Apple for wide-gamut
/// displays.
///
/// This is an RGB standard with the same white point and transfer function as
/// [`Srgb`], but with a wider color gamut.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type DisplayP3<T = f32> = Rgb<encoding::DisplayP3, T>;

/// Linear DCI-P3.
///
/// You probably want [`DciP3`] if you are looking for an input or output format.
/// This is the linear version of DCI-P3, which is what you would usually convert
/// to before working with the color.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type LinDciP3<T = f32> = Rgb<Linear<encoding::DciP3>, T>;

/// Linear DCI-P3+.
///
/// You probably want [`DciP3Plus`] if you are looking for an input or output format.
/// This is the linear version of DCI-P3+, which is what you would usually convert
/// to before working with the color.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type LinDciP3Plus<F, T = f32> = Rgb<Linear<encoding::DciP3Plus<F>>, T>;

/// Linear Display P3.
///
/// You probably want [`DisplayP3`] if you are looking for an input or output format.
/// This is the linear version of Display P3, which is what you would usually convert
/// to before working with the color.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type LinDisplayP3<T = f32> = Rgb<Linear<encoding::DisplayP3>, T>;

/// Linear Adobe RGB.
///
/// You probably want [`AdobeRgb`] if you are looking for an input or output format.
Expand Down

0 comments on commit 5476be6

Please sign in to comment.