diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 759aa98ee..ccea6cc5d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -13,7 +13,7 @@ jobs: toolchain: stable override: true profile: minimal - - uses: boa-dev/criterion-compare-action@v3.2.0 + - uses: boa-dev/criterion-compare-action@v3 with: token: ${{ secrets.GITHUB_TOKEN }} branchName: ${{ github.base_ref }} diff --git a/palette/src/encoding/srgb.rs b/palette/src/encoding/srgb.rs index 474a59819..bb90a9e19 100644 --- a/palette/src/encoding/srgb.rs +++ b/palette/src/encoding/srgb.rs @@ -7,7 +7,7 @@ use crate::{ num::{Arithmetics, MulAdd, MulSub, PartialCmp, Powf, Real}, rgb::{Primaries, RgbSpace, RgbStandard}, white_point::{Any, D65}, - Yxy, + Mat3, Yxy, }; use lookup_tables::*; @@ -59,6 +59,28 @@ impl Primaries for Srgb { impl RgbSpace for Srgb { type Primaries = Srgb; 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.4124564, 0.3575761, 0.1804375, + 0.2126729, 0.7151522, 0.0721750, + 0.0193339, 0.1191920, 0.9503041, + ]) + } + + #[rustfmt::skip] + #[inline(always)] + fn xyz_to_rgb_matrix() -> Option> { + // Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + Some([ + 3.2404542, -1.5371385, -0.4985314, + -0.9692660, 1.8760108, 0.0415560, + 0.0556434, -0.2040259, 1.0572252, + ]) + } } impl RgbStandard for Srgb { @@ -130,7 +152,25 @@ impl FromLinear for Srgb { #[cfg(test)] mod test { - use crate::encoding::{FromLinear, IntoLinear, Srgb}; + use crate::{ + encoding::{FromLinear, IntoLinear, Srgb}, + matrix::{matrix_inverse, rgb_to_xyz_matrix}, + rgb::RgbSpace, + }; + + #[test] + fn rgb_to_xyz() { + let dynamic = rgb_to_xyz_matrix::(); + let constant = Srgb::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 = Srgb::xyz_to_rgb_matrix().unwrap(); + assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001); + } #[test] fn u8_to_f32_to_u8() { diff --git a/palette/src/matrix.rs b/palette/src/matrix.rs index da0eaefe9..ea33be3c4 100644 --- a/palette/src/matrix.rs +++ b/palette/src/matrix.rs @@ -152,6 +152,25 @@ where ] } +/// Maps a matrix from one item type to another. +/// +/// This turned out to be easier for the compiler to optimize than `matrix.map(f)`. +#[inline(always)] +pub fn matrix_map(matrix: Mat3, mut f: impl FnMut(T) -> U) -> Mat3 { + let [m1, m2, m3, m4, m5, m6, m7, m8, m9] = matrix; + [ + f(m1), + f(m2), + f(m3), + f(m4), + f(m5), + f(m6), + f(m7), + f(m8), + f(m9), + ] +} + /// Generates the Srgb to Xyz transformation matrix for a given white point. #[inline] pub fn rgb_to_xyz_matrix() -> Mat3 diff --git a/palette/src/rgb.rs b/palette/src/rgb.rs index bac11b978..a90296dc9 100644 --- a/palette/src/rgb.rs +++ b/palette/src/rgb.rs @@ -64,7 +64,7 @@ use crate::{ encoding::{self, FromLinear, Gamma, IntoLinear, Linear}, stimulus::{FromStimulus, Stimulus}, white_point::Any, - Yxy, + Mat3, Yxy, }; pub use self::rgb::{FromHexError, Rgb, Rgba}; @@ -121,6 +121,26 @@ pub trait RgbSpace { /// The white point of the RGB color space. type WhitePoint; + + /// Get a pre-defined matrix for converting an RGB value with this standard + /// into an XYZ value. + /// + /// Returning `None` (as in the default implementation) means that the + /// matrix will be computed dynamically, which is significantly slower. + #[inline(always)] + fn rgb_to_xyz_matrix() -> Option> { + None + } + + /// Get a pre-defined matrix for converting an XYZ value into an RGB value + /// with this standard. + /// + /// Returning `None` (as in the default implementation) means that the + /// matrix will be computed dynamically, which is significantly slower. + #[inline(always)] + fn xyz_to_rgb_matrix() -> Option> { + None + } } impl RgbSpace for (P, W) { diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index 415a1aefa..8566d9cfa 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -29,7 +29,7 @@ use crate::{ convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::{FromLinear, IntoLinear, Linear, Srgb}, luma::LumaStandard, - matrix::{matrix_inverse, multiply_xyz_to_rgb, rgb_to_xyz_matrix}, + matrix::{matrix_inverse, matrix_map, multiply_xyz_to_rgb, rgb_to_xyz_matrix}, num::{ self, Abs, Arithmetics, FromScalar, FromScalarArray, IntoScalarArray, IsValidDivisor, MinMax, One, PartialCmp, Real, Recip, Round, Trigonometry, Zero, @@ -251,6 +251,7 @@ impl Rgb { /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. + #[inline(always)] pub fn into_linear(self) -> Rgb, U> where S::TransferFn: IntoLinear, @@ -276,6 +277,7 @@ impl Rgb { /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. + #[inline(always)] pub fn from_linear(color: Rgb, U>) -> Self where S::TransferFn: FromLinear, @@ -589,12 +591,19 @@ where ::Primaries: Primaries, ::WhitePoint: WhitePoint, T: Arithmetics + FromScalar, - T::Scalar: - Recip + IsValidDivisor + Arithmetics + Clone + FromScalar, + T::Scalar: Real + + Recip + + IsValidDivisor + + Arithmetics + + Clone + + FromScalar, Yxy: IntoColorUnclamped>, { fn from_color_unclamped(color: Xyz<::WhitePoint, T>) -> Self { - let transform_matrix = matrix_inverse(rgb_to_xyz_matrix::()); + let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else( + || matrix_inverse(rgb_to_xyz_matrix::()), + |matrix| matrix_map(matrix, T::Scalar::from_f64), + ); Self::from_linear(multiply_xyz_to_rgb(transform_matrix, color)) } } diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index 9c029dff3..b30f59095 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -21,7 +21,7 @@ use crate::{ convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::IntoLinear, luma::LumaStandard, - matrix::{multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix}, + matrix::{matrix_map, multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix}, num::{ self, Arithmetics, FromScalar, FromScalarArray, IntoScalarArray, IsValidDivisor, MinMax, One, PartialCmp, Powi, Real, Recip, Zero, @@ -209,8 +209,12 @@ impl FromColorUnclamped> for Xyz { impl FromColorUnclamped> for Xyz where T: Arithmetics + FromScalar, - T::Scalar: - Recip + IsValidDivisor + Arithmetics + FromScalar + Clone, + T::Scalar: Real + + Recip + + IsValidDivisor + + Arithmetics + + FromScalar + + Clone, Wp: WhitePoint, S: RgbStandard, S::TransferFn: IntoLinear, @@ -219,7 +223,10 @@ where Yxy: IntoColorUnclamped>, { fn from_color_unclamped(color: Rgb) -> Self { - let transform_matrix = rgb_to_xyz_matrix::(); + let transform_matrix = S::Space::rgb_to_xyz_matrix().map_or_else( + || rgb_to_xyz_matrix::(), + |matrix| matrix_map(matrix, T::Scalar::from_f64), + ); multiply_rgb_to_xyz(transform_matrix, color.into_linear()) } }