1use crate::{Error, Rect, Result};
4
5#[derive(Debug, Clone, Copy)]
7#[non_exhaustive]
8pub struct Limits {
9 pub max_samples: u64,
11 pub max_dimension: u32,
13}
14
15impl Default for Limits {
16 fn default() -> Self {
17 Limits { max_samples: 1_200_000_000, max_dimension: 65_535 }
19 }
20}
21
22impl Limits {
23 pub fn with_max_samples(mut self, max_samples: u64) -> Self {
25 self.max_samples = max_samples;
26 self
27 }
28
29 pub fn with_max_dimension(mut self, max_dimension: u32) -> Self {
31 self.max_dimension = max_dimension;
32 self
33 }
34
35 pub fn check_dims(&self, width: u32, height: u32, cpp: u32) -> Result<()> {
37 if width == 0 || height == 0 {
38 return Err(Error::invalid_metadata(format!("zero image dimension {width}x{height}")));
39 }
40 if width > self.max_dimension || height > self.max_dimension {
41 return Err(Error::LimitExceeded(format!("dimension {width}x{height} exceeds {}", self.max_dimension)));
42 }
43 let n = (width as u64 * height as u64).checked_mul(cpp.max(1) as u64);
44 if n.is_none_or(|n| n > self.max_samples) {
45 return Err(Error::LimitExceeded(format!(
46 "{width}x{height}x{cpp} samples exceeds limit {}",
47 self.max_samples
48 )));
49 }
50 Ok(())
51 }
52}
53
54pub fn alloc_zeroed<T: zeroable::Zeroable>(len: usize) -> Result<Vec<T>> {
60 if len == 0 {
61 return Ok(Vec::new());
62 }
63 let layout = std::alloc::Layout::array::<T>(len)
64 .map_err(|_| Error::OutOfMemory(format!("allocation of {len} elements overflows")))?;
65 unsafe {
72 let ptr = std::alloc::alloc_zeroed(layout) as *mut T;
73 if ptr.is_null() {
74 return Err(Error::OutOfMemory(format!("failed to allocate {} bytes", layout.size())));
75 }
76 advise_huge_pages(ptr as *mut u8, layout.size());
77 Ok(Vec::from_raw_parts(ptr, len, len))
78 }
79}
80
81#[cfg(target_os = "linux")]
86fn advise_huge_pages(ptr: *mut u8, size: usize) {
87 const HUGE: usize = 2 << 20;
88 if size < 4 * HUGE {
89 return;
90 }
91 let start = (ptr as usize).next_multiple_of(HUGE);
92 let end = (ptr as usize + size) & !(HUGE - 1);
93 if end > start {
94 unsafe {
98 libc::madvise(start as *mut libc::c_void, end - start, libc::MADV_HUGEPAGE);
99 }
100 }
101}
102
103#[cfg(not(target_os = "linux"))]
104fn advise_huge_pages(_ptr: *mut u8, _size: usize) {}
105
106pub mod zeroable {
108 pub unsafe trait Zeroable: Copy + Send + Sync + 'static {}
112 unsafe impl Zeroable for u8 {}
114 unsafe impl Zeroable for u16 {}
116 unsafe impl Zeroable for u32 {}
118 unsafe impl Zeroable for i32 {}
120 unsafe impl Zeroable for f32 {}
122}
123
124#[derive(Clone, PartialEq)]
126pub enum PixelData {
127 U16(Vec<u16>),
129 F32(Vec<f32>),
131}
132
133impl std::fmt::Debug for PixelData {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 PixelData::U16(v) => write!(f, "U16([{} samples])", v.len()),
137 PixelData::F32(v) => write!(f, "F32([{} samples])", v.len()),
138 }
139 }
140}
141
142impl PixelData {
143 pub fn len(&self) -> usize {
145 match self {
146 PixelData::U16(v) => v.len(),
147 PixelData::F32(v) => v.len(),
148 }
149 }
150 pub fn is_empty(&self) -> bool {
152 self.len() == 0
153 }
154 pub fn as_u16(&self) -> Option<&[u16]> {
156 match self {
157 PixelData::U16(v) => Some(v),
158 _ => None,
159 }
160 }
161 pub fn as_u16_mut(&mut self) -> Option<&mut [u16]> {
163 match self {
164 PixelData::U16(v) => Some(v),
165 _ => None,
166 }
167 }
168 pub fn as_f32(&self) -> Option<&[f32]> {
170 match self {
171 PixelData::F32(v) => Some(v),
172 _ => None,
173 }
174 }
175 pub fn bytes_per_sample(&self) -> usize {
177 match self {
178 PixelData::U16(_) => 2,
179 PixelData::F32(_) => 4,
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq)]
186pub struct RawImage {
187 pub width: u32,
189 pub height: u32,
191 pub cpp: u32,
193 pub data: PixelData,
195 pub float_max: Option<f32>,
198}
199
200#[inline]
204pub fn max_nonneg_bits(v: &[f32]) -> u32 {
205 v.iter().fold(0i32, |m, &x| {
206 let b = x.to_bits() as i32;
207 m.max(if b > 0x7f80_0000 { 0 } else { b })
208 }) as u32
209}
210
211impl RawImage {
212 pub fn new_u16(width: u32, height: u32, cpp: u32, limits: &Limits) -> Result<Self> {
214 limits.check_dims(width, height, cpp)?;
215 let n = width as usize * height as usize * cpp as usize;
216 Ok(RawImage { width, height, cpp, data: PixelData::U16(alloc_zeroed(n)?), float_max: None })
217 }
218 pub fn new_f32(width: u32, height: u32, cpp: u32, limits: &Limits) -> Result<Self> {
220 limits.check_dims(width, height, cpp)?;
221 let n = width as usize * height as usize * cpp as usize;
222 Ok(RawImage { width, height, cpp, data: PixelData::F32(alloc_zeroed(n)?), float_max: None })
223 }
224 #[inline]
226 pub fn row_len(&self) -> usize {
227 self.width as usize * self.cpp as usize
228 }
229 pub fn u16(&self) -> &[u16] {
231 self.data.as_u16().expect("u16 image")
232 }
233 pub fn u16_mut(&mut self) -> &mut [u16] {
235 self.data.as_u16_mut().expect("u16 image")
236 }
237 pub fn f32_mut(&mut self) -> &mut [f32] {
239 match &mut self.data {
240 PixelData::F32(v) => v,
241 _ => panic!("f32 image"),
242 }
243 }
244 pub fn crop(&self, r: Rect) -> Result<RawImage> {
246 if r.is_empty() || r.right() > self.width || r.bottom() > self.height {
247 return Err(Error::InvalidArgument(format!("crop {r} outside {}x{}", self.width, self.height)));
248 }
249 let cpp = self.cpp as usize;
250 let (x0, w) = (r.x as usize * cpp, r.width as usize * cpp);
251 let rl = self.row_len();
252 let y0 = r.y as usize;
253 fn copy<T: Copy + Send + Sync + crate::image::zeroable::Zeroable>(
254 v: &[T],
255 rl: usize,
256 x0: usize,
257 y0: usize,
258 w: usize,
259 h: usize,
260 ) -> Result<Vec<T>> {
261 let mut out = alloc_zeroed::<T>(w * h)?;
262 crate::par::for_each_row_chunk(&mut out, w, 64, true, |first, rows| {
263 for (i, row) in rows.chunks_mut(w).enumerate() {
264 let s = (y0 + first + i) * rl + x0;
265 row.copy_from_slice(&v[s..s + w]);
266 }
267 });
268 Ok(out)
269 }
270 let h = r.height as usize;
271 let data = match &self.data {
272 PixelData::U16(v) => PixelData::U16(copy(v, rl, x0, y0, w, h)?),
273 PixelData::F32(v) => PixelData::F32(copy(v, rl, x0, y0, w, h)?),
274 };
275 Ok(RawImage { width: r.width, height: r.height, cpp: self.cpp, data, float_max: self.float_max })
276 }
277 pub fn sample(&self, x: u32, y: u32, c: u32) -> f32 {
279 let i = (y as usize * self.width as usize + x as usize) * self.cpp as usize + c as usize;
280 match &self.data {
281 PixelData::U16(v) => v[i] as f32,
282 PixelData::F32(v) => v[i],
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn alloc_and_crop() {
293 let mut img = RawImage::new_u16(4, 3, 1, &Limits::default()).unwrap();
294 for (i, v) in img.u16_mut().iter_mut().enumerate() {
295 *v = i as u16;
296 }
297 let c = img.crop(Rect::new(1, 1, 2, 2)).unwrap();
298 assert_eq!(c.u16(), &[5, 6, 9, 10]);
299 assert!(img.crop(Rect::new(3, 0, 2, 1)).is_err());
300 }
301
302 #[test]
303 fn limits() {
304 let l = Limits { max_samples: 100, max_dimension: 50 };
305 assert!(RawImage::new_u16(10, 10, 1, &l).is_ok());
306 assert!(RawImage::new_u16(10, 11, 1, &l).is_err());
307 assert!(RawImage::new_u16(60, 1, 1, &l).is_err());
308 assert!(RawImage::new_u16(0, 1, 1, &l).is_err());
309 }
310}