use crate::space::math::{clamp, round};
pub trait Channel: Copy + Default {
fn to_f32(self) -> f32;
fn from_f32(v: f32) -> Self;
}
impl Channel for u8 {
fn to_f32(self) -> f32 {
f32::from(self)
}
fn from_f32(v: f32) -> Self {
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "v is clamped to [0, 255] before the cast"
)]
{
round(clamp(v, 0.0, 255.0)) as Self
}
}
}
impl Channel for u16 {
fn to_f32(self) -> f32 {
f32::from(self)
}
fn from_f32(v: f32) -> Self {
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "v is clamped to [0, 65535] before the cast"
)]
{
round(clamp(v, 0.0, 65_535.0)) as Self
}
}
}
impl Channel for f32 {
fn to_f32(self) -> f32 {
self
}
fn from_f32(v: f32) -> Self {
v
}
}
pub trait NativeMax: Channel {
const MAX: f32;
}
impl NativeMax for u8 {
const MAX: f32 = 255.0;
}
impl NativeMax for u16 {
const MAX: f32 = 65_535.0;
}
impl NativeMax for f32 {
const MAX: Self = 1.0;
}
pub trait RgbChannelScale: crate::rgb::RgbColor {
const RED_MAX: f32;
const GREEN_MAX: f32;
const BLUE_MAX: f32;
}
pub trait FromSrgb: RgbChannelScale + Default {
#[must_use]
fn from_srgb(c: crate::space::Srgb) -> Self
where
<Self as crate::rgb::HasRed>::Component: Channel,
<Self as crate::rgb::HasGreen>::Component: Channel,
<Self as crate::rgb::HasBlue>::Component: Channel,
{
crate::rgb::RgbColor::from_rgb(
Channel::from_f32(c.r.clamp(0.0, 1.0) * Self::RED_MAX),
Channel::from_f32(c.g.clamp(0.0, 1.0) * Self::GREEN_MAX),
Channel::from_f32(c.b.clamp(0.0, 1.0) * Self::BLUE_MAX),
)
}
}
impl<C: RgbChannelScale + Default> FromSrgb for C {}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn u8_roundtrip() {
assert_eq!(u8::from_f32(255.0.to_f32()), 255);
assert_eq!(u8::from_f32(0.0), 0);
assert_eq!(u8::from_f32(-10.0), 0);
assert_eq!(u8::from_f32(300.0), 255);
}
#[test]
fn u16_roundtrip() {
assert_eq!(u16::from_f32(65_535.0), 65_535);
assert_eq!(u16::from_f32(-1.0), 0);
}
#[test]
fn f32_identity() {
assert_eq!(f32::from_f32(0.25), 0.25);
assert_eq!(0.75f32.to_f32(), 0.75);
}
#[test]
fn native_max_values() {
assert_eq!(<u8 as NativeMax>::MAX, 255.0);
assert_eq!(<u16 as NativeMax>::MAX, 65_535.0);
assert_eq!(<f32 as NativeMax>::MAX, 1.0);
}
}