Skip to content

Commit 4406b38

Browse files
bors[bot]Ogeon
andauthored
Merge #308
308: Allow RGB/XYZ conversion matrices to be pre-defined r=Ogeon a=Ogeon As suggested by `@Kannen` in #199 (comment), make it possible to have RGB to XYZ and XYZ to RGB matrices pre-defined for an RGB space. This results in significantly faster conversions. Co-authored-by: Erik Hedvall <erikwhedvall@gmail.com>
2 parents 4365f7a + df9902f commit 4406b38

File tree

6 files changed

+107
-12
lines changed

6 files changed

+107
-12
lines changed

.github/workflows/benchmark.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
toolchain: stable
1414
override: true
1515
profile: minimal
16-
- uses: boa-dev/criterion-compare-action@v3.2.0
16+
- uses: boa-dev/criterion-compare-action@v3
1717
with:
1818
token: ${{ secrets.GITHUB_TOKEN }}
1919
branchName: ${{ github.base_ref }}

palette/src/encoding/srgb.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
num::{Arithmetics, MulAdd, MulSub, PartialCmp, Powf, Real},
88
rgb::{Primaries, RgbSpace, RgbStandard},
99
white_point::{Any, D65},
10-
Yxy,
10+
Mat3, Yxy,
1111
};
1212

1313
use lookup_tables::*;
@@ -59,6 +59,28 @@ impl<T: Real> Primaries<T> for Srgb {
5959
impl RgbSpace for Srgb {
6060
type Primaries = Srgb;
6161
type WhitePoint = D65;
62+
63+
#[rustfmt::skip]
64+
#[inline(always)]
65+
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
66+
// Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
67+
Some([
68+
0.4124564, 0.3575761, 0.1804375,
69+
0.2126729, 0.7151522, 0.0721750,
70+
0.0193339, 0.1191920, 0.9503041,
71+
])
72+
}
73+
74+
#[rustfmt::skip]
75+
#[inline(always)]
76+
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
77+
// Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
78+
Some([
79+
3.2404542, -1.5371385, -0.4985314,
80+
-0.9692660, 1.8760108, 0.0415560,
81+
0.0556434, -0.2040259, 1.0572252,
82+
])
83+
}
6284
}
6385

6486
impl RgbStandard for Srgb {
@@ -130,7 +152,25 @@ impl FromLinear<f64, u8> for Srgb {
130152

131153
#[cfg(test)]
132154
mod test {
133-
use crate::encoding::{FromLinear, IntoLinear, Srgb};
155+
use crate::{
156+
encoding::{FromLinear, IntoLinear, Srgb},
157+
matrix::{matrix_inverse, rgb_to_xyz_matrix},
158+
rgb::RgbSpace,
159+
};
160+
161+
#[test]
162+
fn rgb_to_xyz() {
163+
let dynamic = rgb_to_xyz_matrix::<Srgb, f64>();
164+
let constant = Srgb::rgb_to_xyz_matrix().unwrap();
165+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
166+
}
167+
168+
#[test]
169+
fn xyz_to_rgb() {
170+
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<Srgb, f64>());
171+
let constant = Srgb::xyz_to_rgb_matrix().unwrap();
172+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
173+
}
134174

135175
#[test]
136176
fn u8_to_f32_to_u8() {

palette/src/matrix.rs

+19
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,25 @@ where
152152
]
153153
}
154154

155+
/// Maps a matrix from one item type to another.
156+
///
157+
/// This turned out to be easier for the compiler to optimize than `matrix.map(f)`.
158+
#[inline(always)]
159+
pub fn matrix_map<T, U>(matrix: Mat3<T>, mut f: impl FnMut(T) -> U) -> Mat3<U> {
160+
let [m1, m2, m3, m4, m5, m6, m7, m8, m9] = matrix;
161+
[
162+
f(m1),
163+
f(m2),
164+
f(m3),
165+
f(m4),
166+
f(m5),
167+
f(m6),
168+
f(m7),
169+
f(m8),
170+
f(m9),
171+
]
172+
}
173+
155174
/// Generates the Srgb to Xyz transformation matrix for a given white point.
156175
#[inline]
157176
pub fn rgb_to_xyz_matrix<S, T>() -> Mat3<T>

palette/src/rgb.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ use crate::{
6464
encoding::{self, FromLinear, Gamma, IntoLinear, Linear},
6565
stimulus::{FromStimulus, Stimulus},
6666
white_point::Any,
67-
Yxy,
67+
Mat3, Yxy,
6868
};
6969

7070
pub use self::rgb::{FromHexError, Rgb, Rgba};
@@ -121,6 +121,26 @@ pub trait RgbSpace {
121121

122122
/// The white point of the RGB color space.
123123
type WhitePoint;
124+
125+
/// Get a pre-defined matrix for converting an RGB value with this standard
126+
/// into an XYZ value.
127+
///
128+
/// Returning `None` (as in the default implementation) means that the
129+
/// matrix will be computed dynamically, which is significantly slower.
130+
#[inline(always)]
131+
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
132+
None
133+
}
134+
135+
/// Get a pre-defined matrix for converting an XYZ value into an RGB value
136+
/// with this standard.
137+
///
138+
/// Returning `None` (as in the default implementation) means that the
139+
/// matrix will be computed dynamically, which is significantly slower.
140+
#[inline(always)]
141+
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
142+
None
143+
}
124144
}
125145

