use crate::{Error, Rect, Result};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Limits {
pub max_samples: u64,
pub max_dimension: u32,
}
impl Default for Limits {
fn default() -> Self {
Limits { max_samples: 1_200_000_000, max_dimension: 65_535 }
}
}
impl Limits {
pub fn with_max_samples(mut self, max_samples: u64) -> Self {
self.max_samples = max_samples;
self
}
pub fn with_max_dimension(mut self, max_dimension: u32) -> Self {
self.max_dimension = max_dimension;
self
}
pub fn check_dims(&self, width: u32, height: u32, cpp: u32) -> Result<()> {
if width == 0 || height == 0 {
return Err(Error::invalid_metadata(format!("zero image dimension {width}x{height}")));
}
if width > self.max_dimension || height > self.max_dimension {
return Err(Error::LimitExceeded(format!("dimension {width}x{height} exceeds {}", self.max_dimension)));
}
let n = (width as u64 * height as u64).checked_mul(cpp.max(1) as u64);
if n.is_none_or(|n| n > self.max_samples) {
return Err(Error::LimitExceeded(format!(
"{width}x{height}x{cpp} samples exceeds limit {}",
self.max_samples
)));
}
Ok(())
}
}
pub fn alloc_zeroed<T: zeroable::Zeroable>(len: usize) -> Result<Vec<T>> {
if len == 0 {
return Ok(Vec::new());
}
let layout = std::alloc::Layout::array::<T>(len)
.map_err(|_| Error::OutOfMemory(format!("allocation of {len} elements overflows")))?;
unsafe {
let ptr = std::alloc::alloc_zeroed(layout) as *mut T;
if ptr.is_null() {
return Err(Error::OutOfMemory(format!("failed to allocate {} bytes", layout.size())));
}
advise_huge_pages(ptr as *mut u8, layout.size());
Ok(Vec::from_raw_parts(ptr, len, len))
}
}
#[cfg(target_os = "linux")]
fn advise_huge_pages(ptr: *mut u8, size: usize) {
const HUGE: usize = 2 << 20;
if size < 4 * HUGE {
return;
}
let start = (ptr as usize).next_multiple_of(HUGE);
let end = (ptr as usize + size) & !(HUGE - 1);
if end > start {
unsafe {
libc::madvise(start as *mut libc::c_void, end - start, libc::MADV_HUGEPAGE);
}
}
}
#[cfg(not(target_os = "linux"))]
fn advise_huge_pages(_ptr: *mut u8, _size: usize) {}
pub mod zeroable {
pub unsafe trait Zeroable: Copy + Send + Sync + 'static {}
unsafe impl Zeroable for u8 {}
unsafe impl Zeroable for u16 {}
unsafe impl Zeroable for u32 {}
unsafe impl Zeroable for i32 {}
unsafe impl Zeroable for f32 {}
}
#[derive(Clone, PartialEq)]
pub enum PixelData {
U16(Vec<u16>),
F32(Vec<f32>),
}
impl std::fmt::Debug for PixelData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PixelData::U16(v) => write!(f, "U16([{} samples])", v.len()),
PixelData::F32(v) => write!(f, "F32([{} samples])", v.len()),
}
}
}
impl PixelData {
pub fn len(&self) -> usize {
match self {
PixelData::U16(v) => v.len(),
PixelData::F32(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_u16(&self) -> Option<&[u16]> {
match self {
PixelData::U16(v) => Some(v),
_ => None,
}
}
pub fn as_u16_mut(&mut self) -> Option<&mut [u16]> {
match self {
PixelData::U16(v) => Some(v),
_ => None,
}
}
pub fn as_f32(&self) -> Option<&[f32]> {
match self {
PixelData::F32(v) => Some(v),
_ => None,
}
}
pub fn bytes_per_sample(&self) -> usize {
match self {
PixelData::U16(_) => 2,
PixelData::F32(_) => 4,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RawImage {
pub width: u32,
pub height: u32,
pub cpp: u32,
pub data: PixelData,
pub float_max: Option<f32>,
}
#[inline]
pub fn max_nonneg_bits(v: &[f32]) -> u32 {
v.iter().fold(0i32, |m, &x| {
let b = x.to_bits() as i32;
m.max(if b > 0x7f80_0000 { 0 } else { b })
}) as u32
}
impl RawImage {
pub fn new_u16(width: u32, height: u32, cpp: u32, limits: &Limits) -> Result<Self> {
limits.check_dims(width, height, cpp)?;
let n = width as usize * height as usize * cpp as usize;
Ok(RawImage { width, height, cpp, data: PixelData::U16(alloc_zeroed(n)?), float_max: None })
}
pub fn new_f32(width: u32, height: u32, cpp: u32, limits: &Limits) -> Result<Self> {
limits.check_dims(width, height, cpp)?;
let n = width as usize * height as usize * cpp as usize;
Ok(RawImage { width, height, cpp, data: PixelData::F32(alloc_zeroed(n)?), float_max: None })
}
#[inline]
pub fn row_len(&self) -> usize {
self.width as usize * self.cpp as usize
}
pub fn u16(&self) -> &[u16] {
self.data.as_u16().expect("u16 image")
}
pub fn u16_mut(&mut self) -> &mut [u16] {
self.data.as_u16_mut().expect("u16 image")
}
pub fn f32_mut(&mut self) -> &mut [f32] {
match &mut self.data {
PixelData::F32(v) => v,
_ => panic!("f32 image"),
}
}
pub fn crop(&self, r: Rect) -> Result<RawImage> {
if r.is_empty() || r.right() > self.width || r.bottom() > self.height {
return Err(Error::InvalidArgument(format!("crop {r} outside {}x{}", self.width, self.height)));
}
let cpp = self.cpp as usize;
let (x0, w) = (r.x as usize * cpp, r.width as usize * cpp);
let rl = self.row_len();
let y0 = r.y as usize;
fn copy<T: Copy + Send + Sync + crate::image::zeroable::Zeroable>(
v: &[T],
rl: usize,
x0: usize,
y0: usize,
w: usize,
h: usize,
) -> Result<Vec<T>> {
let mut out = alloc_zeroed::<T>(w * h)?;
crate::par::for_each_row_chunk(&mut out, w, 64, true, |first, rows| {
for (i, row) in rows.chunks_mut(w).enumerate() {
let s = (y0 + first + i) * rl + x0;
row.copy_from_slice(&v[s..s + w]);
}
});
Ok(out)
}
let h = r.height as usize;
let data = match &self.data {
PixelData::U16(v) => PixelData::U16(copy(v, rl, x0, y0, w, h)?),
PixelData::F32(v) => PixelData::F32(copy(v, rl, x0, y0, w, h)?),
};
Ok(RawImage { width: r.width, height: r.height, cpp: self.cpp, data, float_max: self.float_max })
}
pub fn sample(&self, x: u32, y: u32, c: u32) -> f32 {
let i = (y as usize * self.width as usize + x as usize) * self.cpp as usize + c as usize;
match &self.data {
PixelData::U16(v) => v[i] as f32,
PixelData::F32(v) => v[i],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_and_crop() {
let mut img = RawImage::new_u16(4, 3, 1, &Limits::default()).unwrap();
for (i, v) in img.u16_mut().iter_mut().enumerate() {
*v = i as u16;
}
let c = img.crop(Rect::new(1, 1, 2, 2)).unwrap();
assert_eq!(c.u16(), &[5, 6, 9, 10]);
assert!(img.crop(Rect::new(3, 0, 2, 1)).is_err());
}
#[test]
fn limits() {
let l = Limits { max_samples: 100, max_dimension: 50 };
assert!(RawImage::new_u16(10, 10, 1, &l).is_ok());
assert!(RawImage::new_u16(10, 11, 1, &l).is_err());
assert!(RawImage::new_u16(60, 1, 1, &l).is_err());
assert!(RawImage::new_u16(0, 1, 1, &l).is_err());
}
}