#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
use std::io::Cursor;
use std::fmt;
use std::fs::File;
use std::io::BufReader;
use std::io::BufRead;
use std::path::Path;
pub type Colour = Color;
#[derive(Debug,Copy,Clone)]
pub struct Color {
pub red: f64,
pub green: f64,
pub blue: f64,
pub alpha: f64,
}
impl Color {
pub fn new(red: f64, green: f64, blue: f64, alpha: f64) -> Color {
Color { red: red, green: green, blue: blue, alpha: alpha }
}
pub fn from_rgb1(r: f64, g: f64, b: f64) -> Color {
Color { red: r, green: g, blue: b, alpha: 1.0 }
}
pub fn from_rgb1v(rgb: &[f64]) -> Color {
Color { red: rgb[0], green: rgb[1], blue: rgb[2], alpha: 1.0 }
}
pub fn to_rgb1(&self) -> (f64,f64,f64) {
(self.red, self.green, self.blue)
}
pub fn from_rgb255(red: u8, green: u8, blue: u8) -> Color {
Color::from_rgb1((red as f64)/255.,
(green as f64)/255.,
(blue as f64)/255.)
}
pub fn from_rgb255v(rgb: &[u8]) -> Color {
Color::from_rgb255(rgb[0], rgb[1], rgb[2])
}
pub fn to_rgb255(&self) -> (u8,u8,u8) {
let r = (self.red * 255.0) as u8;
let g = (self.green * 255.0) as u8;
let b = (self.blue * 255.0) as u8;
(r,g,b)
}
pub fn from_hex(hex: &str) -> Color {
let n = if hex.chars().nth(0).unwrap() == '#' { 1 } else { 0 };
let r = u8::from_str_radix(&hex[n+0..n+2],16).unwrap();
let g = u8::from_str_radix(&hex[n+2..n+4],16).unwrap();
let b = u8::from_str_radix(&hex[n+4..n+6],16).unwrap();
Color::from_rgb255(r,g,b)
}
pub fn to_hex(&self) -> String {
let (r,g,b) = self.to_rgb255();
format!("#{:02x}{:02x}{:02x}", r,g,b)
}
pub fn name(name: &str) -> Option<Color> {
match COLOR_MAP.lock().unwrap().get(name) {
Some(&c) => Some(c.clone()),
None => None,
}
}
pub fn to_hsv(&self) -> (f64,f64,f64) {
rgb2hsv(self.red, self.green, self.blue)
}
pub fn from_hsv(&self) -> Color {
let (r,g,b) = hsv2rgb(self.red, self.green, self.blue);
Color::new(r,g,b,1.0)
}
pub fn to_hsl(&self) -> (f64,f64,f64) {
rgb2hsl(self.red, self.green, self.blue)
}
pub fn from_hsl(&self) -> Color {
let (r,g,b) = hsl2rgb(self.red, self.green, self.blue);
Color::new(r,g,b,1.0)
}
pub fn to_yiq(&self) -> (f64,f64,f64) {
rgb2yiq(self.red, self.green, self.blue)
}
pub fn from_yiq(&self) -> Color {
let (r,g,b) = yiq2rgb(self.red, self.green, self.blue);
Color::new(r,g,b,1.0)
}
}
impl From<String> for Color {
fn from(s: String) -> Color {
match Color::name(&s) {
None => Color::from_hex(&s),
Some(c) => c
}
}
}
impl <'a> From<&'a String> for Color {
fn from(s: &'a String) -> Color {
match Color::name(s) {
None => Color::from_hex(s),
Some(c) => c
}
}
}
impl <'a> From<&'a str> for Color {
fn from(s: &'a str) -> Color {
match Color::name(s) {
None => Color::from_hex(s),
Some(c) => c
}
}
}
impl From<(u8,u8,u8)> for Color {
fn from(c: (u8,u8,u8)) -> Color {
Color::from_rgb255(c.0, c.1, c.2)
}
}
impl From<(f64,f64,f64)> for Color {
fn from(c: (f64,f64,f64)) -> Color {
Color::from_rgb1(c.0, c.1, c.2)
}
}
impl From<(f32,f32,f32)> for Color {
fn from(c: (f32,f32,f32)) -> Color {
Color::from_rgb1(c.0 as f64, c.1 as f64, c.2 as f64)
}
}
impl <'a> From<&'a [u8;3]> for Color {
fn from(c: &'a [u8;3]) -> Color {
Color::from_rgb255v(c)
}
}
impl <'a> From<&'a [f64;3]> for Color {
fn from(c: &'a [f64;3]) -> Color {
Color::from_rgb1v(c)
}
}
impl <'a> From<&'a [f32;3]> for Color {
fn from(c: &'a [f32;3]) -> Color {
Color::new(c[0] as f64, c[1] as f64, c[2] as f64, 1.0)
}
}
impl From<[u8;3]> for Color {
fn from(c: [u8;3]) -> Color {
Color::from_rgb255v(&c)
}
}
impl From<[f64;3]> for Color {
fn from(c: [f64;3]) -> Color {
Color::from_rgb1v(&c)
}
}
impl From<[f32;3]> for Color {
fn from(c: [f32;3]) -> Color {
Color::new(c[0] as f64, c[1] as f64, c[2] as f64, 1.0)
}
}
impl <'a> From<&'a Vec<f64>> for Color {
fn from(c: &'a Vec<f64>) -> Color {
match c.len() {
3 => Color::new(c[0], c[1], c[2], 1.0),
4 => Color::new(c[0], c[1], c[2], c[3]),
_ => panic!("Expected three or four color components"),
}
}
}
impl <'a> From<&'a Vec<f32>> for Color {
fn from(c: &'a Vec<f32>) -> Color {
let c64 : Vec<_> = c.into_iter().map(|x| *x as f64).collect();
Color::from(&c64)
}
}
impl From<Vec<f64>> for Color {
fn from(c: Vec<f64>) -> Color {
Color::from(&c)
}
}
impl From<Vec<f32>> for Color {
fn from(c: Vec<f32>) -> Color {
let c64 : Vec<_> = c.into_iter().map(|x| x as f64).collect();
Color::from(&c64)
}
}
impl PartialEq for Color {
fn eq(&self, other: &Color) -> bool {
self.red == other.red &&
self.blue == other.blue &&
self.green == other.green &&
self.alpha == other.alpha
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({:5.3}, {:5.3}, {:5.3}, {:5.3})", self.red, self.green, self.blue, self.alpha)
}
}
fn parse_rgb_name(line: &str) -> Option<(String, Vec<u8>)> {
let rgb : Vec<_>= line.split_whitespace().take(3)
.map(|x| x.parse::<u8>())
.collect();
let is_rgb = rgb.iter().all(|x| x.is_ok());
if is_rgb && rgb.len() == 3 {
let rgb = rgb.into_iter().map(|x| x.unwrap()).collect::<Vec<u8>>();
let name = line.split_whitespace()
.skip(3)
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(" ");
return Some((name, rgb));
}
None
}
fn parse_name_hex(line: &str) -> Option<(String, Color)> {
let vals = line.split('#').map(|x| x.trim()).collect::<Vec<&str>>();
if vals.len() == 2 {
let name = vals[0].to_owned();
let hex = vals[1];
if hex.len() == 6 {
return Some((name, Color::from_hex(hex)));
}
}
None
}
pub fn read_buffer<T>(buf: T) -> Vec<(String, Color)>
where T: BufRead
{
let mut out = vec![];
for xline in buf.lines() {
let line = xline.unwrap();
if let Some((name,rgb)) = parse_rgb_name(&line) {
out.push((name, Color::from_rgb255v(&rgb)));
} else if let Some((name, color)) = parse_name_hex(&line) {
out.push((name, color))
} else {
}
}
out
}
pub fn read_file<P>(file: P) -> Vec<(String, Color)>
where P: AsRef<Path>
{
let fp = File::open(file).unwrap();
let fp = BufReader::new(&fp);
read_buffer(fp)
}
pub fn load_rgb_buffer<T>(buf: T)
where T: BufRead
{
for (xname, color) in read_buffer(buf).into_iter() {
let name = xname.to_lowercase();
if COLOR_MAP.lock().unwrap().contains_key(&name) {
println!("warning: color already exists: {}", name);
continue;
}
COLOR_MAP.lock().unwrap().insert(name, color);
}
}
pub fn load_rgb_file<P>(file: P)
where P: AsRef<Path>
{
let fp = File::open(file).unwrap();
let fp = BufReader::new(&fp);
load_rgb_buffer(fp);
}
lazy_static! {
static ref COLOR_MAP: Mutex<HashMap<String, Color>> = {
let mut m : HashMap<String, Color> = HashMap::new();
for s in [COLORS_BASIC, COLORS_EXTENDED].iter() {
for (ref xname, color) in read_buffer( Cursor::new( s ) ).into_iter() {
let name = xname.to_lowercase();
if ! m.contains_key(&name) {
m.insert(name, color);
}
}
}
Mutex::new(m)
};
}
pub fn xkcd() {
load_rgb_buffer(Cursor::new(COLORS_XKCD));
}
pub fn names() -> Vec<String> {
let map = COLOR_MAP.lock().unwrap();
map.keys().cloned().collect()
}
fn cmp3(a: (f64,f64,f64), b:(f64,f64,f64)) -> std::cmp::Ordering {
if a.0 > b.0 {
return std::cmp::Ordering::Greater;
} else if a.0 < b.0 {
return std::cmp::Ordering::Less;
}
if a.1 > b.1 {
return std::cmp::Ordering::Greater;
} else if a.1 < b.1 {
return std::cmp::Ordering::Less;
}
if a.2 > b.2 {
return std::cmp::Ordering::Greater;
} else if a.2 < b.2 {
return std::cmp::Ordering::Less;
}
return std::cmp::Ordering::Equal;
}
pub fn compare_by_rgb(a: &Color, b: &Color) -> std::cmp::Ordering {
cmp3(a.to_rgb1(), b.to_rgb1())
}
pub fn compare_by_hsv(a: &Color, b: &Color) -> std::cmp::Ordering {
cmp3(a.to_hsv(),b.to_hsv())
}
fn rgb2yiq(r: f64, g: f64, b: f64) -> (f64,f64,f64) {
let y = 0.30 * r + 0.59 * g + 0.11 * b;
let i = 0.74 * (r-y) - 0.27 * (b-y);
let q = 0.48 * (r-y) + 0.41 * (b-y);
(y,i,q)
}
fn yiq2rgb(y: f64, i: f64, q:f64) -> (f64,f64,f64) {
let v33 = 1.7090069284064666;
let v32 = -1.1085450346420322;
let v22 = -0.27478764629897834;
let v23 = -0.6356910791873801;
let v13 = 0.6235565819861433;
let v12 = 0.9468822170900693;
let r = y + v12 * i + v13 * q;
let g = y + v22 * i + v23 * q;
let b = y + v32 * i + v33 * q;
(r,g,b)
}
fn fmin(v: &[f64]) -> f64 {
let mut val = v[0];
for vi in v { if *vi < val { val = *vi; } }
val
}
fn fmax(v: &[f64]) -> f64 {
let mut val = v[0];
for vi in v { if *vi > val { val = *vi; } }
val
}
fn rgb2hsv(r: f64, g: f64, b: f64) -> (f64,f64,f64) {
let cmax = fmax(&[r,g,b]);
let cmin = fmin(&[r,g,b]);
if (cmax - cmin).abs() < 1e-5 {
return (0.,0.,cmax);
}
let v = cmax;
let delta = cmax - cmin;
let s = delta / cmax;
let mut h = if r >= cmax {
(g - b) / delta
} else if g >= cmax {
2.0 + (b - r) / delta
} else if b >= cmax {
4.0 + (r - g) / delta
} else {
0.0
};
h *= 60.0;
if h < 0.0 {
h += 360.0;
}
(h, s, v)
}
fn hsv2rgb(h: f64, s: f64, v: f64) -> (f64, f64, f64) {
if s <= 0.0 {
return (v,v,v);
}
let mut hh = h;
if hh >= 360.0 {
hh = 0.0;
}
hh = hh / 60.0;
let i = hh.floor() as u64;
let ff = hh - i as f64;
let p = v * (1.0 - s);
let q = v * (1.0 - (s * ff));
let t = v * (1.0 - (s * (1.0 - ff)));
match i {
0 => (v,t,p),
1 => (q,v,p),
2 => (p,v,t),
3 => (p,q,v),
4 => (t,p,v),
5 => (v,p,q),
_ => panic!("Unexpected value in hsv2rgb: i: {} h: {}", i, h),
}
}
fn rgb2hsl(r: f64, g: f64, b: f64) -> (f64, f64, f64) {
let cmax = fmax(&[r,g,b]);
let cmin = fmin(&[r,g,b]);
let l = (cmax + cmin)/2.0;
if (cmax - cmin).abs() <= 1e-5 {
return (0., 0., l);
}
let d = cmax - cmin;
let mut s = d / (2.0 - cmax - cmin);
if l <= 0.5 {
s = d / (cmax + cmin);
}
let mut h =
if r >= cmax {
let dh = if g < b { 6.0 } else { 0.0 };
(g - b) / d + dh
} else if g >= cmax {
2.0 + (b - r) / d
} else if b >= cmax {
4.0 + (r - g) / d
} else {
0.0
};
h = h / 6.0;
(h,s,l)
}
fn hue2rgb(p: f64, q: f64, t: f64) -> f64 {
let mut tt = t;
if tt < 0.0 {
tt += 1.0;
}
if tt > 1.0 {
tt -= 1.0
}
if tt < 1./6. {
return p + (q - p) * 6.0 * tt;
}
if tt < 1./2. {
return q;
}
if tt < 2./3. {
return p + (q - p) * (2./3. - tt) * 6.0;
}
return p;
}
fn hsl2rgb(h: f64, s: f64, l: f64) -> (f64, f64, f64) {
if s.abs() <= 1e-5 {
return (l,l,l);
}
let q =
if l < 0.5 {
l * (1.0 + s)
} else {
l + s - (l * s)
};
let p = 2.0 * l - q;
let r = hue2rgb(p, q, h + 1./3.);
let g = hue2rgb(p, q, h);
let b = hue2rgb(p, q, h - 1./3.);
(r,g,b)
}
static COLORS_BASIC: &'static str = include_str!("w3c_basic.txt");
static COLORS_EXTENDED: &'static str = include_str!("w3c_extended.txt");
static COLORS_XKCD: &'static str = include_str!("xkcd.txt");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
assert_eq!(Color::name("black"), Some(Color::new(0.,0.,0.,1.)));
assert_eq!(Color::name("white"), Some(Color::new(1.,1.,1.,1.)));
assert_eq!(Color::name("red"), Some(Color::new(1.,0.,0.,1.)));
assert_eq!(Color::name("green"), Some(Color::new(0.,128./255.,0.,1.)));
assert_eq!(Color::name("blue"), Some(Color::new(0.,0.,1.,1.)));
assert_eq!(Color::name("yellow"), Some(Color::new(1.,1.,0.,1.)));
assert_eq!(Color::name("cyan"), Some(Color::new(0.,1.,1.,1.)));
assert_eq!(Color::name("orange"), Some(Color::new(1.,165./255.,0.,1.)));
assert_eq!(Color::name("fuchsia"), Some(Color::new(1.,0.,1.,1.)));
for name in ["black","silver","gray","white","maroon", "red","purple",
"fuchsia","green","lime","olive","yellow","navy","blue",
"teal","aqua"].iter() {
assert_eq!(Color::name(name).is_some(), true);
}
}
#[test]
fn hex() {
assert_eq!(Color::name("black").unwrap().to_hex(), "#000000");
assert_eq!(Color::name("silver").unwrap().to_hex(), "#c0c0c0");
assert_eq!(Color::name("gray").unwrap().to_hex(), "#808080");
assert_eq!(Color::name("white").unwrap().to_hex(), "#ffffff");
assert_eq!(Color::name("maroon").unwrap().to_hex(), "#800000");
assert_eq!(Color::name("red").unwrap().to_hex(), "#ff0000");
assert_eq!(Color::name("purple").unwrap().to_hex(), "#800080");
assert_eq!(Color::name("fuchsia").unwrap().to_hex(), "#ff00ff");
assert_eq!(Color::name("green").unwrap().to_hex(), "#008000");
assert_eq!(Color::name("lime").unwrap().to_hex(), "#00ff00");
assert_eq!(Color::name("olive").unwrap().to_hex(), "#808000");
assert_eq!(Color::name("yellow").unwrap().to_hex(), "#ffff00");
assert_eq!(Color::name("navy").unwrap().to_hex(), "#000080");
assert_eq!(Color::name("blue").unwrap().to_hex(), "#0000ff");
assert_eq!(Color::name("teal").unwrap().to_hex(), "#008080");
assert_eq!(Color::name("aqua").unwrap().to_hex(), "#00ffff");
assert_eq!(Color::name("cyan").unwrap().to_hex(), "#00ffff");
assert_eq!(Color::name("orange").unwrap().to_hex(), "#ffa500");
}
#[test]
fn extended() {
let ext_names = ["aliceblue","antiquewhite","aqua","aquamarine","azure",
"beige","bisque","black","blanchedalmond","blue","blueviolet",
"brown","burlywood","cadetblue","chartreuse","chocolate",
"coral","cornflowerblue","cornsilk","crimson","cyan",
"darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen",
"darkgrey","darkkhaki","darkmagenta","darkolivegreen",
"darkorange","darkorchid","darkred","darksalmon",
"darkseagreen","darkslateblue","darkslategray","darkslategrey",
"darkturquoise","darkviolet","deeppink","deepskyblue","dimgray",
"dimgrey","dodgerblue","firebrick","floralwhite","forestgreen",
"fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray",
"green","greenyellow","grey","honeydew","hotpink","indianred",
"indigo","ivory","khaki","lavender","lavenderblush","lawngreen",
"lemonchiffon","lightblue","lightcoral","lightcyan",
"lightgoldenrodyellow","lightgray","lightgreen","lightgrey",
"lightpink","lightsalmon","lightseagreen","lightskyblue",
"lightslategray","lightslategrey","lightsteelblue","lightyellow",
"lime","limegreen","linen","magenta","maroon","mediumaquamarine",
"mediumblue","mediumorchid","mediumpurple","mediumseagreen",
"mediumslateblue","mediumspringgreen","mediumturquoise",
"mediumvioletred","midnightblue","mintcream","mistyrose",
"moccasin","navajowhite","navy","oldlace","olive","olivedrab",
"orange","orangered","orchid","palegoldenrod","palegreen",
"paleturquoise","palevioletred","papayawhip","peachpuff","peru",
"pink","plum","powderblue","purple","red","rosybrown","royalblue",
"saddlebrown","salmon","sandybrown","seagreen","seashell",
"sienna","silver","skyblue","slateblue","slategray","slategrey",
"snow","springgreen","steelblue","tan","teal","thistle","tomato",
"turquoise","violet","wheat","white","whitesmoke","yellow",
"yellowgreen"];
for name in ext_names.iter() {
assert_eq!(Color::name(name).is_some(), true);
}
}
#[test]
fn bad_name() {
assert_eq!(Color::name("asdf").is_none(), true);
}
#[test]
fn test_xkcd() {
xkcd();
assert_eq!(Color::name("toxic green").is_some(), true);
assert_eq!(Color::name("blood").is_some(), true);
assert_eq!(Color::name("vomit").is_some(), true);
assert_eq!(Color::name("baby poop").is_some(), true);
}
#[test]
fn test_from() {
let red = Color::name("red").unwrap();
assert_eq!(Color::from("#ff0000"), red);
assert_eq!(Color::from("#ff0000".to_string()), red);
assert_eq!(Color::from((255,0,0)), red);
assert_eq!(Color::from(&[255,0,0]), red);
assert_eq!(Color::from(&[1.,0.,0.]), red);
let rgb = vec![1.,0.,0.];
assert_eq!(Color::from(&rgb), red);
assert_eq!(Color::from(rgb), red);
let rgb = vec![1.0f32,0.,0.];
assert_eq!(Color::from(&rgb), red);
assert_eq!(Color::from(rgb), red);
assert_eq!(Color::from("red"), red);
assert_eq!(Color::from("red".to_string()), red);
}
#[test]
fn test_into() {
let red = Color::name("red").unwrap();
assert_eq!(red, (255,0,0).into());
assert_eq!(red, "red".into());
assert_eq!(red, "#ff0000".into());
assert_eq!(red, (1.0,0.0,0.0).into());
}
#[test]
fn test_display() {
let red = Color::name("red").unwrap();
assert_eq!(format!("{}", red), "(1.000, 0.000, 0.000, 1.000)");
}
fn assert_tol(a: (f64,f64,f64), b: (f64,f64,f64), tol: f64) {
if (a.0-b.0).abs() > tol {
assert_eq!(a,b);
}
if (a.1-b.1).abs() > tol {
assert_eq!(a,b);
}
if (a.2-b.2).abs() > tol {
assert_eq!(a,b);
}
}
#[test]
#[ignore]
fn yiq() {
for r in 0..256 {
for g in 0..256 {
for b in 0..256 {
let rf = r as f64/ 255.0;
let gf = g as f64/ 255.0;
let bf = b as f64/ 255.0;
let (y,i,q) = rgb2yiq(rf,gf,bf);
let (r0,g0,b0) = yiq2rgb(y,i,q);
assert_tol((rf,gf,bf),(r0,g0,b0), 1e-13);
}
}
}
}
#[test]
#[ignore]
fn hsl() {
for r in 0..256 {
for g in 0..256 {
for b in 0..256 {
let rf = r as f64/ 255.0;
let gf = g as f64/ 255.0;
let bf = b as f64/ 255.0;
let (h,s,l) = rgb2hsl(rf,gf,bf);
let (r0,g0,b0) = hsl2rgb(h,s,l);
assert_tol((rf,gf,bf),(r0,g0,b0), 1e-13);
}
}
}
}
#[test]
#[ignore]
fn hsv() {
assert_eq!(rgb2hsv(0.,0.,0.), (0.,0.,0.));
assert_eq!(rgb2hsv(1.,0.,0.), (0.,1.,1.));
assert_eq!(rgb2hsv(0.,1.,0.), (120.,1.,1.));
assert_eq!(rgb2hsv(0.,0.,1.), (240.,1.,1.));
assert_eq!(rgb2hsv(1.,1.,0.), (60.,1.,1.));
assert_eq!(rgb2hsv(1.,0.,1.), (300.,1.,1.));
assert_eq!(rgb2hsv(0.,1.,1.), (180.,1.,1.));
assert_eq!(rgb2hsv(1.0,0.0,std::f64::EPSILON), (360.,1.0,1.0));
assert_eq!(rgb2hsv(1./255.,1./255.,2./255.), (240.,0.5, 2./255.));
assert_eq!(hsv2rgb(240.,0.5,2./255.), (1./255.,1./255., 2./255.));
assert_eq!(rgb2hsv(1./255.,0./255.,1./255.), (300.,1., 1./255.));
assert_eq!(hsv2rgb(300.,1.0,1./255.), (1./255.,0./255., 1./255.));
for r in 0..256 {
for g in 0..256 {
for b in 0..256 {
let rf = r as f64/ 255.0;
let gf = g as f64/ 255.0;
let bf = b as f64/ 255.0;
let (h,s,v) = rgb2hsv(rf,gf,bf);
let (r0,g0,b0) = hsv2rgb(h,s,v);
assert_tol((rf,gf,bf),(r0,g0,b0), 1e-13);
}
}
}
}
}