use fovea_derive::{HomogeneousPixel, LinearPixel, PlainPixel, WhiteChannel, ZeroablePixel};
use crate::pixel::{
FromLinear, HomogeneousPixel, IntegralPixel, IntegralSquaredPixel, LinearChannel, LinearPixel,
LinearSpace, OriginInvariantPixel, PlainChannel, PlainPixel, WhiteChannel, ZeroablePixel,
impl_origin_invariant_pixel,
};
use std::{
hash::{Hash, Hasher},
num::Saturating,
ops::Mul,
};
use super::{canonicalize_f32, canonicalize_f64};
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct Mono<const BITS: usize> {
d: Saturating<u16>,
}
pub type Mono10 = Mono<10>;
pub type Mono12 = Mono<12>;
pub type Mono14 = Mono<14>;
impl<const BITS: usize> Mono<BITS> {
const _ASSERT_BITS: () = assert!(BITS == 10 || BITS == 12 || BITS == 14);
pub(crate) const MAX: u16 = {
let () = Self::_ASSERT_BITS;
(1 << BITS) - 1
};
pub fn new(value: u16) -> Self {
Mono {
d: Saturating(value.min(Self::MAX)),
}
}
pub fn value(&self) -> u16 {
self.d.0
}
}
impl<const BITS: usize> From<Mono<BITS>> for u16 {
fn from(value: Mono<BITS>) -> Self {
value.d.0
}
}
impl<const BITS: usize> From<u16> for Mono<BITS> {
fn from(value: u16) -> Self {
Mono::new(value)
}
}
impl<const BITS: usize> From<Mono<BITS>> for Saturating<u16> {
fn from(value: Mono<BITS>) -> Self {
value.d
}
}
impl<const BITS: usize> From<Saturating<u16>> for Mono<BITS> {
fn from(value: Saturating<u16>) -> Self {
Mono::new(value.0)
}
}
impl<const BITS: usize> std::ops::Add for Mono<BITS> {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
(self.d + other.d).into()
}
}
impl<const BITS: usize> std::ops::Add for &Mono<BITS> {
type Output = Mono<BITS>;
fn add(self, other: Self) -> Self::Output {
(self.d + other.d).into()
}
}
impl<const BITS: usize> std::ops::AddAssign for Mono<BITS> {
fn add_assign(&mut self, other: Self) {
self.d += other.d;
self.d.0 = self.d.0.min(Self::MAX);
}
}
impl<const BITS: usize> std::ops::AddAssign<&Mono<BITS>> for Mono<BITS> {
fn add_assign(&mut self, other: &Mono<BITS>) {
self.d += other.d;
self.d.0 = self.d.0.min(Self::MAX);
}
}
impl<const BITS: usize> std::ops::AddAssign<&u16> for Mono<BITS> {
fn add_assign(&mut self, other: &u16) {
self.d += *other;
self.d.0 = self.d.0.min(Self::MAX);
}
}
impl<const BITS: usize> std::ops::Sub for Mono<BITS> {
type Output = Self;
fn sub(self, other: Self) -> Self {
(self.d - other.d).into()
}
}
impl<const BITS: usize> std::ops::Sub for &Mono<BITS> {
type Output = Mono<BITS>;
fn sub(self, other: Self) -> Mono<BITS> {
(self.d - other.d).into()
}
}
impl<const BITS: usize> std::ops::SubAssign for Mono<BITS> {
fn sub_assign(&mut self, other: Self) {
self.d -= other.d;
}
}
impl<const BITS: usize> std::ops::SubAssign<&Mono<BITS>> for Mono<BITS> {
fn sub_assign(&mut self, other: &Mono<BITS>) {
self.d -= other.d;
}
}
impl<const BITS: usize> std::ops::SubAssign<&u16> for Mono<BITS> {
fn sub_assign(&mut self, other: &u16) {
self.d -= *other;
}
}
impl<const BITS: usize> std::ops::Mul for Mono<BITS> {
type Output = Self;
fn mul(self, other: Self) -> Self {
let result = (self.d.0 as u32) * (other.d.0 as u32);
Mono::new((result.min(Self::MAX as u32)) as u16)
}
}
impl<const BITS: usize> std::ops::Mul for &Mono<BITS> {
type Output = Mono<BITS>;
fn mul(self, other: Self) -> Mono<BITS> {
let result = (self.d.0 as u32) * (other.d.0 as u32);
Mono::new((result.min(Mono::<BITS>::MAX as u32)) as u16)
}
}
impl<const BITS: usize> std::ops::MulAssign for Mono<BITS> {
fn mul_assign(&mut self, other: Self) {
let result = (self.d.0 as u32) * (other.d.0 as u32);
self.d.0 = (result.min(Self::MAX as u32)) as u16;
}
}
impl<const BITS: usize> std::ops::MulAssign<&Mono<BITS>> for Mono<BITS> {
fn mul_assign(&mut self, other: &Mono<BITS>) {
let result = (self.d.0 as u32) * (other.d.0 as u32);
self.d.0 = (result.min(Self::MAX as u32)) as u16;
}
}
impl<const BITS: usize> std::ops::MulAssign<&u16> for Mono<BITS> {
fn mul_assign(&mut self, other: &u16) {
let result = (self.d.0 as u32) * (*other as u32);
self.d.0 = (result.min(Self::MAX as u32)) as u16;
}
}
impl<const BITS: usize> std::ops::Div for Mono<BITS> {
type Output = Self;
fn div(self, other: Self) -> Self {
(self.d / other.d).into()
}
}
impl<const BITS: usize> std::ops::Div for &Mono<BITS> {
type Output = Mono<BITS>;
fn div(self, other: Self) -> Mono<BITS> {
(self.d / other.d).into()
}
}
impl<const BITS: usize> std::ops::DivAssign for Mono<BITS> {
fn div_assign(&mut self, other: Self) {
self.d /= other.d;
}
}
impl<const BITS: usize> std::ops::DivAssign<&Mono<BITS>> for Mono<BITS> {
fn div_assign(&mut self, other: &Mono<BITS>) {
self.d /= other.d;
}
}
impl<const BITS: usize> std::ops::DivAssign<&u16> for Mono<BITS> {
fn div_assign(&mut self, other: &u16) {
self.d.0 /= *other;
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[repr(transparent)]
#[linear(accumulator = MonoF32)]
pub struct Mono8(Saturating<u8>);
impl Mono8 {
pub fn new(value: u8) -> Self {
Mono8(Saturating(value))
}
#[inline]
pub fn value(self) -> u8 {
self.0.0
}
}
impl From<Mono8> for u8 {
#[inline]
fn from(p: Mono8) -> Self {
p.0.0
}
}
impl From<u8> for Mono8 {
#[inline]
fn from(v: u8) -> Self {
Mono8::new(v)
}
}
impl Mul<f32> for Mono8 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
let r = (self.0.0 as f32 * rhs).round();
Mono8(Saturating(r.min(255.0) as u8))
}
}
impl Mul<f32> for &Mono8 {
type Output = Mono8;
fn mul(self, rhs: f32) -> Self::Output {
let r = (self.0.0 as f32 * rhs).round();
Mono8(Saturating(r.min(255.0) as u8))
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[linear(accumulator = MonoF32)]
pub struct Mono16(Saturating<u16>);
impl Mono16 {
pub fn new(value: u16) -> Self {
Mono16(Saturating(value))
}
#[inline]
pub fn value(self) -> u16 {
self.0.0
}
}
impl From<Mono16> for u16 {
#[inline]
fn from(p: Mono16) -> Self {
p.0.0
}
}
impl From<u16> for Mono16 {
#[inline]
fn from(v: u16) -> Self {
Mono16::new(v)
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[linear(accumulator = MonoF64)]
pub struct Mono32(Saturating<u32>);
impl Mono32 {
pub fn new(value: u32) -> Self {
Mono32(Saturating(value))
}
#[inline]
pub fn value(self) -> u32 {
self.0.0
}
}
impl From<Mono32> for u32 {
#[inline]
fn from(p: Mono32) -> Self {
p.0.0
}
}
impl From<u32> for Mono32 {
#[inline]
fn from(v: u32) -> Self {
Mono32::new(v)
}
}
impl LinearPixel<f64> for Mono32 {
type Accumulator = MonoF64;
#[inline(always)]
fn to_accumulator(&self) -> MonoF64 {
MonoF64(<Saturating<u32> as LinearChannel<f64>>::to_accumulator(
&self.0,
))
}
#[inline(always)]
fn scale(&self, scalar: f64) -> MonoF64 {
MonoF64(<Saturating<u32> as LinearChannel<f64>>::scale(
&self.0, scalar,
))
}
#[inline(always)]
fn uniform(scalar: f64) -> MonoF64 {
MonoF64(<Saturating<u32> as LinearChannel<f64>>::uniform(scalar))
}
#[inline(always)]
fn scale_add(&self, scalar: f64, addend: MonoF64) -> MonoF64 {
MonoF64(<Saturating<u32> as LinearChannel<f64>>::scale_add(
&self.0, scalar, addend.0,
))
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[linear(accumulator = MonoF64)]
pub struct Mono64(Saturating<u64>);
impl Mono64 {
pub fn new(value: u64) -> Self {
Mono64(Saturating(value))
}
#[inline]
pub fn value(self) -> u64 {
self.0.0
}
}
impl From<Mono64> for u64 {
#[inline]
fn from(p: Mono64) -> Self {
p.0.0
}
}
impl From<u64> for Mono64 {
#[inline]
fn from(v: u64) -> Self {
Mono64::new(v)
}
}
impl LinearPixel<f64> for Mono64 {
type Accumulator = MonoF64;
#[inline(always)]
fn to_accumulator(&self) -> MonoF64 {
MonoF64(<Saturating<u64> as LinearChannel<f64>>::to_accumulator(
&self.0,
))
}
#[inline(always)]
fn scale(&self, scalar: f64) -> MonoF64 {
MonoF64(<Saturating<u64> as LinearChannel<f64>>::scale(
&self.0, scalar,
))
}
#[inline(always)]
fn uniform(scalar: f64) -> MonoF64 {
MonoF64(<Saturating<u64> as LinearChannel<f64>>::uniform(scalar))
}
#[inline(always)]
fn scale_add(&self, scalar: f64, addend: MonoF64) -> MonoF64 {
MonoF64(<Saturating<u64> as LinearChannel<f64>>::scale_add(
&self.0, scalar, addend.0,
))
}
}
#[repr(transparent)]
#[derive(
Clone, Copy, Debug, PartialEq, PartialOrd, PlainPixel, HomogeneousPixel, ZeroablePixel,
)]
pub struct MonoF32(#[zero(default)] pub f32);
impl MonoF32 {
#[inline]
pub fn new(value: f32) -> Self {
MonoF32(value)
}
#[inline]
pub fn value(self) -> f32 {
self.0
}
#[inline]
pub fn abs(self) -> MonoF32 {
MonoF32(self.0.abs())
}
}
impl From<MonoF32> for f32 {
#[inline]
fn from(p: MonoF32) -> Self {
p.0
}
}
impl From<f32> for MonoF32 {
#[inline]
fn from(v: f32) -> Self {
MonoF32::new(v)
}
}
impl From<MonoF32> for f64 {
#[inline]
fn from(p: MonoF32) -> Self {
p.0 as f64
}
}
impl Hash for MonoF32 {
fn hash<H: Hasher>(&self, state: &mut H) {
canonicalize_f32(self.0).hash(state);
}
}
#[repr(transparent)]
#[derive(
Clone, Copy, Debug, PartialEq, PartialOrd, PlainPixel, HomogeneousPixel, ZeroablePixel,
)]
pub struct MonoF64(#[zero(default)] pub f64);
impl MonoF64 {
#[inline]
pub fn new(value: f64) -> Self {
MonoF64(value)
}
#[inline]
pub fn value(self) -> f64 {
self.0
}
#[inline]
pub fn abs(self) -> MonoF64 {
MonoF64(self.0.abs())
}
}
impl From<MonoF64> for f64 {
#[inline]
fn from(p: MonoF64) -> Self {
p.0
}
}
impl From<f64> for MonoF64 {
#[inline]
fn from(v: f64) -> Self {
MonoF64::new(v)
}
}
impl Hash for MonoF64 {
fn hash<H: Hasher>(&self, state: &mut H) {
canonicalize_f64(self.0).hash(state);
}
}
unsafe impl<const BITS: usize> PlainChannel for Mono<BITS> {}
unsafe impl<const BITS: usize> PlainPixel for Mono<BITS> {
const CHANNELS: &'static [usize] = &[2];
}
impl<const BITS: usize> ZeroablePixel for Mono<BITS> {
fn zero() -> Self {
Saturating(0).into()
}
}
impl<const BITS: usize> LinearPixel for Mono<BITS> {
type Accumulator = MonoF32;
#[inline(always)]
fn to_accumulator(&self) -> MonoF32 {
MonoF32(<Saturating<u16> as LinearChannel<f32>>::to_accumulator(
&self.d,
))
}
#[inline(always)]
fn scale(&self, scalar: f32) -> Self::Accumulator {
MonoF32(<Saturating<u16> as LinearChannel<f32>>::scale(
&self.d, scalar,
))
}
#[inline(always)]
fn scale_add(&self, scalar: f32, addend: MonoF32) -> MonoF32 {
MonoF32(<Saturating<u16> as LinearChannel<f32>>::scale_add(
&self.d, scalar, addend.0,
))
}
#[inline(always)]
fn uniform(scalar: f32) -> MonoF32 {
MonoF32(scalar)
}
}
impl<const BITS: usize> FromLinear<f32> for Mono<BITS> {
#[inline(always)]
fn from_linear(acc: f32) -> Self {
Mono::new(acc.round().clamp(0.0, Mono::<BITS>::MAX as f32) as u16)
}
}
impl<const BITS: usize> FromLinear<MonoF32> for Mono<BITS> {
#[inline(always)]
fn from_linear(acc: MonoF32) -> Self {
<Self as FromLinear<f32>>::from_linear(acc.0)
}
}
unsafe impl<const BITS: usize> HomogeneousPixel for Mono<BITS> {
type Channel = Saturating<u16>;
type Channels = [Saturating<u16>; 1];
}
impl<const BITS: usize> WhiteChannel for Mono<BITS> {
#[inline(always)]
fn white_channel() -> Saturating<u16> {
Saturating(<Mono<BITS>>::MAX)
}
}
impl LinearSpace for MonoF32 {}
impl LinearSpace for MonoF64 {}
impl std::ops::Add for MonoF32 {
type Output = Self;
#[inline(always)]
fn add(self, other: Self) -> Self {
MonoF32(self.0 + other.0)
}
}
impl std::ops::Sub for MonoF32 {
type Output = Self;
#[inline(always)]
fn sub(self, other: Self) -> Self {
MonoF32(self.0 - other.0)
}
}
impl std::ops::Mul for MonoF32 {
type Output = Self;
#[inline(always)]
fn mul(self, other: Self) -> Self {
MonoF32(self.0 * other.0)
}
}
impl LinearPixel for MonoF32 {
type Accumulator = Self;
#[inline(always)]
fn to_accumulator(&self) -> Self {
*self
}
#[inline(always)]
fn scale(&self, scalar: f32) -> Self {
MonoF32(self.0 * scalar)
}
#[inline(always)]
fn scale_add(&self, scalar: f32, addend: Self) -> Self {
#[cfg(target_feature = "fma")]
{
MonoF32(self.0.mul_add(scalar, addend.0))
}
#[cfg(not(target_feature = "fma"))]
{
MonoF32(self.0 * scalar + addend.0)
}
}
#[inline(always)]
fn uniform(scalar: f32) -> Self {
MonoF32(scalar)
}
}
impl std::ops::Add for MonoF64 {
type Output = Self;
#[inline(always)]
fn add(self, other: Self) -> Self {
MonoF64(self.0 + other.0)
}
}
impl std::ops::Sub for MonoF64 {
type Output = Self;
#[inline(always)]
fn sub(self, other: Self) -> Self {
MonoF64(self.0 - other.0)
}
}
impl std::ops::Mul for MonoF64 {
type Output = Self;
#[inline(always)]
fn mul(self, other: Self) -> Self {
MonoF64(self.0 * other.0)
}
}
impl LinearPixel for MonoF64 {
type Accumulator = Self;
#[inline(always)]
fn to_accumulator(&self) -> Self {
*self
}
#[inline(always)]
fn scale(&self, scalar: f32) -> Self {
MonoF64(self.0 * scalar as f64)
}
#[inline(always)]
fn uniform(scalar: f32) -> Self {
MonoF64(scalar as f64)
}
#[inline(always)]
fn scale_add(&self, scalar: f32, addend: Self) -> Self {
#[cfg(target_feature = "fma")]
{
MonoF64(self.0.mul_add(scalar as f64, addend.0))
}
#[cfg(not(target_feature = "fma"))]
{
MonoF64(self.0 * scalar as f64 + addend.0)
}
}
}
impl LinearPixel<f64> for MonoF64 {
type Accumulator = Self;
#[inline(always)]
fn to_accumulator(&self) -> Self {
*self
}
#[inline(always)]
fn scale(&self, scalar: f64) -> Self {
MonoF64(self.0 * scalar)
}
#[inline(always)]
fn uniform(scalar: f64) -> Self {
MonoF64(scalar)
}
#[inline(always)]
fn scale_add(&self, scalar: f64, addend: Self) -> Self {
#[cfg(target_feature = "fma")]
{
MonoF64(self.0.mul_add(scalar, addend.0))
}
#[cfg(not(target_feature = "fma"))]
{
MonoF64(self.0 * scalar + addend.0)
}
}
}
impl<const BITS: usize> LinearSpace for Mono<BITS> {}
impl_origin_invariant_pixel!(Mono8, Mono16, Mono32, Mono64, MonoF32, MonoF64);
impl<const BITS: usize> OriginInvariantPixel for Mono<BITS> {}
impl IntegralPixel<Mono32> for Mono8 {
#[inline]
fn to_integral(self) -> Mono32 {
Mono32::new(self.value() as u32)
}
#[inline]
fn max_integral_value() -> Mono32 {
Mono32::new(u8::MAX as u32)
}
}
impl IntegralPixel<Mono64> for Mono8 {
#[inline]
fn to_integral(self) -> Mono64 {
Mono64::new(self.value() as u64)
}
#[inline]
fn max_integral_value() -> Mono64 {
Mono64::new(u8::MAX as u64)
}
}
impl IntegralPixel<MonoF64> for Mono8 {
#[inline]
fn to_integral(self) -> MonoF64 {
MonoF64::new(self.value() as f64)
}
#[inline]
fn max_integral_value() -> MonoF64 {
MonoF64::new(u8::MAX as f64)
}
}
impl IntegralSquaredPixel<Mono64> for Mono8 {
#[inline]
fn to_integral_squared(self) -> Mono64 {
let v = self.value() as u64;
Mono64::new(v * v)
}
#[inline]
fn max_integral_squared_value() -> Mono64 {
let m = u8::MAX as u64;
Mono64::new(m * m)
}
}
impl IntegralSquaredPixel<MonoF64> for Mono8 {
#[inline]
fn to_integral_squared(self) -> MonoF64 {
let v = self.value() as f64;
MonoF64::new(v * v)
}
#[inline]
fn max_integral_squared_value() -> MonoF64 {
let m = u8::MAX as f64;
MonoF64::new(m * m)
}
}
impl IntegralPixel<Mono64> for Mono16 {
#[inline]
fn to_integral(self) -> Mono64 {
Mono64::new(self.value() as u64)
}
#[inline]
fn max_integral_value() -> Mono64 {
Mono64::new(u16::MAX as u64)
}
}
impl IntegralPixel<MonoF64> for Mono16 {
#[inline]
fn to_integral(self) -> MonoF64 {
MonoF64::new(self.value() as f64)
}
#[inline]
fn max_integral_value() -> MonoF64 {
MonoF64::new(u16::MAX as f64)
}
}
impl IntegralSquaredPixel<MonoF64> for Mono16 {
#[inline]
fn to_integral_squared(self) -> MonoF64 {
let v = self.value() as f64;
MonoF64::new(v * v)
}
#[inline]
fn max_integral_squared_value() -> MonoF64 {
let m = u16::MAX as f64;
MonoF64::new(m * m)
}
}
impl IntegralPixel<Mono64> for Mono32 {
#[inline]
fn to_integral(self) -> Mono64 {
Mono64::new(self.value() as u64)
}
#[inline]
fn max_integral_value() -> Mono64 {
Mono64::new(u32::MAX as u64)
}
}
impl IntegralPixel<MonoF64> for Mono32 {
#[inline]
fn to_integral(self) -> MonoF64 {
MonoF64::new(self.value() as f64)
}
#[inline]
fn max_integral_value() -> MonoF64 {
MonoF64::new(u32::MAX as f64)
}
}
impl IntegralSquaredPixel<MonoF64> for Mono32 {
#[inline]
fn to_integral_squared(self) -> MonoF64 {
let v = self.value() as f64;
MonoF64::new(v * v)
}
#[inline]
fn max_integral_squared_value() -> MonoF64 {
let m = u32::MAX as f64;
MonoF64::new(m * m)
}
}
impl IntegralPixel<MonoF64> for MonoF32 {
#[inline]
fn to_integral(self) -> MonoF64 {
MonoF64::new(self.value() as f64)
}
#[inline]
fn max_integral_value() -> MonoF64 {
MonoF64::new(1.0)
}
}
impl IntegralPixel<MonoF64> for MonoF64 {
#[inline]
fn to_integral(self) -> MonoF64 {
self
}
#[inline]
fn max_integral_value() -> MonoF64 {
MonoF64::new(1.0)
}
}
impl IntegralSquaredPixel<MonoF64> for MonoF32 {
#[inline]
fn to_integral_squared(self) -> MonoF64 {
let v = self.value() as f64;
MonoF64::new(v * v)
}
#[inline]
fn max_integral_squared_value() -> MonoF64 {
MonoF64::new(1.0)
}
}
impl IntegralSquaredPixel<MonoF64> for MonoF64 {
#[inline]
fn to_integral_squared(self) -> MonoF64 {
MonoF64::new(self.value() * self.value())
}
#[inline]
fn max_integral_squared_value() -> MonoF64 {
MonoF64::new(1.0)
}
}
#[cfg(test)]
mod integral_tests {
use super::*;
#[test]
fn mono8_to_mono32_to_integral_and_max() {
assert_eq!(
<Mono8 as IntegralPixel<Mono32>>::to_integral(Mono8::new(200)),
Mono32::new(200)
);
assert_eq!(
<Mono8 as IntegralPixel<Mono32>>::max_integral_value(),
Mono32::new(255)
);
}
#[test]
fn mono8_to_mono64_max() {
assert_eq!(
<Mono8 as IntegralPixel<Mono64>>::max_integral_value(),
Mono64::new(255)
);
}
#[test]
fn mono8_squared_mono64() {
assert_eq!(
<Mono8 as IntegralSquaredPixel<Mono64>>::to_integral_squared(Mono8::new(16)),
Mono64::new(256)
);
assert_eq!(
<Mono8 as IntegralSquaredPixel<Mono64>>::max_integral_squared_value(),
Mono64::new(255 * 255)
);
}
#[test]
fn mono16_to_mono64_max() {
assert_eq!(
<Mono16 as IntegralPixel<Mono64>>::max_integral_value(),
Mono64::new(u16::MAX as u64)
);
}
#[test]
fn mono16_squared_monof64_max() {
let expected = (u16::MAX as f64) * (u16::MAX as f64);
assert_eq!(
<Mono16 as IntegralSquaredPixel<MonoF64>>::max_integral_squared_value(),
MonoF64::new(expected)
);
}
#[test]
fn mono32_to_monof64_roundtrip() {
let v = Mono32::new(123_456_789);
let acc = <Mono32 as IntegralPixel<MonoF64>>::to_integral(v);
assert_eq!(acc, MonoF64::new(123_456_789.0));
}
#[test]
fn monof32_to_monof64_max_is_one() {
assert_eq!(
<MonoF32 as IntegralPixel<MonoF64>>::max_integral_value(),
MonoF64::new(1.0)
);
assert_eq!(
<MonoF32 as IntegralSquaredPixel<MonoF64>>::max_integral_squared_value(),
MonoF64::new(1.0)
);
}
#[test]
fn monof64_self_identity_to_integral() {
let v = MonoF64::new(0.42);
assert_eq!(
<MonoF64 as IntegralPixel<MonoF64>>::to_integral(v),
MonoF64::new(0.42)
);
}
}
#[cfg(test)]
mod accumulator_arith_tests {
use super::*;
#[test]
fn mono32_add_basic() {
let a = Mono32::new(1_000_000);
let b = Mono32::new(2_500_000);
assert_eq!(a + b, Mono32::new(3_500_000));
}
#[test]
fn mono32_sub_basic() {
let a = Mono32::new(5_000_000);
let b = Mono32::new(1_500_000);
assert_eq!(a - b, Mono32::new(3_500_000));
}
#[test]
fn mono32_add_saturates_at_u32_max() {
let a = Mono32::new(u32::MAX - 1);
let b = Mono32::new(10);
assert_eq!(a + b, Mono32::new(u32::MAX));
}
#[test]
fn mono32_sub_saturates_at_zero() {
let a = Mono32::new(5);
let b = Mono32::new(100);
assert_eq!(a - b, Mono32::new(0));
}
#[test]
fn mono32_add_sub_roundtrip() {
let a = Mono32::new(123_456);
let b = Mono32::new(7_890);
assert_eq!((a + b) - b, a);
}
#[test]
fn mono64_add_basic() {
let a = Mono64::new(10_000_000_000);
let b = Mono64::new(25_000_000_000);
assert_eq!(a + b, Mono64::new(35_000_000_000));
}
#[test]
fn mono64_sub_basic() {
let a = Mono64::new(50_000_000_000);
let b = Mono64::new(15_000_000_000);
assert_eq!(a - b, Mono64::new(35_000_000_000));
}
#[test]
fn mono64_add_saturates_at_u64_max() {
let a = Mono64::new(u64::MAX - 1);
let b = Mono64::new(10);
assert_eq!(a + b, Mono64::new(u64::MAX));
}
#[test]
fn mono64_sub_saturates_at_zero() {
let a = Mono64::new(5);
let b = Mono64::new(100);
assert_eq!(a - b, Mono64::new(0));
}
#[test]
fn mono64_add_sub_roundtrip() {
let a = Mono64::new(999_999_999_999);
let b = Mono64::new(123_456_789);
assert_eq!((a + b) - b, a);
}
}