Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow RGB/XYZ conversion matrices to be pre-defined #308

Merged
merged 2 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
44 changes: 42 additions & 2 deletions palette/src/encoding/srgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
19 changes: 19 additions & 0 deletions palette/src/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
22 changes: 21 additions & 1 deletion palette/src/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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) {
Expand Down
17 changes: 13 additions & 4 deletions palette/src/rgb/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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>,
Expand Down Expand Up @@ -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))
}
}
Expand Down
15 changes: 11 additions & 4 deletions palette/src/xyz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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())
}
}
Expand Down