1#![doc = include_str!("../README.md")]
5
6use std::fmt;
7mod adaptation;
8#[cfg(feature = "icc")]
9pub mod icc;
10pub use adaptation::{BradfordAdaptation, WhitePoint};
11mod definition;
12pub use definition::{ParametricCurve, RgbCurve, RgbDefinition, SampledCurve};
13mod gradient;
14mod mapping;
15pub use gradient::{ColorInterpolation, HueInterpolation, LinearGradient, LinearRgba};
16pub use mapping::GamutMapping;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct ColorError;
21impl fmt::Display for ColorError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 f.write_str("invalid color input or non-finite conversion result")
24 }
25}
26impl std::error::Error for ColorError {}
27type Result<T> = std::result::Result<T, ColorError>;
28fn finite(v: [f64; 3]) -> Result<[f64; 3]> {
29 if v.iter().all(|v| v.is_finite()) {
30 Ok(v)
31 } else {
32 Err(ColorError)
33 }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum RgbSpace {
39 Srgb,
40 DisplayP3,
41 Rec2020,
42 AdobeRgb1998,
44}
45impl RgbSpace {
46 pub fn linear_to_xyz_matrix(self) -> [[f64; 3]; 3] {
49 match self {
50 Self::Srgb => [
51 [506752. / 1228815., 87881. / 245763., 12673. / 70218.],
52 [87098. / 409605., 175762. / 245763., 12673. / 175545.],
53 [7918. / 409605., 87881. / 737289., 1001167. / 1053270.],
54 ],
55 Self::DisplayP3 => [
56 [608311. / 1250200., 189793. / 714400., 198249. / 1000160.],
57 [35783. / 156275., 247089. / 357200., 198249. / 2500400.],
58 [0., 32229. / 714400., 5220557. / 5000800.],
59 ],
60 Self::AdobeRgb1998 => [
61 [573536. / 994567., 263643. / 1420810., 187206. / 994567.],
62 [591459. / 1989134., 6239551. / 9945670., 374412. / 4972835.],
63 [53769. / 1989134., 351524. / 4972835., 4929758. / 4972835.],
64 ],
65 Self::Rec2020 => [
66 [
67 63426534. / 99577255.,
68 20160776. / 139408157.,
69 47086771. / 278816314.,
70 ],
71 [
72 26158966. / 99577255.,
73 472592308. / 697040785.,
74 8267143. / 139408157.,
75 ],
76 [0., 19567812. / 697040785., 295819943. / 278816314.],
77 ],
78 }
79 }
80 pub fn xyz_to_linear_matrix(self) -> [[f64; 3]; 3] {
82 inverse(self.linear_to_xyz_matrix())
83 }
84 pub fn linear_to_linear_matrix(self, destination: Self) -> [[f64; 3]; 3] {
86 if self == destination {
87 return [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]];
88 }
89 let a = destination.xyz_to_linear_matrix();
90 let b = self.linear_to_xyz_matrix();
91 std::array::from_fn(|i| std::array::from_fn(|j| (0..3).map(|k| a[i][k] * b[k][j]).sum()))
92 }
93 fn transfer(self, value: f64, decode: bool) -> f64 {
94 let x = value.abs();
95 let y = match (self, decode) {
96 (Self::AdobeRgb1998, true) => x.powf(563. / 256.),
97 (Self::AdobeRgb1998, false) => x.powf(256. / 563.),
98 (Self::Rec2020, true) => {
99 let a = 1.09929682680944;
100 if x < 0.018053968510807 * 4.5 {
101 x / 4.5
102 } else {
103 ((x + a - 1.) / a).powf(1. / 0.45)
104 }
105 }
106 (Self::Rec2020, false) => {
107 if x < 0.018053968510807 {
108 4.5 * x
109 } else {
110 1.09929682680944 * x.powf(0.45) - 0.09929682680944
111 }
112 }
113 (_, true) => {
114 if x <= 0.04045 {
115 x / 12.92
116 } else {
117 ((x + 0.055) / 1.055).powf(2.4)
118 }
119 }
120 (_, false) => {
121 if x <= 0.0031308 {
122 12.92 * x
123 } else {
124 1.055 * x.powf(1. / 2.4) - 0.055
125 }
126 }
127 };
128 y.copysign(value)
129 }
130}
131fn mul(m: [[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
132 m.map(|r| r[0] * v[0] + r[1] * v[1] + r[2] * v[2])
133}
134const fn inverse(m: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
135 let [[a, b, c], [d, e, f], [g, h, i]] = m;
136 let det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
137 [
138 [
139 (e * i - f * h) / det,
140 (c * h - b * i) / det,
141 (b * f - c * e) / det,
142 ],
143 [
144 (f * g - d * i) / det,
145 (a * i - c * g) / det,
146 (c * d - a * f) / det,
147 ],
148 [
149 (d * h - e * g) / det,
150 (b * g - a * h) / det,
151 (a * e - b * d) / det,
152 ],
153 ]
154}
155
156const OKLAB_M1: [[f64; 3]; 3] = [
161 [0.4122214708, 0.5363325363, 0.0514459929],
162 [0.2119034982, 0.6806995451, 0.1073969566],
163 [0.0883024619, 0.2817188376, 0.6299787005],
164];
165const OKLAB_M2: [[f64; 3]; 3] = [
167 [0.2104542553, 0.7936177850, -0.0040720468],
168 [1.9779984951, -2.4285922050, 0.4505937099],
169 [0.0259040371, 0.7827717662, -0.8086757660],
170];
171const OKLAB_M1_INV: [[f64; 3]; 3] = inverse(OKLAB_M1);
172const OKLAB_M2_INV: [[f64; 3]; 3] = inverse(OKLAB_M2);
173
174macro_rules! rgb {
175 ($name:ident, $doc:literal) => {
176 #[doc = $doc]
177 #[derive(Clone, Copy, Debug, PartialEq)]
178 pub struct $name {
179 components: [f64; 3],
180 space: RgbSpace,
181 }
182 impl $name {
183 pub fn new(space: RgbSpace, components: [f64; 3]) -> Result<Self> {
184 Ok(Self {
185 space,
186 components: finite(components)?,
187 })
188 }
189 pub fn components(self) -> [f64; 3] {
190 self.components
191 }
192 pub fn space(self) -> RgbSpace {
193 self.space
194 }
195 }
196 };
197}
198rgb!(
199 EncodedRgb,
200 "Finite encoded RGB, possibly outside [0,1]; no implicit gamut mapping."
201);
202rgb!(
203 LinearRgb,
204 "Finite linear RGB, possibly outside [0,1]; no implicit gamut mapping."
205);
206impl EncodedRgb {
207 pub fn decode(self) -> Result<LinearRgb> {
209 LinearRgb::new(
210 self.space,
211 self.components.map(|v| self.space.transfer(v, true)),
212 )
213 }
214}
215impl LinearRgb {
216 pub fn encode(self) -> Result<EncodedRgb> {
217 EncodedRgb::new(
218 self.space,
219 self.components.map(|v| self.space.transfer(v, false)),
220 )
221 }
222 pub fn convert(self, destination: RgbSpace) -> Result<Self> {
223 Self::new(
224 destination,
225 mul(
226 self.space.linear_to_linear_matrix(destination),
227 self.components,
228 ),
229 )
230 }
231 pub fn to_xyz(self) -> Result<XyzD65> {
232 XyzD65::new(mul(self.space.linear_to_xyz_matrix(), self.components))
233 }
234 pub fn to_oklab(self) -> Result<Oklab> {
235 let lms = mul(OKLAB_M1, self.convert(RgbSpace::Srgb)?.components);
236 Oklab::new(mul(OKLAB_M2, lms.map(f64::cbrt)))
237 }
238}
239#[derive(Clone, Copy, Debug, PartialEq)]
241pub struct XyzD65([f64; 3]);
242impl XyzD65 {
243 pub fn new(v: [f64; 3]) -> Result<Self> {
244 Ok(Self(finite(v)?))
245 }
246 pub fn components(self) -> [f64; 3] {
247 self.0
248 }
249 pub fn to_linear(self, space: RgbSpace) -> Result<LinearRgb> {
250 LinearRgb::new(space, mul(space.xyz_to_linear_matrix(), self.0))
251 }
252}
253#[derive(Clone, Copy, Debug, PartialEq)]
255pub struct Oklab([f64; 3]);
256impl Oklab {
257 pub fn new(v: [f64; 3]) -> Result<Self> {
258 Ok(Self(finite(v)?))
259 }
260 pub fn components(self) -> [f64; 3] {
261 self.0
262 }
263 pub fn to_linear(self, space: RgbSpace) -> Result<LinearRgb> {
265 let lms = mul(OKLAB_M2_INV, self.0).map(|x| x * x * x);
266 LinearRgb::new(RgbSpace::Srgb, mul(OKLAB_M1_INV, lms))?.convert(space)
267 }
268 pub fn to_oklch(self) -> Result<Oklch> {
269 let [l, a, b] = self.0;
270 Oklch::new(l, a.hypot(b), b.atan2(a).to_degrees())
271 }
272}
273#[derive(Clone, Copy, Debug, PartialEq)]
276pub struct Oklch([f64; 3]);
277impl Oklch {
278 pub fn new(l: f64, c: f64, h: f64) -> Result<Self> {
279 finite([l, c, h])?;
280 if c < 0. {
281 return Err(ColorError);
282 }
283 let hue = h.rem_euclid(360.);
284 Ok(Self([l, c, if c == 0. || hue >= 360. { 0. } else { hue }]))
286 }
287 pub fn components(self) -> [f64; 3] {
288 self.0
289 }
290 pub fn to_oklab(self) -> Result<Oklab> {
291 let [l, c, h] = self.0;
292 Oklab::new([l, c * h.to_radians().cos(), c * h.to_radians().sin()])
293 }
294}
295
296#[cfg(test)]
297mod tests;
298
299pub const WGSL_COLOR_MATH: &str = include_str!("math.wgsl");