#![deny(missing_docs)]
#[cfg(test)] extern crate quickcheck;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
pub struct HSL {
pub h: f64,
pub s: f64,
pub l: f64,
}
impl HSL {
#[cfg_attr(feature = "dev", allow(float_cmp))]
pub fn from_rgb(rgb: &[u8]) -> HSL {
use std::cmp::{max, min};
let mut h: f64;
let s: f64;
let l: f64;
let (r, g, b) = (rgb[0], rgb[1], rgb[2]);
let max = max(max(r, g), b);
let min = min(min(r, g), b);
let (r, g, b) = (r as f64 / 255_f64,
g as f64 / 255_f64,
b as f64 / 255_f64);
let (min, max) = (min as f64 / 255_f64, max as f64 / 255_f64);
l = (max + min) / 2_f64;
let delta: f64 = max - min;
if delta == 0_f64 {
return HSL { h: 0_f64, s: 0_f64, l: l };
}
if l < 0.5_f64 {
s = delta / (max + min);
} else {
s = delta / (2_f64 - max - min);
}
let r2 = (((max - r) / 6_f64) + (delta / 2_f64)) / delta;
let g2 = (((max - g) / 6_f64) + (delta / 2_f64)) / delta;
let b2 = (((max - b) / 6_f64) + (delta / 2_f64)) / delta;
h = match max {
x if x == r => b2 - g2,
x if x == g => (1_f64 / 3_f64) + r2 - b2,
_ => (2_f64 / 3_f64) + g2 - r2,
};
if h < 0 as f64 {
h += 1_f64;
} else if h > 1 as f64 {
h -= 1_f64;
}
let h_degrees = (h * 360_f64 * 100_f64).round() / 100_f64;
HSL { h: h_degrees, s: s, l: l }
}
pub fn to_rgb(&self) -> (u8, u8, u8) {
if self.s == 0.0 {
let l = percent_to_byte(self.l);
return (l, l, l);
}
let h = self.h / 360.0; let s = self.s;
let l = self.l;
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - (l * s)
};
let p = 2.0 * l - q;
(percent_to_byte(hue_to_rgb(p, q, h + 1.0 / 3.0)),
percent_to_byte(hue_to_rgb(p, q, h)),
percent_to_byte(hue_to_rgb(p, q, h - 1.0 / 3.0)))
}
}
fn percent_to_byte(percent: f64) -> u8 {
(percent * 255.0).round() as u8
}
fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
let t = if t < 0.0 {
t + 1.0
} else if t > 1.0 {
t - 1.0
} else {
t
};
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}
#[cfg(test)]
mod tests {
use quickcheck::{Arbitrary, Gen, quickcheck};
use super::*;
#[derive(Clone, Debug, Hash, PartialEq)]
struct RGB {
r: u8, g: u8, b: u8,
}
impl Arbitrary for RGB {
fn arbitrary<G: Gen>(g: &mut G) -> RGB {
RGB {
r: g.gen(),
g: g.gen(),
b: g.gen(),
}
}
}
fn sloppy_rgb_compare(a: RGB, b: RGB) -> bool {
const EPSILON: i32 = 0;
let res = (a.r as i32 - b.r as i32 <= EPSILON) &&
(a.g as i32 - b.g as i32 <= EPSILON) &&
(a.b as i32 - b.b as i32 <= EPSILON);
if !res {
println!("in: {:?}, out: {:?}", a, b);
}
res
}
fn idemponent(input: RGB) -> bool {
let RGB { r, g, b } = input;
let (r_out, g_out, b_out) = HSL::from_rgb(&[r, g, b]).to_rgb();
sloppy_rgb_compare(input, RGB { r: r_out, g: g_out, b: b_out })
}
#[test]
fn quickcheck_rgb_to_hsl_and_back() {
quickcheck(idemponent as fn(RGB) -> bool);
}
}