use std::fmt;
use std::num::ParseIntError;
const DEFAULT_ALPHA: u8 = 0xFF;
#[derive(Debug, Clone, Copy)]
pub enum ParseColorError {
InvalidCharCount(usize),
InvalidChar(u8),
}
#[repr(align(4))]
#[derive(PartialEq, Clone, Copy)]
pub struct Color {
value: u32,
}
impl Color {
pub fn new(value: u32) -> Self {
Color { value }
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Color::from_rgba(r, g, b, DEFAULT_ALPHA)
}
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Color::new(r as u32 | (g as u32) << 8 | (b as u32) << 16 | (a as u32) << 24)
}
pub fn red(&self) -> u32 {
self.value & 0xFF
}
pub fn green(&self) -> u32 {
(self.value & 0xFF00) >> 8
}
pub fn blue(&self) -> u32 {
(self.value & 0xFF0000) >> 16
}
pub fn alpha(&self) -> u32 {
(self.value & 0xFF000000) >> 24
}
pub fn from_hex(value: &str) -> Result<Self, ParseIntError> {
let mut raw = u32::from_str_radix(value, 16)?;
if value.len() <= 6 {
raw = (raw << 8) | 0xFF;
}
let color = Color::new(raw.to_be());
Ok(color)
}
pub fn from_hex_raw(value: &[u8]) -> Result<Self, ParseColorError> {
let len = value.len();
fn parse_char(input: u8) -> Result<u8, ParseColorError> {
if input >= b'a' && input <= b'f' {
Ok(input - b'a' + 10)
} else if input >= b'A' && input <= b'F' {
Ok(input - b'A' + 10)
} else if input >= b'0' && input <= b'9' {
Ok(input - b'0')
} else {
Err(ParseColorError::InvalidChar(input))
}
}
let build = || {
let mut raw_u32 = 0u32;
for char in value.iter() {
raw_u32 <<= 4;
raw_u32 |= parse_char(*char)? as u32;
}
Ok(raw_u32)
};
if len == 6 {
let mut value = build()?;
value = (value << 8) | 0xFF;
Ok(Color {
value: value.to_be(),
})
} else if len == 8 {
let value = build()?;
Ok(Color {
value: value.to_be(),
})
} else {
Err(ParseColorError::InvalidCharCount(len))
}
}
#[allow(dead_code)]
pub fn hex(&self) -> String {
format!("{:06X}", self.value.to_be() >> 8)
}
pub fn black() -> Self {
Color::from_rgb(0, 0, 0)
}
pub fn to_raw(&self) -> u32 {
self.value
}
pub fn blend(&mut self, other: Color) {
let mut r = other.red();
let mut g = other.green();
let mut b = other.blue();
let mut a = other.alpha();
if a == 0 {
return;
} else if a < u8::MAX as u32 {
let na = u8::MAX as u32 - a;
r = ((a * r) + (na * self.red())) / 0xFF;
g = ((a * g) + (na * self.green())) / 0xFF;
b = ((a * b) + (na * self.blue())) / 0xFF;
a = a + self.alpha();
}
self.value = r & 0xFF | (g & 0xFF) << 8 | (b & 0xFF) << 16 | (a & 0xFF) << 24;
}
}
impl fmt::Debug for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.alpha() == 0 {
write!(
f,
"ColorRGB({:X}, {:X}, {:X})",
self.red(),
self.green(),
self.blue()
)
} else {
write!(
f,
"ColorRGBA({:X}, {:X}, {:X}, {:X})",
self.red(),
self.green(),
self.blue(),
self.alpha()
)
}
}
}
#[test]
fn from_hex_raw() {
macro_rules! test {
($in: literal, $out: expr, $print: literal) => {
let color_raw = Color::from_hex_raw($in.as_bytes()).unwrap();
let color = Color::from_hex($in).unwrap();
assert_eq!(color, color_raw);
assert_eq!(Color::new($out), color_raw);
assert_eq!(format!("{:?}", color_raw), $print);
};
}
test!("ABCDEFBA", 0xBAEFCDAB, "ColorRGBA(AB, CD, EF, BA)");
test!("AABBCC", 0xFFCCBBAA, "ColorRGBA(AA, BB, CC, FF)");
test!("ABCDEF00", 0x00EFCDAB, "ColorRGB(AB, CD, EF)");
}