Skip to content

Commit 5476be6

Browse files
authored
Merge pull request #412 from Kirk-Fox/master
Add DCI-P3, DCI-P3+, and Display P3
2 parents fab4412 + 068eccc commit 5476be6

File tree

3 files changed

+369
-0
lines changed

3 files changed

+369
-0
lines changed

palette/src/encoding.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
pub use self::adobe::AdobeRgb;
99
pub use self::gamma::{F2p2, Gamma};
1010
pub use self::linear::Linear;
11+
pub use self::p3::{DciP3, DciP3Plus, DisplayP3};
1112
pub use self::rec_standards::{Rec2020, Rec709};
1213
pub use self::srgb::Srgb;
1314

1415
pub mod adobe;
1516
pub mod gamma;
1617
pub mod linear;
18+
pub mod p3;
1719
pub mod rec_standards;
1820
pub mod srgb;
1921

palette/src/encoding/p3.rs

+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
//! The P3 color space(s) and standards.
2+
3+
use core::marker::PhantomData;
4+
5+
use crate::{
6+
encoding::{FromLinear, IntoLinear, Srgb},
7+
luma::LumaStandard,
8+
num::{Powf, Real},
9+
rgb::{Primaries, RgbSpace, RgbStandard},
10+
white_point::{Any, WhitePoint, D65},
11+
Mat3, Xyz, Yxy,
12+
};
13+
14+
/// The theatrical DCI-P3 standard.
15+
///
16+
/// This standard uses a gamma 2.6 transfer function and a white point of ~6300K that
17+
/// matches the color of xenon bulbs used in theater projectors
18+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19+
pub struct DciP3;
20+
21+
impl<T: Real> Primaries<T> for DciP3 {
22+
fn red() -> Yxy<Any, T> {
23+
Yxy::new(
24+
T::from_f64(0.680),
25+
T::from_f64(0.320),
26+
T::from_f64(0.209492),
27+
)
28+
}
29+
fn green() -> Yxy<Any, T> {
30+
Yxy::new(
31+
T::from_f64(0.265),
32+
T::from_f64(0.690),
33+
T::from_f64(0.721595),
34+
)
35+
}
36+
fn blue() -> Yxy<Any, T> {
37+
Yxy::new(
38+
T::from_f64(0.150),
39+
T::from_f64(0.060),
40+
T::from_f64(0.068913),
41+
)
42+
}
43+
}
44+
45+
impl<T: Real> WhitePoint<T> for DciP3 {
46+
fn get_xyz() -> Xyz<Any, T> {
47+
Xyz::new(
48+
T::from_f64(0.314 / 0.351),
49+
T::from_f64(1.0),
50+
T::from_f64(0.335 / 0.351),
51+
)
52+
}
53+
}
54+
55+
impl RgbSpace for DciP3 {
56+
type Primaries = DciP3;
57+
type WhitePoint = DciP3;
58+
59+
#[rustfmt::skip]
60+
#[inline(always)]
61+
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
62+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
63+
Some([
64+
0.4451698, 0.2771344, 0.1722827,
65+
0.2094917, 0.7215953, 0.0689131,
66+
0.0000000, 0.0470606, 0.9073554,
67+
])
68+
}
69+
70+
#[rustfmt::skip]
71+
#[inline(always)]
72+
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
73+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
74+
Some([
75+
2.7253940, -1.0180030, -0.4401632,
76+
-0.7951680, 1.6897321, 0.0226472,
77+
0.0412419, -0.0876390, 1.1009294,
78+
])
79+
}
80+
}
81+
82+
impl RgbStandard for DciP3 {
83+
type Space = DciP3;
84+
type TransferFn = P3Gamma;
85+
}
86+
87+
impl LumaStandard for DciP3 {
88+
type WhitePoint = DciP3;
89+
type TransferFn = P3Gamma;
90+
}
91+
92+
/// The Canon DCI-P3+ color space and standard.
93+
///
94+
/// This standard has the same white point as [`DciP3`], but has a much wider gamut and
95+
/// no standardized transfer function (left to user preference). The generic `F` in
96+
/// this struct represents the chosen transfer function.
97+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98+
pub struct DciP3Plus<F>(PhantomData<F>);
99+
100+
impl<T: Real, F> Primaries<T> for DciP3Plus<F> {
101+
fn red() -> Yxy<Any, T> {
102+
Yxy::new(T::from_f64(0.740), T::from_f64(0.270), T::from_f64(0.203986))
103+
}
104+
fn green() -> Yxy<Any, T> {
105+
Yxy::new(T::from_f64(0.220), T::from_f64(0.780), T::from_f64(0.882591))
106+
}
107+
fn blue() -> Yxy<Any, T> {
108+
Yxy::new(
109+
T::from_f64(0.090),
110+
T::from_f64(-0.090),
111+
T::from_f64(-0.086577),
112+
)
113+
}
114+
}
115+
116+
impl<F> RgbSpace for DciP3Plus<F> {
117+
type Primaries = DciP3Plus<F>;
118+
type WhitePoint = DciP3;
119+
120+
#[rustfmt::skip]
121+
#[inline(always)]
122+
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
123+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
124+
Some([
125+
0.5590736, 0.2489359, 0.0865774,
126+
0.2039863, 0.8825911, -0.0865774,
127+
-0.0075550, 0.0000000, 0.9619710,
128+
])
129+
}
130+
131+
#[rustfmt::skip]
132+
#[inline(always)]
133+
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
134+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
135+
Some([
136+
1.9904035, -0.5613959, -0.2296619,
137+
-0.4584928, 1.2623460, 0.1548755,
138+
0.0156321, -0.0044090, 1.0377287,
139+
])
140+
}
141+
}
142+
143+
impl<F> RgbStandard for DciP3Plus<F> {
144+
type Space = DciP3Plus<F>;
145+
type TransferFn = F;
146+
}
147+
148+
impl<F> LumaStandard for DciP3Plus<F> {
149+
type WhitePoint = DciP3;
150+
type TransferFn = F;
151+
}
152+
153+
/// A gamma 2.6 transfer function used by some P3 variants
154+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
155+
pub struct P3Gamma;
156+
157+
impl<T> IntoLinear<T, T> for P3Gamma
158+
where
159+
T: Real + Powf,
160+
{
161+
#[inline]
162+
fn into_linear(encoded: T) -> T {
163+
encoded.powf(T::from_f64(2.6))
164+
}
165+
}
166+
167+
impl<T> FromLinear<T, T> for P3Gamma
168+
where
169+
T: Real + Powf,
170+
{
171+
#[inline]
172+
fn from_linear(linear: T) -> T {
173+
linear.powf(T::from_f64(1.0 / 2.6))
174+
}
175+
}
176+
177+
/// The Display P3 standard.
178+
///
179+
/// This standard uses the same primaries as [`DciP3`] but with a [`D65`] white point
180+
/// and the [`Srgb`] transfer function.
181+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
182+
pub struct DisplayP3;
183+
184+
impl<T: Real> Primaries<T> for DisplayP3 {
185+
fn red() -> Yxy<Any, T> {
186+
Yxy::new(T::from_f64(0.680), T::from_f64(0.320), T::from_f64(0.22900))
187+
}
188+
fn green() -> Yxy<Any, T> {
189+
Yxy::new(T::from_f64(0.265), T::from_f64(0.690), T::from_f64(0.69173))
190+
}
191+
fn blue() -> Yxy<Any, T> {
192+
Yxy::new(T::from_f64(0.150), T::from_f64(0.060), T::from_f64(0.07927))
193+
}
194+
}
195+
impl RgbSpace for DisplayP3 {
196+
type Primaries = DisplayP3;
197+
type WhitePoint = D65;
198+
199+
#[rustfmt::skip]
200+
#[inline(always)]
201+
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
202+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
203+
Some([
204+
0.4866327, 0.2656632, 0.1981742,
205+
0.2290036, 0.6917267, 0.0792697,
206+
0.0000000, 0.0451126, 1.0437174,
207+
])
208+
}
209+
210+
#[rustfmt::skip]
211+
#[inline(always)]
212+
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
213+
// Matrix calculated using https://www.russellcottrell.com/photo/matrixCalculator.htm
214+
Some([
215+
2.4931808, -0.9312655, -0.4026597,
216+
-0.8295031, 1.7626941, 0.0236251,
217+
0.0358536, -0.0761890, 0.9570926,
218+
])
219+
}
220+
}
221+
222+
impl RgbStandard for DisplayP3 {
223+
type Space = DisplayP3;
224+
type TransferFn = Srgb;
225+
}
226+
227+
impl LumaStandard for DisplayP3 {
228+
type WhitePoint = D65;
229+
type TransferFn = Srgb;
230+
}
231+
232+
#[cfg(test)]
233+
mod test {
234+
#[cfg(feature = "approx")]
235+
mod conversion {
236+
use crate::{
237+
convert::IntoColorUnclamped,
238+
encoding::p3::{DciP3, DciP3Plus, DisplayP3, P3Gamma},
239+
matrix::{matrix_inverse, rgb_to_xyz_matrix},
240+
rgb::{Primaries, RgbSpace},
241+
white_point::{Any, WhitePoint, D65},
242+
Xyz,
243+
};
244+
245+
#[test]
246+
fn rgb_to_xyz_display_p3() {
247+
let dynamic = rgb_to_xyz_matrix::<DisplayP3, f64>();
248+
let constant = DisplayP3::rgb_to_xyz_matrix().unwrap();
249+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
250+
}
251+
252+
#[test]
253+
fn xyz_to_rgb_display_p3() {
254+
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DisplayP3, f64>());
255+
let constant = DisplayP3::xyz_to_rgb_matrix().unwrap();
256+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
257+
}
258+
259+
#[test]
260+
fn rgb_to_xyz_dci_p3() {
261+
let dynamic = rgb_to_xyz_matrix::<DciP3, f64>();
262+
let constant = DciP3::rgb_to_xyz_matrix().unwrap();
263+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
264+
}
265+
266+
#[test]
267+
fn xyz_to_rgb_dci_p3() {
268+
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DciP3, f64>());
269+
let constant = DciP3::xyz_to_rgb_matrix().unwrap();
270+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
271+
}
272+
273+
#[test]
274+
fn rgb_to_xyz_dci_p3_plus() {
275+
let dynamic = rgb_to_xyz_matrix::<DciP3Plus<P3Gamma>, f64>();
276+
let constant = DciP3Plus::<P3Gamma>::rgb_to_xyz_matrix().unwrap();
277+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
278+
}
279+
280+
#[test]
281+
fn xyz_to_rgb_dci_p3_plus() {
282+
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<DciP3Plus<P3Gamma>, f64>());
283+
let constant = DciP3Plus::<P3Gamma>::xyz_to_rgb_matrix().unwrap();
284+
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
285+
}
286+
287+
#[test]
288+
fn primaries_display_p3() {
289+
let red: Xyz<Any, f64> = DisplayP3::red().into_color_unclamped();
290+
let green: Xyz<Any, f64> = DisplayP3::green().into_color_unclamped();
291+
let blue: Xyz<Any, f64> = DisplayP3::blue().into_color_unclamped();
292+
// Compare sum of primaries to white point.
293+
assert_relative_eq!(red + green + blue, D65::get_xyz(), epsilon = 0.00001)
294+
}
295+
296+
#[test]
297+
fn primaries_dci_p3() {
298+
let red: Xyz<Any, f64> = DciP3::red().into_color_unclamped();
299+
let green: Xyz<Any, f64> = DciP3::green().into_color_unclamped();
300+
let blue: Xyz<Any, f64> = DciP3::blue().into_color_unclamped();
301+
// Compare sum of primaries to white point.
302+
assert_relative_eq!(red + green + blue, DciP3::get_xyz(), epsilon = 0.00001)
303+
}
304+
305+
#[test]
306+
fn primaries_dci_p3_plus() {
307+
let red: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::red().into_color_unclamped();
308+
let green: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::green().into_color_unclamped();
309+
let blue: Xyz<Any, f64> = DciP3Plus::<P3Gamma>::blue().into_color_unclamped();
310+
// Compare sum of primaries to white point.
311+
assert_relative_eq!(red + green + blue, DciP3::get_xyz(), epsilon = 0.00001)
312+
}
313+
}
314+
}

