use crate::ColorModel;
use crate::chan::{
Ch8, Ch16, Ch32, Channel, Linear, Premultiplied, Srgb, Straight,
};
use crate::el::{Pix, PixRgba, Pixel};
use crate::hue::{Hexcone, rgb_to_hue_chroma_value};
use std::ops::Range;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Hsv {}
impl Hsv {
pub fn hue<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<0>()
}
pub fn hue_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<0>()
}
pub fn saturation<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<1>()
}
pub fn saturation_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<1>()
}
pub fn value<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<2>()
}
pub fn value_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<2>()
}
}
impl ColorModel for Hsv {
const CIRCULAR: Range<usize> = 0..1;
const LINEAR: Range<usize> = 1..3;
const ALPHA: usize = 3;
fn into_rgba<P>(p: P) -> PixRgba<P>
where
P: Pixel<Model = Self>,
{
let v = Self::value(p);
let chroma = v * Self::saturation(p);
let hp = Self::hue(p).to_f32() * 6.0; let hc = Hexcone::from_hue_prime(hp);
let (red, green, blue) = hc.rgb(chroma);
let m = v - chroma;
PixRgba::<P>::new::<P::Chan>(red + m, green + m, blue + m, p.alpha())
}
fn from_rgba<P>(rgba: PixRgba<P>) -> P
where
P: Pixel<Model = Self>,
{
let chan = rgba.channels();
let red = chan[0];
let green = chan[1];
let blue = chan[2];
let alpha = chan[3];
let (hue, chroma, val) = rgb_to_hue_chroma_value(red, green, blue);
let sat_v = chroma / val;
P::from_channels(&[hue, sat_v, val, alpha])
}
}
pub type Hsv8 = Pix<3, Ch8, Hsv, Straight, Linear>;
pub type Hsv16 = Pix<3, Ch16, Hsv, Straight, Linear>;
pub type Hsv32 = Pix<3, Ch32, Hsv, Straight, Linear>;
pub type Hsva8 = Pix<4, Ch8, Hsv, Straight, Linear>;
pub type Hsva16 = Pix<4, Ch16, Hsv, Straight, Linear>;
pub type Hsva32 = Pix<4, Ch32, Hsv, Straight, Linear>;
pub type Hsva8p = Pix<4, Ch8, Hsv, Premultiplied, Linear>;
pub type Hsva16p = Pix<4, Ch16, Hsv, Premultiplied, Linear>;
pub type Hsva32p = Pix<4, Ch32, Hsv, Premultiplied, Linear>;
pub type SHsv8 = Pix<3, Ch8, Hsv, Straight, Srgb>;
pub type SHsv16 = Pix<3, Ch16, Hsv, Straight, Srgb>;
pub type SHsv32 = Pix<3, Ch32, Hsv, Straight, Srgb>;
pub type SHsva8 = Pix<4, Ch8, Hsv, Straight, Srgb>;
pub type SHsva16 = Pix<4, Ch16, Hsv, Straight, Srgb>;
pub type SHsva32 = Pix<4, Ch32, Hsv, Straight, Srgb>;
pub type SHsva8p = Pix<4, Ch8, Hsv, Premultiplied, Srgb>;
pub type SHsva16p = Pix<4, Ch16, Hsv, Premultiplied, Srgb>;
pub type SHsva32p = Pix<4, Ch32, Hsv, Premultiplied, Srgb>;
#[cfg(test)]
mod test {
use crate::el::Pixel;
use crate::hsv::*;
use crate::ops::*;
use crate::rgb::*;
#[test]
fn hsv_to_rgb() {
assert_eq!(Rgb8::new(255, 0, 0), Hsv8::new(0, 255, 255).convert());
assert_eq!(
Rgb8::new(255, 255, 0),
Hsv32::new(60.0 / 360.0, 1.0, 1.0).convert(),
);
assert_eq!(
Rgb8::new(0, 255, 0),
Hsv16::new(21845, 65535, 65535).convert(),
);
assert_eq!(Rgb8::new(0, 255, 255), Hsv32::new(0.5, 1.0, 1.0).convert());
assert_eq!(
Rgb8::new(0, 0, 255),
Hsv32::new(240.0 / 360.0, 1.0, 1.0).convert(),
);
assert_eq!(
Rgb8::new(255, 0, 255),
Hsv32::new(300.0 / 360.0, 1.0, 1.0).convert(),
);
}
#[test]
fn hsv_to_rgb_unsat() {
assert_eq!(Rgb8::new(255, 127, 127), Hsv8::new(0, 128, 255).convert());
assert_eq!(
Rgb8::new(255, 255, 128),
Hsv32::new(60.0 / 360.0, 0.5, 1.0).convert(),
);
assert_eq!(
Rgb8::new(127, 255, 127),
Hsv16::new(21845, 32768, 65535).convert(),
);
assert_eq!(
Rgb8::new(128, 255, 255),
Hsv32::new(180.0 / 360.0, 0.5, 1.0).convert(),
);
assert_eq!(
Rgb8::new(128, 128, 255),
Hsv32::new(240.0 / 360.0, 0.5, 1.0).convert(),
);
assert_eq!(
Rgb8::new(255, 128, 255),
Hsv32::new(300.0 / 360.0, 0.5, 1.0).convert(),
);
}
#[test]
fn hsv_to_rgb_dark() {
assert_eq!(Rgb8::new(128, 0, 0), Hsv8::new(0, 255, 128).convert());
assert_eq!(
Rgb8::new(128, 128, 0),
Hsv32::new(60.0 / 360.0, 1.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(0, 128, 0),
Hsv16::new(21845, 65535, 32768).convert(),
);
assert_eq!(
Rgb8::new(0, 128, 128),
Hsv32::new(180.0 / 360.0, 1.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(0, 0, 128),
Hsv32::new(240.0 / 360.0, 1.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(128, 0, 128),
Hsv32::new(300.0 / 360.0, 1.0, 0.5).convert(),
);
}
#[test]
fn hsv_to_rgb_hue() {
assert_eq!(Rgb8::new(255, 192, 0), Hsv8::new(32, 255, 255).convert());
assert_eq!(Rgb8::new(126, 255, 0), Hsv8::new(64, 255, 255).convert());
assert_eq!(Rgb8::new(0, 255, 66), Hsv8::new(96, 255, 255).convert());
assert_eq!(Rgb8::new(0, 60, 255), Hsv8::new(160, 255, 255).convert());
assert_eq!(Rgb8::new(132, 0, 255), Hsv8::new(192, 255, 255).convert());
assert_eq!(Rgb8::new(255, 0, 186), Hsv8::new(224, 255, 255).convert());
}
#[test]
fn hsv_to_rgb_grays() {
assert_eq!(Rgb8::new(255, 255, 255), Hsv8::new(0, 0, 255).convert());
assert_eq!(Rgb8::new(128, 128, 128), Hsv8::new(100, 0, 128).convert());
assert_eq!(Hsv8::new(0, 0, 255), Rgb8::new(255, 255, 255).convert());
assert_eq!(Hsv8::new(0, 0, 128), Rgb8::new(128, 128, 128).convert());
}
#[test]
fn rgb_to_hsv() {
assert_eq!(Hsv8::new(0, 255, 255), Rgb8::new(255, 0, 0).convert());
assert_eq!(
Hsv32::new(60.0 / 360.0, 1.0, 1.0),
Rgb8::new(255, 255, 0).convert(),
);
assert_eq!(
Hsv16::new(21845, 65535, 65535),
Rgb8::new(0, 255, 0).convert(),
);
assert_eq!(Hsv32::new(0.5, 1.0, 1.0), Rgb8::new(0, 255, 255).convert());
assert_eq!(
Hsv32::new(240.0 / 360.0, 1.0, 1.0),
Rgb8::new(0, 0, 255).convert(),
);
assert_eq!(
Hsv32::new(300.0 / 360.0, 1.0, 1.0),
Rgb8::new(255, 0, 255).convert(),
);
}
#[test]
fn rgb_to_hsv_unsat() {
assert_eq!(Hsv8::new(0, 128, 255), Rgb8::new(255, 127, 127).convert());
assert_eq!(Hsv8::new(42, 128, 255), Rgb8::new(255, 255, 127).convert());
assert_eq!(Hsv8::new(85, 127, 255), Rgb8::new(128, 255, 128).convert());
assert_eq!(
Hsv8::new(128, 127, 255),
Rgb8::new(128, 255, 255).convert()
);
assert_eq!(
Hsv8::new(170, 127, 255),
Rgb8::new(128, 128, 255).convert(),
);
assert_eq!(
Hsv8::new(213, 127, 255),
Rgb8::new(255, 128, 255).convert(),
);
}
#[test]
fn composite_hsv() {
let mut a = Hsva8p::new(0, 64, 64, 128);
a.composite_channels(&Hsva8p::new(32, 128, 64, 128), SrcOver);
assert_eq!(a, Hsva8p::new(16, 159, 95, 191));
}
}