Skip to main content

chromashift/
lab.rs

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