use std::f32::consts::PI;
use utils::{degrees, fmod, turns};
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum Color {
Rgba(f32, f32, f32, f32),
Hsla(f32, f32, f32, f32),
}
pub type Colour = Color;
#[inline]
pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
Color::Rgba(r, g, b, a)
}
#[inline]
pub fn rgb(r: f32, g: f32, b: f32) -> Color {
Color::Rgba(r, g, b, 1.0)
}
#[inline]
pub fn rgba_bytes(r: u8, g: u8, b: u8, a: f32) -> Color {
Color::Rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
}
#[inline]
pub fn rgb_bytes(r: u8, g: u8, b: u8) -> Color {
rgba_bytes(r, g, b, 1.0)
}
#[inline]
pub fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {
Color::Hsla(hue - turns((hue / (2.0 * PI)).floor()), saturation, lightness, alpha)
}
#[inline]
pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Color {
hsla(hue, saturation, lightness, 1.0)
}
pub fn grayscale(p: f32) -> Color {
Color::Hsla(0.0, 0.0, 1.0-p, 1.0)
}
pub fn greyscale(p: f32) -> Color {
Color::Hsla(0.0, 0.0, 1.0-p, 1.0)
}
fn clampf32(f: f32) -> f32 {
if f < 0.0 { 0.0 } else if f > 1.0 { 1.0 } else { f }
}
impl Color {
pub fn complement(self) -> Color {
match self {
Color::Hsla(h, s, l, a) => hsla(h + degrees(180.0), s, l, a),
Color::Rgba(r, g, b, a) => {
let (h, s, l) = rgb_to_hsl(r, g, b);
hsla(h + degrees(180.0), s, l, a)
},
}
}
pub fn luminance(&self) -> f32 {
match *self {
Color::Rgba(r, g, b, _) => (r + g + b) / 3.0,
Color::Hsla(_, _, l, _) => l,
}
}
pub fn plain_contrast(self) -> Color {
match self {
Color::Hsla(h, s, l, _) => {
let (r, g, b) = hsl_to_rgb(h, s, l);
rgb(r, g, b).plain_contrast()
},
Color::Rgba(r, g, b, _) => {
let l = 0.2126 * r
+ 0.7152 * g
+ 0.0722 * b;
if l > 0.5 { BLACK } else { WHITE }
}
}
}
pub fn to_hsl(self) -> Hsla {
match self {
Color::Hsla(h, s, l, a) => Hsla(h, s, l, a),
Color::Rgba(r, g, b, a) => {
let (h, s, l) = rgb_to_hsl(r, g, b);
Hsla(h, s, l, a)
},
}
}
pub fn to_rgb(self) -> Rgba {
match self {
Color::Rgba(r, g, b, a) => Rgba(r, g, b, a),
Color::Hsla(h, s, l, a) => {
let (r, g, b) = hsl_to_rgb(h, s, l);
Rgba(r, g, b, a)
},
}
}
pub fn to_fsa(self) -> [f32; 4] {
let Rgba(r, g, b, a) = self.to_rgb();
[r, g, b, a]
}
pub fn to_byte_fsa(self) -> [u8; 4] {
let Rgba(r, g, b, a) = self.to_rgb();
[f32_to_byte(r), f32_to_byte(g), f32_to_byte(b), f32_to_byte(a)]
}
pub fn with_luminance(self, l: f32) -> Color {
let Hsla(h, s, _, a) = self.to_hsl();
Color::Hsla(h, s, l, a)
}
pub fn alpha(self, alpha: f32) -> Color {
match self {
Color::Rgba(r, g, b, a) => Color::Rgba(r, g, b, a * alpha),
Color::Hsla(h, s, l, a) => Color::Hsla(h, s, l, a * alpha),
}
}
pub fn with_alpha(self, a: f32) -> Color {
match self {
Color::Rgba(r, g, b, _) => Color::Rgba(r, g, b, a),
Color::Hsla(h, s, l, _) => Color::Hsla(h, s, l, a),
}
}
pub fn highlighted(self) -> Color {
let luminance = self.luminance();
let Rgba(r, g, b, a) = self.to_rgb();
let (r, g, b) = {
if luminance > 0.8 { (r - 0.2, g - 0.2, b - 0.2) }
else if luminance < 0.2 { (r + 0.2, g + 0.2, b + 0.2) }
else {
(clampf32((1.0 - r) * 0.5 * r + r),
clampf32((1.0 - g) * 0.1 * g + g),
clampf32((1.0 - b) * 0.1 * b + b))
}
};
let a = clampf32((1.0 - a) * 0.5 + a);
rgba(r, g, b, a)
}
pub fn clicked(&self) -> Color {
let luminance = self.luminance();
let Rgba(r, g, b, a) = self.to_rgb();
let (r, g, b) = {
if luminance > 0.8 { (r , g - 0.2, b - 0.2) }
else if luminance < 0.2 { (r + 0.4, g + 0.2, b + 0.2) }
else {
(clampf32((1.0 - r) * 0.75 + r),
clampf32((1.0 - g) * 0.25 + g),
clampf32((1.0 - b) * 0.25 + b))
}
};
let a = clampf32((1.0 - a) * 0.75 + a);
rgba(r, g, b, a)
}
pub fn invert(self) -> Color {
let Rgba(r, g, b, a) = self.to_rgb();
rgba((r - 1.0).abs(), (g - 1.0).abs(), (b - 1.0).abs(), a)
}
pub fn red(&self) -> f32 {
let Rgba(r, _, _, _) = self.to_rgb();
r
}
pub fn green(&self) -> f32 {
let Rgba(_, g, _, _) = self.to_rgb();
g
}
pub fn blue(&self) -> f32 {
let Rgba(_, _, b, _) = self.to_rgb();
b
}
pub fn set_red(&mut self, r: f32) {
let Rgba(_, g, b, a) = self.to_rgb();
*self = rgba(r, g, b, a);
}
pub fn set_green(&mut self, g: f32) {
let Rgba(r, _, b, a) = self.to_rgb();
*self = rgba(r, g, b, a);
}
pub fn set_blue(&mut self, b: f32) {
let Rgba(r, g, _, a) = self.to_rgb();
*self = rgba(r, g, b, a);
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Hsla(pub f32, pub f32, pub f32, pub f32);
impl From<Color> for Hsla {
fn from(color: Color) -> Self {
color.to_hsl()
}
}
impl From<Hsla> for Color {
fn from(Hsla(h, s, l, a): Hsla) -> Self {
Color::Hsla(h, s, l, a)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Rgba(pub f32, pub f32, pub f32, pub f32);
impl From<Color> for Rgba {
fn from(color: Color) -> Self {
color.to_rgb()
}
}
impl From<Rgba> for Color {
fn from(Rgba(r, g, b, a): Rgba) -> Self {
Color::Rgba(r, g, b, a)
}
}
impl Into<[f32; 4]> for Rgba {
fn into(self) -> [f32; 4] {
let Rgba(r, g, b, a) = self;
[r, g, b, a]
}
}
#[inline]
pub fn f32_to_byte(c: f32) -> u8 { (c * 255.0) as u8 }
pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let c_max = r.max(g).max(b);
let c_min = r.min(g).min(b);
let c = c_max - c_min;
let hue = if c == 0.0 {
0.0
} else {
degrees(60.0) * if c_max == r { fmod((g - b) / c, 6) }
else if c_max == g { ((b - r) / c) + 2.0 }
else { ((r - g) / c) + 4.0 }
};
let lightness = (c_max + c_min) / 2.0;
let saturation = if lightness == 0.0 || lightness == 1.0 { 0.0 }
else { c / (1.0 - (2.0 * lightness - 1.0).abs()) };
(hue, saturation, lightness)
}
pub fn hsl_to_rgb(hue: f32, saturation: f32, lightness: f32) -> (f32, f32, f32) {
let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
let hue = hue / degrees(60.0);
let x = chroma * (1.0 - (fmod(hue, 2) - 1.0).abs());
let (r, g, b) = match hue {
hue if hue < 0.0 => (0.0, 0.0, 0.0),
hue if hue < 1.0 => (chroma, x, 0.0),
hue if hue < 2.0 => (x, chroma, 0.0),
hue if hue < 3.0 => (0.0, chroma, x),
hue if hue < 4.0 => (0.0, x, chroma),
hue if hue < 5.0 => (x, 0.0, chroma),
hue if hue < 6.0 => (chroma, 0.0, x),
_ => (0.0, 0.0, 0.0),
};
let m = lightness - chroma / 2.0;
(r + m, g + m, b + m)
}
#[derive(Clone, Debug)]
pub enum Gradient {
Linear((f64, f64), (f64, f64), Vec<(f64, Color)>),
Radial((f64, f64), f64, (f64, f64), f64, Vec<(f64, Color)>),
}
pub fn linear(start: (f64, f64), end: (f64, f64), colors: Vec<(f64, Color)>) -> Gradient {
Gradient::Linear(start, end, colors)
}
pub fn radial(start: (f64, f64), start_r: f64,
end: (f64, f64), end_r: f64,
colors: Vec<(f64, Color)>) -> Gradient {
Gradient::Radial(start, start_r, end, end_r, colors)
}
macro_rules! make_color {
($r:expr, $g:expr, $b:expr) => ( Color::Rgba($r as f32 / 255.0, $g as f32 / 255.0, $b as f32 / 255.0, 1.0));
($r:expr, $g:expr, $b:expr, $a:expr) => ( Color::Rgba($r as f32 / 255.0, $g as f32 / 255.0, $b as f32 / 255.0, $a as f32 / 255.0));
}
pub const LIGHT_RED : Color = make_color!(239, 41, 41);
pub const RED : Color = make_color!(204, 0, 0);
pub const DARK_RED : Color = make_color!(164, 0, 0);
pub const LIGHT_ORANGE : Color = make_color!(252, 175, 62);
pub const ORANGE : Color = make_color!(245, 121, 0);
pub const DARK_ORANGE : Color = make_color!(206, 92, 0);
pub const LIGHT_YELLOW : Color = make_color!(252, 233, 79);
pub const YELLOW : Color = make_color!(237, 212, 0);
pub const DARK_YELLOW : Color = make_color!(196, 160, 0);
pub const LIGHT_GREEN : Color = make_color!(138, 226, 52);
pub const GREEN : Color = make_color!(115, 210, 22);
pub const DARK_GREEN : Color = make_color!(78, 154, 6);
pub const LIGHT_BLUE : Color = make_color!(114, 159, 207);
pub const BLUE : Color = make_color!(52, 101, 164);
pub const DARK_BLUE : Color = make_color!(32, 74, 135);
pub const LIGHT_PURPLE : Color = make_color!(173, 127, 168);
pub const PURPLE : Color = make_color!(117, 80, 123);
pub const DARK_PURPLE : Color = make_color!(92, 53, 102);
pub const LIGHT_BROWN : Color = make_color!(233, 185, 110);
pub const BROWN : Color = make_color!(193, 125, 17);
pub const DARK_BROWN : Color = make_color!(143, 89, 2);
pub const BLACK : Color = make_color!(0, 0, 0);
pub const WHITE : Color = make_color!(255, 255, 255);
pub const LIGHT_GRAY : Color = make_color!(238, 238, 236);
pub const GRAY : Color = make_color!(211, 215, 207);
pub const DARK_GRAY : Color = make_color!(186, 189, 182);
pub const LIGHT_GREY : Color = make_color!(238, 238, 236);
pub const GREY : Color = make_color!(211, 215, 207);
pub const DARK_GREY : Color = make_color!(186, 189, 182);
pub const LIGHT_CHARCOAL : Color = make_color!(136, 138, 133);
pub const CHARCOAL : Color = make_color!(85, 87, 83);
pub const DARK_CHARCOAL : Color = make_color!(46, 52, 54);
pub const TRANSPARENT : Color = Color::Rgba(0.0, 0.0, 0.0, 0.0);
pub trait Colorable: Sized {
fn color(self, color: Color) -> Self;
fn rgba(self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.color(rgba(r, g, b, a))
}
fn rgb(self, r: f32, g: f32, b: f32) -> Self {
self.color(rgb(r, g, b))
}
fn hsla(self, h: f32, s: f32, l: f32, a: f32) -> Self {
self.color(hsla(h, s, l, a))
}
fn hsl(self, h: f32, s: f32, l: f32) -> Self {
self.color(hsl(h, s, l))
}
}
#[test]
fn plain_contrast_should_weight_colors() {
let white_contrast = rgb(1.0, 1.0, 1.0).plain_contrast();
let Rgba(r, g, b, _) = white_contrast.to_rgb();
assert_eq!(r, 0.0);
assert_eq!(g, 0.0);
assert_eq!(b, 0.0);
let black_contrast = rgb(0.0, 0.0, 0.0).plain_contrast();
let Rgba(r, g, b, _) = black_contrast.to_rgb();
assert_eq!(r, 1.0);
assert_eq!(g, 1.0);
assert_eq!(b, 1.0);
let greenish = rgb(0.29, 0.90, 0.29).plain_contrast();
let Rgba(r, g, b, _) = greenish.to_rgb();
assert_eq!(r, 0.0);
assert_eq!(g, 0.0);
assert_eq!(b, 0.0);
let purplish = rgb(0.71, 0.10, 0.71).plain_contrast();
let Rgba(r, g, b, _) = purplish.to_rgb();
assert_eq!(r, 1.0);
assert_eq!(g, 1.0);
assert_eq!(b, 1.0);
}