pub const RED: usize = 0;
pub const GREEN: usize = 1;
pub const BLUE: usize = 2;
pub const ALPHA: usize = 3;
pub const RED_SHIFT: u32 = 24;
pub const GREEN_SHIFT: u32 = 16;
pub const BLUE_SHIFT: u32 = 8;
pub const ALPHA_SHIFT: u32 = 0;
#[inline]
pub fn red(pixel: u32) -> u8 {
((pixel >> RED_SHIFT) & 0xff) as u8
}
#[inline]
pub fn green(pixel: u32) -> u8 {
((pixel >> GREEN_SHIFT) & 0xff) as u8
}
#[inline]
pub fn blue(pixel: u32) -> u8 {
((pixel >> BLUE_SHIFT) & 0xff) as u8
}
#[inline]
pub fn alpha(pixel: u32) -> u8 {
((pixel >> ALPHA_SHIFT) & 0xff) as u8
}
#[inline]
pub fn compose_rgb(r: u8, g: u8, b: u8) -> u32 {
((r as u32) << RED_SHIFT)
| ((g as u32) << GREEN_SHIFT)
| ((b as u32) << BLUE_SHIFT)
| (255 << ALPHA_SHIFT)
}
#[inline]
pub fn compose_rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
((r as u32) << RED_SHIFT)
| ((g as u32) << GREEN_SHIFT)
| ((b as u32) << BLUE_SHIFT)
| ((a as u32) << ALPHA_SHIFT)
}
#[inline]
pub fn extract_rgb(pixel: u32) -> (u8, u8, u8) {
(red(pixel), green(pixel), blue(pixel))
}
#[inline]
pub fn extract_rgba(pixel: u32) -> (u8, u8, u8, u8) {
(red(pixel), green(pixel), blue(pixel), alpha(pixel))
}
#[inline]
pub fn extract_min_component(pixel: u32) -> u8 {
let (r, g, b) = extract_rgb(pixel);
r.min(g).min(b)
}
#[inline]
pub fn extract_max_component(pixel: u32) -> u8 {
let (r, g, b) = extract_rgb(pixel);
r.max(g).max(b)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hsv {
pub h: i32,
pub s: i32,
pub v: i32,
}
pub fn rgb_to_hsv(r: u8, g: u8, b: u8) -> Hsv {
let ri = r as i32;
let gi = g as i32;
let bi = b as i32;
let min = ri.min(gi).min(bi);
let max = ri.max(gi).max(bi);
let delta = max - min;
let v = max;
if delta == 0 {
return Hsv { h: 0, s: 0, v };
}
let s = (255.0 * delta as f32 / max as f32 + 0.5) as i32;
let h_raw = if ri == max {
(gi - bi) as f32 / delta as f32
} else if gi == max {
2.0 + (bi - ri) as f32 / delta as f32
} else {
4.0 + (ri - gi) as f32 / delta as f32
};
let mut h = h_raw * 40.0;
if h < 0.0 {
h += 240.0;
}
if h >= 239.5 {
h = 0.0;
}
let h = (h + 0.5) as i32;
Hsv { h, s, v }
}
pub fn hsv_to_rgb(hsv: Hsv) -> (u8, u8, u8) {
let Hsv {
mut h,
s: sval,
v: vval,
} = hsv;
if sval == 0 {
return (vval as u8, vval as u8, vval as u8);
}
if h == 240 {
h = 0;
}
let hf = h as f32 / 40.0;
let i = hf as i32;
let f = hf - i as f32;
let s = sval as f32 / 255.0;
let x = (vval as f32 * (1.0 - s) + 0.5) as i32;
let y = (vval as f32 * (1.0 - s * f) + 0.5) as i32;
let z = (vval as f32 * (1.0 - s * (1.0 - f)) + 0.5) as i32;
let (r, g, b) = match i {
0 => (vval, z, x),
1 => (y, vval, x),
2 => (x, vval, z),
3 => (x, y, vval),
4 => (z, x, vval),
5 => (vval, x, y),
_ => (0, 0, 0),
};
(r as u8, g as u8, b as u8)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgb_to_hsv_pure_red() {
let hsv = rgb_to_hsv(255, 0, 0);
assert_eq!(hsv.h, 0);
assert_eq!(hsv.s, 255);
assert_eq!(hsv.v, 255);
}
#[test]
fn test_rgb_to_hsv_pure_green() {
let hsv = rgb_to_hsv(0, 255, 0);
assert_eq!(hsv.h, 80);
assert_eq!(hsv.s, 255);
assert_eq!(hsv.v, 255);
}
#[test]
fn test_rgb_to_hsv_pure_blue() {
let hsv = rgb_to_hsv(0, 0, 255);
assert_eq!(hsv.h, 160);
assert_eq!(hsv.s, 255);
assert_eq!(hsv.v, 255);
}
#[test]
fn test_rgb_to_hsv_gray() {
let hsv = rgb_to_hsv(128, 128, 128);
assert_eq!(hsv.h, 0);
assert_eq!(hsv.s, 0);
assert_eq!(hsv.v, 128);
}
#[test]
fn test_rgb_to_hsv_black() {
let hsv = rgb_to_hsv(0, 0, 0);
assert_eq!(hsv.h, 0);
assert_eq!(hsv.s, 0);
assert_eq!(hsv.v, 0);
}
#[test]
fn test_rgb_to_hsv_white() {
let hsv = rgb_to_hsv(255, 255, 255);
assert_eq!(hsv.h, 0);
assert_eq!(hsv.s, 0);
assert_eq!(hsv.v, 255);
}
#[test]
fn test_hsv_roundtrip() {
let colors = [
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(255, 255, 0),
(0, 255, 255),
(128, 64, 32),
];
for (r, g, b) in colors {
let hsv = rgb_to_hsv(r, g, b);
let (rr, rg, rb) = hsv_to_rgb(hsv);
assert!(
(rr as i32 - r as i32).abs() <= 1
&& (rg as i32 - g as i32).abs() <= 1
&& (rb as i32 - b as i32).abs() <= 1,
"roundtrip failed for ({r},{g},{b}): got ({rr},{rg},{rb})"
);
}
}
#[test]
fn test_hsv_to_rgb_gray() {
let (r, g, b) = hsv_to_rgb(Hsv { h: 0, s: 0, v: 128 });
assert_eq!((r, g, b), (128, 128, 128));
}
#[test]
fn test_extract_min_component() {
let pixel = compose_rgba(100, 50, 200, 255);
assert_eq!(extract_min_component(pixel), 50);
}
#[test]
fn test_extract_max_component() {
let pixel = compose_rgba(100, 50, 200, 255);
assert_eq!(extract_max_component(pixel), 200);
}
#[test]
fn test_extract_min_max_equal() {
let pixel = compose_rgba(128, 128, 128, 0);
assert_eq!(extract_min_component(pixel), 128);
assert_eq!(extract_max_component(pixel), 128);
}
}