From 0d0b9e5cee4d5719a9710a6ead480f23ab4faf1e Mon Sep 17 00:00:00 2001 From: Erik Hedvall <erikwhedvall@gmail.com> Date: Sun, 9 Apr 2023 20:24:55 +0200 Subject: [PATCH 1/2] Allow RGB/XYZ conversion matrices to be pre-defined --- palette/src/encoding/srgb.rs | 44 ++++++++++++++++++++++++++++++++++-- palette/src/matrix.rs | 19 ++++++++++++++++ palette/src/rgb.rs | 22 +++++++++++++++++- palette/src/rgb/rgb.rs | 17 ++++++++++---- palette/src/xyz.rs | 15 ++++++++---- 5 files changed, 106 insertions(+), 11 deletions(-) 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<T: Real> Primaries<T> for Srgb { impl RgbSpace for Srgb { type Primaries = Srgb; type WhitePoint = D65; + + #[rustfmt::skip] + #[inline(always)] + fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> { + // 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<Mat3<f64>> { + // 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<f64, u8> 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::<Srgb, f64>(); + 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::<Srgb, f64>()); + 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<T, U>(matrix: Mat3<T>, mut f: impl FnMut(T) -> U) -> Mat3<U> { + 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<S, T>() -> Mat3<T> 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<Mat3<f64>> { + 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<Mat3<f64>> { + None + } } impl<P, W> 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<S: RgbStandard, T> Rgb<S, T> { /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. + #[inline(always)] pub fn into_linear<U>(self) -> Rgb<Linear<S::Space>, U> where S::TransferFn: IntoLinear<U, T>, @@ -276,6 +277,7 @@ impl<S: RgbStandard, T> Rgb<S, T> { /// /// See the transfer function types in the [`encoding`](crate::encoding) /// module for details and performance characteristics. + #[inline(always)] pub fn from_linear<U>(color: Rgb<Linear<S::Space>, U>) -> Self where S::TransferFn: FromLinear<U, T>, @@ -589,12 +591,19 @@ where <S::Space as RgbSpace>::Primaries: Primaries<T::Scalar>, <S::Space as RgbSpace>::WhitePoint: WhitePoint<T::Scalar>, T: Arithmetics + FromScalar, - T::Scalar: - Recip + IsValidDivisor<Mask = bool> + Arithmetics + Clone + FromScalar<Scalar = T::Scalar>, + T::Scalar: Real + + Recip + + IsValidDivisor<Mask = bool> + + Arithmetics + + Clone + + FromScalar<Scalar = T::Scalar>, Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>, { fn from_color_unclamped(color: Xyz<<S::Space as RgbSpace>::WhitePoint, T>) -> Self { - let transform_matrix = matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>()); + let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else( + || matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>()), + |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<Wp, T> FromColorUnclamped<Xyz<Wp, T>> for Xyz<Wp, T> { impl<Wp, T, S> FromColorUnclamped<Rgb<S, T>> for Xyz<Wp, T> where T: Arithmetics + FromScalar, - T::Scalar: - Recip + IsValidDivisor<Mask = bool> + Arithmetics + FromScalar<Scalar = T::Scalar> + Clone, + T::Scalar: Real + + Recip + + IsValidDivisor<Mask = bool> + + Arithmetics + + FromScalar<Scalar = T::Scalar> + + Clone, Wp: WhitePoint<T::Scalar>, S: RgbStandard, S::TransferFn: IntoLinear<T, T>, @@ -219,7 +223,10 @@ where Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>, { fn from_color_unclamped(color: Rgb<S, T>) -> Self { - let transform_matrix = rgb_to_xyz_matrix::<S::Space, T::Scalar>(); + let transform_matrix = S::Space::rgb_to_xyz_matrix().map_or_else( + || rgb_to_xyz_matrix::<S::Space, T::Scalar>(), + |matrix| matrix_map(matrix, T::Scalar::from_f64), + ); multiply_rgb_to_xyz(transform_matrix, color.into_linear()) } } From df9902f7bebac4c2810e72cfc89fd5f3dd93ed1a Mon Sep 17 00:00:00 2001 From: Erik Hedvall <erikwhedvall@gmail.com> Date: Sun, 9 Apr 2023 21:01:49 +0200 Subject: [PATCH 2/2] Change criterion-compare-action version --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }}