use palette::rgb::{Rgb, Rgba};
use palette::{Alpha, ConvertInto, Hsl, Lab, Mix};
pub mod encoding;
mod named;
pub type DisplayP3<T = f32> = Rgb<encoding::DisplayP3, T>;
pub type DisplayP3a<T = f32> = Rgba<encoding::DisplayP3, T>;
pub use palette::{Srgb, Srgba};
#[derive(Copy, Clone, Debug, PartialEq)]
enum ColorComponents {
Transparent,
StandardRGB(Srgb),
DisplayP3(DisplayP3),
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
components: ColorComponents,
alpha: f32,
}
impl Color {
pub fn rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Color {
Color {
components: ColorComponents::StandardRGB(Srgb::new(
red as f32 / 255.0,
green as f32 / 255.0,
blue as f32 / 255.0,
)),
alpha,
}
}
pub fn rgb(red: u8, green: u8, blue: u8) -> Color {
Self::rgba(red, green, blue, 1.0)
}
pub fn hexa(hex: u32, alpha: f32) -> Color {
Self::rgba(
((hex >> 16) & 0xff) as u8,
((hex >> 8) & 0xff) as u8,
((hex >> 0) & 0xff) as u8,
alpha,
)
}
pub fn hex(hex: u32) -> Color {
Self::hexa(hex, 1.0)
}
pub fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {
Color {
components: ColorComponents::StandardRGB(Hsl::new(hue, saturation, lightness).into()),
alpha,
}
}
pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Color {
Self::hsla(hue, saturation, lightness, 1.0)
}
pub fn display_p3_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Color {
Color {
components: ColorComponents::DisplayP3(DisplayP3::new(
red as f32 / 255.0,
green as f32 / 255.0,
blue as f32 / 255.0,
)),
alpha,
}
}
pub fn display_p3_rgb(red: u8, green: u8, blue: u8) -> Color {
Self::display_p3_rgba(red, green, blue, 1.0)
}
pub fn transparent() -> Color {
Color {
components: ColorComponents::Transparent,
alpha: 0.0,
}
}
pub fn alpha(&self) -> f32 {
self.alpha
}
pub fn mix(self, other: Color, factor: f32) -> Color {
let factor = factor.max(0.0).min(1.0);
Color {
components: ColorComponents::StandardRGB(Srgb::from_linear(
self.to_srgb()
.color
.into_linear()
.mix(&other.to_srgb().color.into_linear(), factor),
)),
alpha: self.alpha * factor + other.alpha * (1.0 - factor),
}
}
pub fn to_display_p3(&self) -> DisplayP3a {
Alpha {
color: match self.components {
ColorComponents::StandardRGB(rgb) => {
let lab: Lab = rgb.convert_into();
lab.convert_into()
}
ColorComponents::DisplayP3(rgb) => rgb,
ColorComponents::Transparent => DisplayP3::new(0.0, 0.0, 0.0),
},
alpha: self.alpha,
}
}
pub fn to_srgb(&self) -> Srgba {
Alpha {
color: match self.components {
ColorComponents::StandardRGB(rgb) => rgb,
ColorComponents::DisplayP3(rgb) => {
let lab: Lab = rgb.convert_into();
lab.convert_into()
}
ColorComponents::Transparent => Srgb::new(0.0, 0.0, 0.0),
},
alpha: self.alpha,
}
}
pub fn to_hex(&self) -> u32 {
let srgb = self.to_srgb();
(((srgb.red * 255.0) as u32) << 16)
| (((srgb.green * 255.0) as u32) << 8)
| (((srgb.blue * 255.0) as u32) << 0)
}
}
impl Default for Color {
fn default() -> Self {
Color::transparent()
}
}
pub use named::NamedColor;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_srgb_to_display_p3() {
let color = Color::rgb(255, 0, 0);
let p3 = color.to_display_p3();
assert!((p3.red - 234.0 / 255.0) < (1.0 / 255.0));
assert!((p3.green - 51.0 / 255.0) < (1.0 / 255.0));
assert!((p3.blue - 35.0 / 255.0) < (1.0 / 255.0));
}
#[test]
fn test_display_p3_to_srgb() {
let color = Color::display_p3_rgb(234, 51, 35);
assert_eq!(color.to_srgb(), Srgba::new(1.0, 0.0, 0.0, 1.0));
}
#[test]
fn test_named_color() {
use NamedColor;
let color = Color::coral();
assert_eq!(color, Color::rgb(255, 127, 80));
}
#[test]
fn test_gamma_correct_blending() {
let blue = Color::hex(0x0073FF);
let red = Color::hex(0xFF003D);
let mix = blue.mix(red, 0.5);
assert_eq!(mix.to_hex(), 0xBB52BF);
}
}