use core::fmt;
pub trait Sample:
Copy + Clone + Default + PartialEq + PartialOrd + fmt::Debug + Send + Sync
{
const ZERO: Self;
const ONE: Self;
const MIN: Self;
const MAX: Self;
const UNIT: f32;
fn as_f32(self) -> f32;
fn from_f32(v: f32) -> Self;
#[inline]
fn to_unit(self) -> f32 {
self.as_f32() / Self::UNIT
}
#[inline]
fn from_unit(v: f32) -> Self {
Self::from_f32(v * Self::UNIT)
}
fn saturating_add(self, rhs: Self) -> Self;
fn saturating_sub(self, rhs: Self) -> Self;
fn saturating_mul(self, rhs: Self) -> Self;
fn wrapping_add(self, rhs: Self) -> Self;
fn wrapping_sub(self, rhs: Self) -> Self;
fn wrapping_mul(self, rhs: Self) -> Self;
fn min(self, rhs: Self) -> Self;
fn max(self, rhs: Self) -> Self;
fn clamp(self, low: Self, high: Self) -> Self {
self.max(low).min(high)
}
}
impl Sample for u8 {
const ZERO: Self = 0;
const ONE: Self = 1;
const MIN: Self = u8::MIN;
const MAX: Self = u8::MAX;
const UNIT: f32 = 255.0;
#[inline]
fn as_f32(self) -> f32 {
self as f32
}
#[inline]
fn from_f32(v: f32) -> Self {
(v.clamp(0.0, 255.0) + 0.5) as u8
}
#[inline]
fn saturating_add(self, rhs: Self) -> Self {
self.saturating_add(rhs)
}
#[inline]
fn saturating_sub(self, rhs: Self) -> Self {
self.saturating_sub(rhs)
}
#[inline]
fn saturating_mul(self, rhs: Self) -> Self {
self.saturating_mul(rhs)
}
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
self.wrapping_add(rhs)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
self.wrapping_sub(rhs)
}
#[inline]
fn wrapping_mul(self, rhs: Self) -> Self {
self.wrapping_mul(rhs)
}
#[inline]
fn min(self, rhs: Self) -> Self {
Ord::min(self, rhs)
}
#[inline]
fn max(self, rhs: Self) -> Self {
Ord::max(self, rhs)
}
}
impl Sample for u16 {
const ZERO: Self = 0;
const ONE: Self = 1;
const MIN: Self = u16::MIN;
const MAX: Self = u16::MAX;
const UNIT: f32 = 65535.0;
#[inline]
fn as_f32(self) -> f32 {
self as f32
}
#[inline]
fn from_f32(v: f32) -> Self {
(v.clamp(0.0, 65535.0) + 0.5) as u16
}
#[inline]
fn saturating_add(self, rhs: Self) -> Self {
self.saturating_add(rhs)
}
#[inline]
fn saturating_sub(self, rhs: Self) -> Self {
self.saturating_sub(rhs)
}
#[inline]
fn saturating_mul(self, rhs: Self) -> Self {
self.saturating_mul(rhs)
}
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
self.wrapping_add(rhs)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
self.wrapping_sub(rhs)
}
#[inline]
fn wrapping_mul(self, rhs: Self) -> Self {
self.wrapping_mul(rhs)
}
#[inline]
fn min(self, rhs: Self) -> Self {
Ord::min(self, rhs)
}
#[inline]
fn max(self, rhs: Self) -> Self {
Ord::max(self, rhs)
}
}
impl Sample for f32 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const MIN: Self = f32::MIN;
const MAX: Self = f32::MAX;
const UNIT: f32 = 1.0;
#[inline]
fn as_f32(self) -> f32 {
self
}
#[inline]
fn from_f32(v: f32) -> Self {
v
}
#[inline]
fn saturating_add(self, rhs: Self) -> Self {
self + rhs
}
#[inline]
fn saturating_sub(self, rhs: Self) -> Self {
self - rhs
}
#[inline]
fn saturating_mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
self + rhs
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
self - rhs
}
#[inline]
fn wrapping_mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn min(self, rhs: Self) -> Self {
f32::min(self, rhs)
}
#[inline]
fn max(self, rhs: Self) -> Self {
f32::max(self, rhs)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Pixel<T: Sample, const C: usize> {
pub channels: [T; C],
}
impl<T: Sample, const C: usize> Default for Pixel<T, C> {
#[inline]
fn default() -> Self {
Self::splat(T::ZERO)
}
}
impl<T: Sample, const C: usize> Pixel<T, C> {
#[inline]
pub const fn new(channels: [T; C]) -> Self {
Self { channels }
}
#[inline]
pub const fn splat(value: T) -> Self {
Self {
channels: [value; C],
}
}
#[inline]
pub const fn channels() -> usize {
C
}
#[inline]
pub fn channel(&self, index: usize) -> T {
self.channels[index]
}
#[inline]
pub fn as_f32(&self) -> [f32; C] {
let mut out = [0.0f32; C];
for (dst, src) in out.iter_mut().zip(self.channels.iter()) {
*dst = src.as_f32();
}
out
}
}
impl<T: Sample, const C: usize> From<[T; C]> for Pixel<T, C> {
#[inline]
fn from(channels: [T; C]) -> Self {
Self::new(channels)
}
}
impl<T: Sample> Pixel<T, 1> {
#[inline]
pub fn scalar(&self) -> T {
self.channels[0]
}
}
impl<T: Sample, const C: usize> core::ops::Add for Pixel<T, C> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
let mut out = self;
for (d, s) in out.channels.iter_mut().zip(rhs.channels.iter()) {
*d = d.wrapping_add(*s);
}
out
}
}
impl<T: Sample, const C: usize> core::ops::Sub for Pixel<T, C> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
let mut out = self;
for (d, s) in out.channels.iter_mut().zip(rhs.channels.iter()) {
*d = d.wrapping_sub(*s);
}
out
}
}
pub trait ByteRepr: Sample {}
impl ByteRepr for u8 {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sample_saturating() {
assert_eq!(u8::MAX.saturating_add(1), u8::MAX);
assert_eq!(0u8.saturating_sub(1), 0);
assert_eq!(u16::MAX.saturating_add(1), u16::MAX);
assert_eq!(255.0f32.saturating_add(1.0), 256.0);
}
#[test]
fn sample_wrapping() {
assert_eq!(u8::MAX.wrapping_add(1), 0);
assert_eq!(0u8.wrapping_sub(1), 255);
}
#[test]
fn sample_from_f32_roundtrip() {
assert_eq!(u8::from_f32(0.0), 0);
assert_eq!(u8::from_f32(255.0), 255);
assert_eq!(u8::from_f32(-10.0), 0);
assert_eq!(u8::from_f32(300.0), 255);
assert_eq!(u8::from_f32(127.6), 128);
assert_eq!(u16::from_f32(65535.0), 65535);
assert_eq!(f32::from_f32(1.5), 1.5);
}
#[test]
fn pixel_channels() {
let p = Pixel::<u8, 3>::new([10, 20, 30]);
assert_eq!(Pixel::<u8, 3>::channels(), 3);
assert_eq!(p.channel(1), 20);
assert_eq!(p.as_f32(), [10.0, 20.0, 30.0]);
}
#[test]
fn pixel_arithmetic() {
let a = Pixel::<u8, 3>::new([250, 0, 5]);
let b = Pixel::<u8, 3>::new([10, 255, 2]);
assert_eq!(a + b, Pixel::new([4, 255, 7]));
assert_eq!(a - b, Pixel::new([240, 1, 3]));
}
}