use std::fmt;
#[derive(Clone, Copy, Debug)]
pub struct FloatImageView<'a> {
pixels: &'a [f32],
width: u32,
height: u32,
}
impl<'a> FloatImageView<'a> {
pub const MAX_ABS_VALUE: f32 = 16_777_216.0;
pub fn new(pixels: &'a [f32], width: u32, height: u32) -> Result<Self, FloatImageError> {
let expected = width as u64 * height as u64;
if pixels.len() as u64 != expected {
return Err(FloatImageError::SizeMismatch {
expected,
actual: pixels.len(),
});
}
if let Some(index) = first_invalid(pixels) {
return Err(FloatImageError::InvalidValue {
index,
value: pixels[index],
});
}
Ok(Self {
pixels,
width,
height,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn pixels(&self) -> &'a [f32] {
self.pixels
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FloatImageError {
SizeMismatch {
expected: u64,
actual: usize,
},
InvalidValue {
index: usize,
value: f32,
},
}
impl fmt::Display for FloatImageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SizeMismatch { expected, actual } => {
write!(f, "image needs {expected} pixels, got {actual}")
}
Self::InvalidValue { index, value } => write!(
f,
"pixel {index} is {value}; values must be finite and at most {} in magnitude",
FloatImageView::MAX_ABS_VALUE
),
}
}
}
impl std::error::Error for FloatImageError {}
fn first_invalid(pixels: &[f32]) -> Option<usize> {
let valid = |value: f32| value.abs() <= FloatImageView::MAX_ABS_VALUE;
const CHUNK: usize = 256;
for (chunk_index, chunk) in pixels.chunks(CHUNK).enumerate() {
let all_valid = chunk.iter().fold(true, |all, &value| all & valid(value));
if !all_valid {
let offset = chunk.iter().position(|&value| !valid(value));
return offset.map(|offset| chunk_index * CHUNK + offset);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_finite_values_within_range_and_empty_images() {
let max = FloatImageView::MAX_ABS_VALUE;
let pixels = [0.0, -0.0, 1e-30, -max, max, 25.5];
let view = FloatImageView::new(&pixels, 3, 2).unwrap();
assert_eq!(view.dimensions(), (3, 2));
assert_eq!(view.pixels(), &pixels);
assert!(FloatImageView::new(&[], 0, 7).is_ok());
}
#[test]
fn rejects_size_mismatch_without_overflow() {
assert_eq!(
FloatImageView::new(&[0.0; 5], 2, 3).unwrap_err(),
FloatImageError::SizeMismatch {
expected: 6,
actual: 5
}
);
let error = FloatImageView::new(&[0.0; 4], u32::MAX, u32::MAX).unwrap_err();
assert!(matches!(error, FloatImageError::SizeMismatch { .. }));
}
#[test]
fn reports_the_first_invalid_value() {
let max = FloatImageView::MAX_ABS_VALUE;
for (bad, index) in [
(f32::NAN, 0),
(f32::INFINITY, 300),
(f32::NEG_INFINITY, 511),
(max * 2.0, 999),
(-max * 2.0, 257),
] {
let mut pixels = vec![1.0f32; 1000];
pixels[index] = bad;
if index + 2 < pixels.len() {
pixels[index + 2] = f32::NAN;
}
match FloatImageView::new(&pixels, 100, 10).unwrap_err() {
FloatImageError::InvalidValue { index: got, value } => {
assert_eq!(got, index);
assert_eq!(value.to_bits(), bad.to_bits());
}
other => panic!("unexpected {other:?}"),
}
}
}
}