From 232995aecf809309848b864a77e9d968c6185a29 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 2 May 2020 13:43:16 -0400 Subject: color: Implement color spaces --- src/color.rs | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 283 insertions(+) create mode 100644 src/color.rs (limited to 'src') diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..a062795 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,282 @@ +//! Colors and color spaces. + +use crate::metric::kd::{Cartesian, CartesianMetric}; +use crate::metric::{Metric, SquaredDistance}; + +use image::Rgb; + +use std::ops::Index; + +/// An 8-bit RGB color. +pub type Rgb8 = Rgb; + +/// A [color space](https://en.wikipedia.org/wiki/Color_space). +pub trait ColorSpace: Copy + From + CartesianMetric { + /// Compute the average of the given colors. + fn average>(colors: I) -> Self; +} + +/// [sRGB](https://en.wikipedia.org/wiki/SRGB) space. +#[derive(Clone, Copy, Debug)] +pub struct RgbSpace([f64; 3]); + +impl Index for RgbSpace { + type Output = f64; + + fn index(&self, i: usize) -> &f64 { + &self.0[i] + } +} + +impl From for RgbSpace { + fn from(rgb8: Rgb8) -> Self { + Self([ + (rgb8[0] as f64) / 255.0, + (rgb8[1] as f64) / 255.0, + (rgb8[2] as f64) / 255.0, + ]) + } +} + +impl Metric<[f64]> for RgbSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &[f64]) -> Self::Distance { + self.0.distance(other) + } +} + +impl Metric for RgbSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &Self) -> Self::Distance { + self.0.distance(&other.0) + } +} + +impl Cartesian for RgbSpace { + fn dimensions(&self) -> usize { + self.0.dimensions() + } + + fn coordinate(&self, i: usize) -> f64 { + self.0.coordinate(i) + } +} + +impl ColorSpace for RgbSpace { + fn average>(colors: I) -> Self { + let mut sum = [0.0, 0.0, 0.0]; + let mut len: usize = 0; + for color in colors.into_iter() { + for i in 0..3 { + sum[i] += color[i]; + } + len += 1; + } + for i in 0..3 { + sum[i] /= len as f64; + } + Self(sum) + } +} + +/// [CIE XYZ](https://en.wikipedia.org/wiki/CIE_1931_color_space) space. +#[derive(Clone, Copy, Debug)] +struct XyzSpace([f64; 3]); + +impl Index for XyzSpace { + type Output = f64; + + fn index(&self, i: usize) -> &f64 { + &self.0[i] + } +} + +/// The inverse of the sRGB gamma function. +fn srgb_inv_gamma(t: f64) -> f64 { + if t <= 0.040449936 { + t / 12.92 + } else { + ((t + 0.055) / 1.055).powf(2.4) + } +} + +impl From for XyzSpace { + fn from(rgb8: Rgb8) -> Self { + let rgb = RgbSpace::from(rgb8); + + let r = srgb_inv_gamma(rgb[0]); + let g = srgb_inv_gamma(rgb[1]); + let b = srgb_inv_gamma(rgb[2]); + + Self([ + 0.4123808838268995 * r + 0.3575728355732478 * g + 0.1804522977447919 * b, + 0.2126198631048975 * r + 0.7151387878413206 * g + 0.0721499433963131 * b, + 0.0193434956789248 * r + 0.1192121694056356 * g + 0.9505065664127130 * b, + ]) + } +} + +/// CIE D50 [white point](https://en.wikipedia.org/wiki/Standard_illuminant). +const WHITE: XyzSpace = XyzSpace([0.9504060171449392, 0.9999085943425312, 1.089062231497274]); + +/// CIE L\*a\*b\* (and L\*u\*v\*) gamma +fn lab_gamma(t: f64) -> f64 { + if t > 216.0 / 24389.0 { + t.cbrt() + } else { + 841.0 * t / 108.0 + 4.0 / 29.0 + } +} + +/// [CIE L\*a\*b\*](https://en.wikipedia.org/wiki/CIELAB_color_space) space. +#[derive(Clone, Copy, Debug)] +pub struct LabSpace([f64; 3]); + +impl Index for LabSpace { + type Output = f64; + + fn index(&self, i: usize) -> &f64 { + &self.0[i] + } +} + +impl From for LabSpace { + fn from(rgb8: Rgb8) -> Self { + let xyz = XyzSpace::from(rgb8); + + let x = lab_gamma(xyz[0] / WHITE[0]); + let y = lab_gamma(xyz[1] / WHITE[1]); + let z = lab_gamma(xyz[2] / WHITE[2]); + + let l = 116.0 * y - 16.0; + let a = 500.0 * (x - y); + let b = 200.0 * (y - z); + + Self([l, a, b]) + } +} + +impl Metric<[f64]> for LabSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &[f64]) -> Self::Distance { + self.0.distance(other) + } +} + +impl Metric for LabSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &Self) -> Self::Distance { + self.0.distance(&other.0) + } +} + +impl Cartesian for LabSpace { + fn dimensions(&self) -> usize { + self.0.dimensions() + } + + fn coordinate(&self, i: usize) -> f64 { + self.0.coordinate(i) + } +} + +impl ColorSpace for LabSpace { + fn average>(colors: I) -> Self { + let mut sum = [0.0, 0.0, 0.0]; + let mut len: usize = 0; + for color in colors.into_iter() { + for i in 0..3 { + sum[i] += color[i]; + } + len += 1; + } + for i in 0..3 { + sum[i] /= len as f64; + } + Self(sum) + } +} + +/// [CIE L\*u\*v\*](https://en.wikipedia.org/wiki/CIELUV) space. +#[derive(Clone, Copy, Debug)] +pub struct LuvSpace([f64; 3]); + +impl Index for LuvSpace { + type Output = f64; + + fn index(&self, i: usize) -> &f64 { + &self.0[i] + } +} + +/// Computes the u' and v' values for L\*u\*v\*. +fn uv_prime(xyz: &XyzSpace) -> (f64, f64) { + let denom = xyz[0] + 15.0 * xyz[1] + 3.0 * xyz[2]; + if denom == 0.0 { + (0.0, 0.0) + } else { + (4.0 * xyz[0] / denom, 9.0 * xyz[1] / denom) + } +} + +impl From for LuvSpace { + fn from(rgb8: Rgb8) -> Self { + let xyz = XyzSpace::from(rgb8); + + let (uprime, vprime) = uv_prime(&xyz); + let (unprime, vnprime) = uv_prime(&WHITE); + + let l = 116.0 * lab_gamma(xyz[1] / WHITE[1]) - 16.0; + let u = 13.0 * l * (uprime - unprime); + let v = 13.0 * l * (vprime - vnprime); + + Self([l, u, v]) + } +} + +impl Metric<[f64]> for LuvSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &[f64]) -> Self::Distance { + self.0.distance(other) + } +} + +impl Metric for LuvSpace { + type Distance = SquaredDistance; + + fn distance(&self, other: &Self) -> Self::Distance { + self.0.distance(&other.0) + } +} + +impl Cartesian for LuvSpace { + fn dimensions(&self) -> usize { + self.0.dimensions() + } + + fn coordinate(&self, i: usize) -> f64 { + self.0.coordinate(i) + } +} + +impl ColorSpace for LuvSpace { + fn average>(colors: I) -> Self { + let mut sum = [0.0, 0.0, 0.0]; + let mut len: usize = 0; + for color in colors.into_iter() { + for i in 0..3 { + sum[i] += color[i]; + } + len += 1; + } + for i in 0..3 { + sum[i] /= len as f64; + } + Self(sum) + } +} diff --git a/src/main.rs b/src/main.rs index 0d7989b..a7bda67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod color; pub mod metric; fn main() {} -- cgit v1.2.3