chromashift/
lab.rs

1use crate::{XyzD50, round_dp};
2use core::fmt;
3
4const D50X: f64 = 96.4220;
5const D50Y: f64 = 100.0;
6const D50Z: f64 = 82.5210;
7
8/// An CIE defined colour space representing L - perceptual lightness, and two axes A & B.
9/// The components are:
10/// - L - a number between 0.0 and 100.0
11/// - A - a number between -125.0 and +125.0
12/// - B - a number between -125.0 and +125.0
13/// - Alpha - a number between 0.0 and 100.0
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Lab {
16	pub lightness: f64,
17	pub a: f64,
18	pub b: f64,
19	pub alpha: f32,
20}
21
22impl Lab {
23	pub fn new(lightness: f64, a: f64, b: f64, alpha: f32) -> Self {
24		Self {
25			lightness: lightness.clamp(0.0, 100.0),
26			a: a.clamp(-125.0, 125.0),
27			b: b.clamp(-125.0, 125.0),
28			alpha: alpha.clamp(0.0, 100.0),
29		}
30	}
31}
32
33impl fmt::Display for Lab {
34	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35		let Self { lightness, a, b, alpha } = self;
36		write!(f, "lab({} {} {}", round_dp(*lightness, 2), round_dp(*a, 3), round_dp(*b, 3))?;
37		if *alpha < 100.0 {
38			write!(f, " / {}", round_dp(*alpha as f64, 2))?;
39		}
40		write!(f, ")")
41	}
42}
43
44impl From<XyzD50> for Lab {
45	fn from(value: XyzD50) -> Self {
46		let XyzD50 { x, y, z, alpha } = value;
47		let x = x / D50X;
48		let y = y / D50Y;
49		let z = z / D50Z;
50		let epsilon = 216.0 / 24389.0; // 6^3/29^3
51		let kappa = 24389.0 / 27.0; // 29^3/3^3
52		let fx = if x > epsilon { x.cbrt() } else { (kappa * x + 16.0) / 116.0 };
53		let fy = if y > epsilon { y.cbrt() } else { (kappa * y + 16.0) / 116.0 };
54		let fz = if z > epsilon { z.cbrt() } else { (kappa * z + 16.0) / 116.0 };
55		let lightness = 116.0 * fy - 16.0;
56		let a = 500.0 * (fx - fy);
57		let b = 200.0 * (fy - fz);
58		Lab::new(lightness, a, b, alpha)
59	}
60}
61
62impl From<Lab> for XyzD50 {
63	fn from(value: Lab) -> Self {
64		let Lab { lightness, a, b, alpha } = value;
65		let epsilon = 216.0 / 24389.0; // 6^3/29^3
66		let kappa = 24389.0 / 27.0; // 29^3/3^3
67		let fy = (lightness + 16.0) / 116.0;
68		let fx = a / 500.0 + fy;
69		let fz = fy - b / 200.0;
70		let x = if fx.powi(3) > epsilon { fx.powi(3) } else { (116.0 * fx - 16.0) / kappa };
71		let y = if lightness > kappa * epsilon { ((lightness + 16.0) / 116.0).powi(3) } else { lightness / kappa };
72		let z = if fz.powi(3) > epsilon { fz.powi(3) } else { (116.0 * fz - 16.0) / kappa };
73		XyzD50::new(x * D50X, y * D50Y, z * D50Z, alpha)
74	}
75}