palette/src/rgb.rs

+53
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,59 @@ pub type AdobeRgb<T = f32> = Rgb<encoding::AdobeRgb, T>;
150150
/// create a value and use it.
151151
pub type AdobeRgba<T = f32> = Rgba<encoding::AdobeRgb, T>;
152152

153+
/// Non-linear DCI-P3, an RGB format used for digital movie distribution.
154+
///
155+
/// This is an RGB standard with a color gamut wider than that of [`Srgb`] and a
156+
/// white point similar to that of a film projector's xenon bulb.
157+
///
158+
/// See [`Rgb`] for more details on how to create a value and use it.
159+
pub type DciP3<T = f32> = Rgb<encoding::DciP3, T>;
160+
161+
/// Non-linear Canon DCI-P3+, an RGB format with a very wide gamut.
162+
///
163+
/// This is an RGB standard with a color gamut much wider than that of [`Srgb`].
164+
/// It uses the same white point as [`DciP3`], but uses a user-defined transfer
165+
/// function, represented here by the generic `F`.
166+
///
167+
/// See [`Rgb`] for more details on how to create a value and use it.
168+
pub type DciP3Plus<F, T = f32> = Rgb<encoding::DciP3Plus<F>, T>;
169+
170+
/// Non-linear Display P3, an RGB format used developed by Apple for wide-gamut
171+
/// displays.
172+
///
173+
/// This is an RGB standard with the same white point and transfer function as
174+
/// [`Srgb`], but with a wider color gamut.
175+
///
176+
/// See [`Rgb`] for more details on how to create a value and use it.
177+
pub type DisplayP3<T = f32> = Rgb<encoding::DisplayP3, T>;
178+
179+
/// Linear DCI-P3.
180+
///
181+
/// You probably want [`DciP3`] if you are looking for an input or output format.
182+
/// This is the linear version of DCI-P3, which is what you would usually convert
183+
/// to before working with the color.
184+
///
185+
/// See [`Rgb`] for more details on how to create a value and use it.
186+
pub type LinDciP3<T = f32> = Rgb<Linear<encoding::DciP3>, T>;
187+
188+
/// Linear DCI-P3+.
189+
///
190+
/// You probably want [`DciP3Plus`] if you are looking for an input or output format.
191+
/// This is the linear version of DCI-P3+, which is what you would usually convert
192+
/// to before working with the color.
193+
///
194+
/// See [`Rgb`] for more details on how to create a value and use it.
195+
pub type LinDciP3Plus<F, T = f32> = Rgb<Linear<encoding::DciP3Plus<F>>, T>;
196+
197+
/// Linear Display P3.
198+
///
199+
/// You probably want [`DisplayP3`] if you are looking for an input or output format.
200+
/// This is the linear version of Display P3, which is what you would usually convert
201+
/// to before working with the color.
202+
///
203+
/// See [`Rgb`] for more details on how to create a value and use it.
204+
pub type LinDisplayP3<T = f32> = Rgb<Linear<encoding::DisplayP3>, T>;
205+
153206
/// Linear Adobe RGB.
154207
///
155208
/// You probably want [`AdobeRgb`] if you are looking for an input or output format.

0 commit comments

Comments
 (0)