chromashift/
display_p3.rs

1use crate::{LinearRgb, ToAlpha, XyzD65, round_dp};
2use core::fmt;
3
4/// A colour in the Display P3 colour space.
5/// The components are:
6/// - Red - a number between 0.0 and 1.0
7/// - Green - a number between 0.0 and 1.0
8/// - Blue - a number between 0.0 and 1.0
9/// - Alpha - a number between 0.0 and 100.0
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct DisplayP3 {
12	pub red: f64,
13	pub green: f64,
14	pub blue: f64,
15	pub alpha: f32,
16}
17
18impl DisplayP3 {
19	pub fn new(red: f64, green: f64, blue: f64, alpha: f32) -> Self {
20		Self { red, green, blue, alpha: alpha.clamp(0.0, 100.0) }
21	}
22}
23
24impl ToAlpha for DisplayP3 {
25	fn to_alpha(&self) -> f32 {
26		self.alpha
27	}
28}
29
30impl fmt::Display for DisplayP3 {
31	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32		let Self { red, green, blue, alpha } = self;
33		write!(f, "color(display-p3 {} {} {}", round_dp(*red, 2), round_dp(*green, 2), round_dp(*blue, 2))?;
34		if *alpha < 100.0 {
35			write!(f, " / {}", round_dp(*alpha as f64, 2))?;
36		}
37		write!(f, ")")
38	}
39}
40
41/// sRGB transfer function: linear to gamma-encoded
42fn gamma(u: f64) -> f64 {
43	let abs = u.abs();
44	if abs <= 0.0031308 { u * 12.92 } else { u.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055) }
45}
46
47/// sRGB transfer function: gamma-encoded to linear
48fn linear(c: f64) -> f64 {
49	let abs = c.abs();
50	if abs > 0.04045 { c.signum() * ((abs + 0.055) / 1.055).powf(2.4) } else { c / 12.92 }
51}
52
53impl From<XyzD65> for DisplayP3 {
54	fn from(value: XyzD65) -> Self {
55		let XyzD65 { x, y, z, alpha } = value;
56		let x = x / 100.0;
57		let y = y / 100.0;
58		let z = z / 100.0;
59		// XYZ D65 -> Linear Display P3 (see XYZ_to_lin_P3 in CSS Color 4)
60		let lr = x * (446124.0 / 178915.0) + y * (-333277.0 / 357830.0) + z * (-72051.0 / 178915.0);
61		let lg = x * (-14852.0 / 17905.0) + y * (63121.0 / 35810.0) + z * (423.0 / 17905.0);
62		let lb = x * (11844.0 / 330415.0) + y * (-50337.0 / 660830.0) + z * (316169.0 / 330415.0);
63		// Apply sRGB gamma
64		DisplayP3::new(gamma(lr), gamma(lg), gamma(lb), alpha)
65	}
66}
67
68impl From<DisplayP3> for XyzD65 {
69	fn from(value: DisplayP3) -> Self {
70		let DisplayP3 { red, green, blue, alpha } = value;
71		// Linearize with sRGB gamma
72		let lr = linear(red);
73		let lg = linear(green);
74		let lb = linear(blue);
75		// Linear Display P3 -> XYZ D65 (see lin_d3_to_XYZ in CSS Color 4)
76		let x = lr * (608311.0 / 1250200.0) + lg * (189793.0 / 714400.0) + lb * (198249.0 / 1000160.0);
77		let y = lr * (35783.0 / 156275.0) + lg * (247089.0 / 357200.0) + lb * (198249.0 / 2500400.0);
78		let z = lg * (32229.0 / 714400.0) + lb * (5220557.0 / 5000800.0);
79		XyzD65::new(x * 100.0, y * 100.0, z * 100.0, alpha)
80	}
81}
82
83impl From<DisplayP3> for LinearRgb {
84	fn from(value: DisplayP3) -> Self {
85		let DisplayP3 { red, green, blue, alpha } = value;
86		// Linearize with sRGB gamma
87		let lr = linear(red);
88		let lg = linear(green);
89		let lb = linear(blue);
90		// Linear Display P3 -> Linear sRGB
91		let red = lr * (3685649.0 / 3008840.0) + lg * (-676809.0 / 3008840.0);
92		let green = lr * (-5617931.0 / 133579120.0) + lg * (139197051.0 / 133579120.0);
93		let blue = lr * (-1323971.0 / 67420360.0) + lg * (-1514763.0 / 19262960.0) + lb * (148092003.0 / 134840720.0);
94		LinearRgb::new(red, green, blue, alpha)
95	}
96}
97
98impl From<LinearRgb> for DisplayP3 {
99	fn from(value: LinearRgb) -> Self {
100		let LinearRgb { red, green, blue, alpha } = value;
101		// Linear sRGB -> Linear Display P3
102		let lr = red * (2442703.0 / 2969989.0) + green * (527286.0 / 2969989.0);
103		let lg = red * (621563.0 / 18725049.0) + green * (18103486.0 / 18725049.0);
104		let lb =
105			red * (281089.0 / 16454667.0) + green * (10721482.0 / 148092003.0) + blue * (134840720.0 / 148092003.0);
106		// Apply sRGB gamma
107		DisplayP3::new(gamma(lr), gamma(lg), gamma(lb), alpha)
108	}
109}