diff --git a/palette/src/encoding.rs b/palette/src/encoding.rs index d7aee29d..21d922ae 100644 --- a/palette/src/encoding.rs +++ b/palette/src/encoding.rs @@ -9,6 +9,7 @@ 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::prophoto::ProPhotoRgb; pub use self::rec_standards::{Rec2020, Rec709}; pub use self::srgb::Srgb; @@ -16,6 +17,7 @@ pub mod adobe; pub mod gamma; pub mod linear; pub mod p3; +pub mod prophoto; pub mod rec_standards; pub mod srgb; diff --git a/palette/src/encoding/prophoto.rs b/palette/src/encoding/prophoto.rs new file mode 100644 index 00000000..b05159b0 --- /dev/null +++ b/palette/src/encoding/prophoto.rs @@ -0,0 +1,150 @@ +//! The ProPhoto RGB standard. + +use crate::{ + bool_mask::LazySelect, + encoding::{FromLinear, IntoLinear}, + luma::LumaStandard, + num::{Arithmetics, PartialCmp, Powf, Real}, + rgb::{Primaries, RgbSpace, RgbStandard}, + white_point::{Any, D50}, + Mat3, Yxy, +}; + +/// The ProPhoto RGB standard and color space with gamma 2.2 transfer function. +/// +/// About 13% of the colors in this space are "[impossible colors](https://en.wikipedia.org/wiki/Impossible_color)" +/// meaning that they model cone responses that are, in practice, impossible to +/// achieve. +/// +/// # As transfer function +/// +/// `ProPhotoRgb` will not use any kind of approximation when converting from `T` to +/// `T`. This involves a call to `powf`, which may make it too slow for certain +/// applications. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ProPhotoRgb; + +impl Primaries for ProPhotoRgb { + fn red() -> Yxy { + Yxy::new( + T::from_f64(0.7347), + T::from_f64(0.2653), + T::from_f64(0.28804), + ) + } + fn green() -> Yxy { + Yxy::new( + T::from_f64(0.1596), + T::from_f64(0.8404), + T::from_f64(0.71187), + ) + } + fn blue() -> Yxy { + Yxy::new( + T::from_f64(0.0366), + T::from_f64(0.0001), + T::from_f64(0.000085663), + ) + } +} + +impl RgbSpace for ProPhotoRgb { + type Primaries = ProPhotoRgb; + type WhitePoint = D50; + + #[rustfmt::skip] + #[inline(always)] + fn rgb_to_xyz_matrix() -> Option> { + // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + Some([ + 0.7976749, 0.1351917, 0.0313534, + 0.2880402, 0.7118741, 0.0000857, + 0.0000000, 0.0000000, 0.8252100, + ]) + } + + #[rustfmt::skip] + #[inline(always)] + fn xyz_to_rgb_matrix() -> Option> { + // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + Some([ + 1.3459433, -0.2556075, -0.0511118, + -0.5445989, 1.5081673, 0.0205351, + 0.0000000, 0.0000000, 1.2118128, + ]) + } +} + +impl RgbStandard for ProPhotoRgb { + type Space = ProPhotoRgb; + type TransferFn = ProPhotoRgb; +} + +impl LumaStandard for ProPhotoRgb { + type WhitePoint = D50; + type TransferFn = ProPhotoRgb; +} + +impl IntoLinear for ProPhotoRgb +where + T: Real + Powf + Arithmetics + PartialCmp + Clone, + T::Mask: LazySelect, +{ + fn into_linear(encoded: T) -> T { + lazy_select! { + if encoded.lt(&T::from_f64(0.03125)) => T::from_f64(1.0 / 16.0) * &encoded, + else => encoded.clone().powf(T::from_f64(1.8)), + } + } +} + +impl FromLinear for ProPhotoRgb +where + T: Real + Powf + Arithmetics + PartialCmp + Clone, + T::Mask: LazySelect, +{ + fn from_linear(linear: T) -> T { + lazy_select! { + if linear.lt(&T::from_f64(0.001953125)) => T::from_f64(16.0) * &linear, + else => linear.clone().powf(T::from_f64(1.0 / 1.8)), + } + } +} + +#[cfg(test)] +mod test { + #[cfg(feature = "approx")] + mod conversion { + use crate::{ + convert::IntoColorUnclamped, + encoding::prophoto::ProPhotoRgb, + matrix::{matrix_inverse, rgb_to_xyz_matrix}, + rgb::{Primaries, RgbSpace}, + white_point::{Any, WhitePoint, D50}, + Xyz, + }; + + #[test] + fn rgb_to_xyz() { + let dynamic = rgb_to_xyz_matrix::(); + let constant = ProPhotoRgb::rgb_to_xyz_matrix().unwrap(); + assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); + } + + #[test] + fn xyz_to_rgb() { + let dynamic = matrix_inverse(rgb_to_xyz_matrix::()); + let constant = ProPhotoRgb::xyz_to_rgb_matrix().unwrap(); + assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); + } + + #[test] + fn primaries_prophoto() { + let red: Xyz = ProPhotoRgb::red().into_color_unclamped(); + let green: Xyz = ProPhotoRgb::green().into_color_unclamped(); + let blue: Xyz = ProPhotoRgb::blue().into_color_unclamped(); + // Compare sum of primaries to white point. + assert_relative_eq!(red + green + blue, D50::get_xyz(), epsilon = 0.0001); + } + } +} diff --git a/palette/src/rgb.rs b/palette/src/rgb.rs index 09cd08ec..d34d00b2 100644 --- a/palette/src/rgb.rs +++ b/palette/src/rgb.rs @@ -222,6 +222,42 @@ pub type LinAdobeRgb = Rgb, T>; /// create a value and use it. pub type LinAdobeRgba = Rgba, T>; +/// Non-linear ProPhoto RGB, a wide color gamut RGB format. +/// +/// This is an RGB standard with a color gamut designed to include 100% of likely +/// occurring real-world colors. +/// +/// See [`Rgb`] for more details on how to create a value and use it. +pub type ProPhotoRgb = Rgb; + +/// Non-linear ProPhoto RGB with an alpha component. +/// +/// This is a transparent version of [`ProPhotoRgb`], which is commonly used as the +/// input or output format. +/// +/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to +/// create a value and use it. +pub type ProPhotoRgba = Rgba; + +/// Linear ProPhoto RGB. +/// +/// You probably want [`ProPhotoRgb`] if you are looking for an input or output format. +/// This is the linear version of ProPhoto RGB, 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 LinProPhotoRgb = Rgb, T>; + +/// Linear ProPhoto RGB with an alpha component. +/// +/// You probably want [`ProPhotoRgba`] if you are looking for an input or output format. +/// This is the linear version of ProPhoto RGBA, which is what you would usually convert +/// to before working with the color. +/// +/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to +/// create a value and use it. +pub type LinProPhotoRgba = Rgba, T>; + /// Rec. 709. /// /// This standard has the same primaries as [`Srgb`], but uses the transfer