126146
impl<P, W> RgbSpace for (P, W) {

palette/src/rgb/rgb.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::{
2929
convert::{FromColorUnclamped, IntoColorUnclamped},
3030
encoding::{FromLinear, IntoLinear, Linear, Srgb},
3131
luma::LumaStandard,
32-
matrix::{matrix_inverse, multiply_xyz_to_rgb, rgb_to_xyz_matrix},
32+
matrix::{matrix_inverse, matrix_map, multiply_xyz_to_rgb, rgb_to_xyz_matrix},
3333
num::{
3434
self, Abs, Arithmetics, FromScalar, FromScalarArray, IntoScalarArray, IsValidDivisor,
3535
MinMax, One, PartialCmp, Real, Recip, Round, Trigonometry, Zero,
@@ -251,6 +251,7 @@ impl<S: RgbStandard, T> Rgb<S, T> {
251251
///
252252
/// See the transfer function types in the [`encoding`](crate::encoding)
253253
/// module for details and performance characteristics.
254+
#[inline(always)]
254255
pub fn into_linear<U>(self) -> Rgb<Linear<S::Space>, U>
255256
where
256257
S::TransferFn: IntoLinear<U, T>,
@@ -276,6 +277,7 @@ impl<S: RgbStandard, T> Rgb<S, T> {
276277
///
277278
/// See the transfer function types in the [`encoding`](crate::encoding)
278279
/// module for details and performance characteristics.
280+
#[inline(always)]
279281
pub fn from_linear<U>(color: Rgb<Linear<S::Space>, U>) -> Self
280282
where
281283
S::TransferFn: FromLinear<U, T>,
@@ -589,12 +591,19 @@ where
589591
<S::Space as RgbSpace>::Primaries: Primaries<T::Scalar>,
590592
<S::Space as RgbSpace>::WhitePoint: WhitePoint<T::Scalar>,
591593
T: Arithmetics + FromScalar,
592-
T::Scalar:
593-
Recip + IsValidDivisor<Mask = bool> + Arithmetics + Clone + FromScalar<Scalar = T::Scalar>,
594+
T::Scalar: Real
595+
+ Recip
596+
+ IsValidDivisor<Mask = bool>
597+
+ Arithmetics
598+
+ Clone
599+
+ FromScalar<Scalar = T::Scalar>,
594600
Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>,
595601
{
596602
fn from_color_unclamped(color: Xyz<<S::Space as RgbSpace>::WhitePoint, T>) -> Self {
597-
let transform_matrix = matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>());
603+
let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else(
604+
|| matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>()),
605+
|matrix| matrix_map(matrix, T::Scalar::from_f64),
606+
);
598607
Self::from_linear(multiply_xyz_to_rgb(transform_matrix, color))
599608
}
600609
}

palette/src/xyz.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::{
2121
convert::{FromColorUnclamped, IntoColorUnclamped},
2222
encoding::IntoLinear,
2323
luma::LumaStandard,
24-
matrix::{multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix},
24+
matrix::{matrix_map, multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix},
2525
num::{
2626
self, Arithmetics, FromScalar, FromScalarArray, IntoScalarArray, IsValidDivisor, MinMax,
2727
One, PartialCmp, Powi, Real, Recip, Zero,
@@ -209,8 +209,12 @@ impl<Wp, T> FromColorUnclamped<Xyz<Wp, T>> for Xyz<Wp, T> {
209209
impl<Wp, T, S> FromColorUnclamped<Rgb<S, T>> for Xyz<Wp, T>
210210
where
211211
T: Arithmetics + FromScalar,
212-
T::Scalar:
213-
Recip + IsValidDivisor<Mask = bool> + Arithmetics + FromScalar<Scalar = T::Scalar> + Clone,
212+
T::Scalar: Real
213+
+ Recip
214+
+ IsValidDivisor<Mask = bool>
215+
+ Arithmetics
216+
+ FromScalar<Scalar = T::Scalar>
217+
+ Clone,
214218
Wp: WhitePoint<T::Scalar>,
215219
S: RgbStandard,
216220
S::TransferFn: IntoLinear<T, T>,
@@ -219,7 +223,10 @@ where
219223
Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>,
220224
{
221225
fn from_color_unclamped(color: Rgb<S, T>) -> Self {
222-
let transform_matrix = rgb_to_xyz_matrix::<S::Space, T::Scalar>();
226+
let transform_matrix = S::Space::rgb_to_xyz_matrix().map_or_else(
227+
|| rgb_to_xyz_matrix::<S::Space, T::Scalar>(),
228+
|matrix| matrix_map(matrix, T::Scalar::from_f64),
229+
);
223230
multiply_rgb_to_xyz(transform_matrix, color.into_linear())
224231
}
225232
}

0 commit comments

Comments
 (0)