From 540354bbe0b8cecb42fce930a8fa8467236f010a Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sun, 11 Aug 2024 11:58:07 -0700 Subject: [PATCH 1/2] Add Adobe RGB (1998) --- palette/src/encoding.rs | 2 + palette/src/encoding/adobe.rs | 109 ++++++++++++++++++++++++++++++++++ palette/src/lib.rs | 5 +- palette/src/rgb.rs | 36 +++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 palette/src/encoding/adobe.rs diff --git a/palette/src/encoding.rs b/palette/src/encoding.rs index a1674018e..8ea6bb0dd 100644 --- a/palette/src/encoding.rs +++ b/palette/src/encoding.rs @@ -5,11 +5,13 @@ //! represented as type parameters in Palette, as a form of type branding, to //! prevent accidental mixups. +pub use self::adobe::AdobeRgb; pub use self::gamma::{F2p2, Gamma}; pub use self::linear::Linear; pub use self::rec_standards::{Rec2020, Rec709}; pub use self::srgb::Srgb; +pub mod adobe; pub mod gamma; pub mod linear; pub mod rec_standards; diff --git a/palette/src/encoding/adobe.rs b/palette/src/encoding/adobe.rs new file mode 100644 index 000000000..e445ba814 --- /dev/null +++ b/palette/src/encoding/adobe.rs @@ -0,0 +1,109 @@ +//! The Adobe RGB (1998) standard. + +use crate::{ + encoding::gamma::GammaFn, + luma::LumaStandard, + num::Real, + rgb::{Primaries, RgbSpace, RgbStandard}, + white_point::{Any, D65}, + Mat3, Yxy, +}; + +/// The Adobe RGB (1998) (a.k.a. opRGB) color space and standard. +/// +/// This color space was designed to encompass most colors achievable by CMYK +/// printers using RGB primaries. It has a wider color gamut than sRGB, primarily +/// in cyan-green hues. +/// +/// The Adobe RGB standard uses a gamma 2.2 transfer function. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct AdobeRgb; + +impl Primaries for AdobeRgb { + // Primary values from https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf with + // `luma` values taken from the conversion matrix in `RgbSpace` implementation. + fn red() -> Yxy { + Yxy::new( + T::from_f64(0.6400), + T::from_f64(0.3300), + T::from_f64(0.2974), + ) + } + fn green() -> Yxy { + Yxy::new( + T::from_f64(0.2100), + T::from_f64(0.7100), + T::from_f64(0.6273), + ) + } + fn blue() -> Yxy { + Yxy::new( + T::from_f64(0.1500), + T::from_f64(0.0600), + T::from_f64(0.0753), + ) + } +} + +impl RgbSpace for AdobeRgb { + type Primaries = AdobeRgb; + type WhitePoint = D65; + + #[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.5767309, 0.1855540, 0.1881852, + 0.2973769, 0.6273491, 0.0752741, + 0.0270343, 0.0706872, 0.9911085, + ]) + } + + #[rustfmt::skip] + #[inline(always)] + fn xyz_to_rgb_matrix() -> Option> { + // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + Some([ + 2.0413690, -0.5649464, -0.3446944, + -0.9692660, 1.8760108, 0.0415560, + 0.0134474, -0.1183897, 1.0154096, + ]) + } +} + +impl RgbStandard for AdobeRgb { + type Space = AdobeRgb; + type TransferFn = GammaFn; +} + +impl LumaStandard for AdobeRgb { + type WhitePoint = D65; + type TransferFn = GammaFn; +} + +#[cfg(test)] +mod test { + #[cfg(feature = "approx")] + mod conversion { + use crate::{ + encoding::adobe::AdobeRgb, + matrix::{matrix_inverse, rgb_to_xyz_matrix}, + rgb::RgbSpace, + }; + + #[test] + fn rgb_to_xyz() { + let dynamic = rgb_to_xyz_matrix::(); + let constant = AdobeRgb::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 = AdobeRgb::xyz_to_rgb_matrix().unwrap(); + assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); + } + } +} diff --git a/palette/src/lib.rs b/palette/src/lib.rs index 50645662f..048be73cd 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -312,7 +312,10 @@ pub use oklab::{Oklab, Oklaba}; #[doc(inline)] pub use oklch::{Oklch, Oklcha}; #[doc(inline)] -pub use rgb::{GammaSrgb, GammaSrgba, LinRec2020, LinSrgb, LinSrgba, Rec2020, Rec709, Srgb, Srgba}; +pub use rgb::{ + AdobeRgb, AdobeRgba, GammaSrgb, GammaSrgba, LinAdobeRgb, LinAdobeRgba, LinRec2020, LinSrgb, + LinSrgba, Rec2020, Rec709, Srgb, Srgba, +}; #[doc(inline)] pub use xyz::{Xyz, Xyza}; #[doc(inline)] diff --git a/palette/src/rgb.rs b/palette/src/rgb.rs index 7c18101a1..80814c690 100644 --- a/palette/src/rgb.rs +++ b/palette/src/rgb.rs @@ -133,6 +133,42 @@ pub type GammaSrgb = Rgb, T>; /// create a value and use it. pub type GammaSrgba = Rgba, T>; +/// Non-linear Adobe RGB. +/// +/// This is a gamma 2.2 encoded RGB color space designed to include most colors +/// producable by CMYK printers. +/// +/// See [`Rgb`] for more details on how to create a value and use it. +pub type AdobeRgb = Rgb; + +/// Non-linear Adobe RGB with an alpha component. +/// +/// This is a transparent version of [`AdobeRgb`], 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 AdobeRgba = Rgba; + +/// Linear Adobe RGB. +/// +/// You probably want [`AdobeRgb`] if you are looking for an input or output format. +/// This is the linear version of Adobe 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 LinAdobeRgb = Rgb, T>; + +/// Linear Adobe RGB with an alpha component. +/// +/// You probably want [`AdobeRgba`] if you are looking for an input or output format. +/// This is the linear version of Adobe 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 LinAdobeRgba = Rgba, T>; + /// Rec. 709. /// /// This standard has the same primaries as [`Srgb`], but uses the transfer From a06ce7a64ff48ba9b91de122eb0579a88e8dae28 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sun, 11 Aug 2024 13:02:04 -0700 Subject: [PATCH 2/2] Custom implementation to avoid GammaFn bug --- palette/src/encoding/adobe.rs | 56 ++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/palette/src/encoding/adobe.rs b/palette/src/encoding/adobe.rs index e445ba814..62960225e 100644 --- a/palette/src/encoding/adobe.rs +++ b/palette/src/encoding/adobe.rs @@ -1,14 +1,15 @@ //! The Adobe RGB (1998) standard. use crate::{ - encoding::gamma::GammaFn, luma::LumaStandard, - num::Real, + num::{Powf, Real}, rgb::{Primaries, RgbSpace, RgbStandard}, white_point::{Any, D65}, Mat3, Yxy, }; +use super::{FromLinear, IntoLinear}; + /// The Adobe RGB (1998) (a.k.a. opRGB) color space and standard. /// /// This color space was designed to encompass most colors achievable by CMYK @@ -74,12 +75,30 @@ impl RgbSpace for AdobeRgb { impl RgbStandard for AdobeRgb { type Space = AdobeRgb; - type TransferFn = GammaFn; + type TransferFn = AdobeRgb; } impl LumaStandard for AdobeRgb { type WhitePoint = D65; - type TransferFn = GammaFn; + type TransferFn = AdobeRgb; +} + +impl IntoLinear for AdobeRgb +where + T: Real + Powf, +{ + fn into_linear(encoded: T) -> T { + encoded.powf(T::from_f64(563.0 / 256.0)) + } +} + +impl FromLinear for AdobeRgb +where + T: Real + Powf, +{ + fn from_linear(linear: T) -> T { + linear.powf(T::from_f64(256.0 / 563.0)) + } } #[cfg(test)] @@ -106,4 +125,33 @@ mod test { assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); } } + + #[cfg(feature = "approx")] + mod transfer { + use crate::encoding::{AdobeRgb, FromLinear, IntoLinear}; + + #[test] + fn lin_to_enc_to_lin() { + for i in 0..=100 { + let linear = i as f64 / 100.0; + let encoded: f64 = AdobeRgb::from_linear(linear); + assert_relative_eq!(linear, AdobeRgb::into_linear(encoded), epsilon = 0.0000001); + } + } + + #[test] + fn enc_to_lin_to_enc() { + for i in 0..=100 { + let encoded = i as f64 / 100.0; + let linear: f64 = AdobeRgb::into_linear(encoded); + assert_relative_eq!(encoded, AdobeRgb::from_linear(linear), epsilon = 0.0000001); + } + } + + #[test] + fn correct_values() { + assert_relative_eq!(AdobeRgb::from_linear(0.5), 0.72965838, epsilon = 0.0000001); + assert_relative_eq!(AdobeRgb::into_linear(0.5), 0.21775552, epsilon = 0.0000001); + } + } }