Skip to main content

flashraw_core/
image.rs

1//! Decoded pixel storage.
2
3use crate::{Error, Rect, Result};
4
5/// Safety limits applied while decoding untrusted input.
6#[derive(Debug, Clone, Copy)]
7#[non_exhaustive]
8pub struct Limits {
9    /// Maximum number of samples (width * height * components) in a decoded image.
10    pub max_samples: u64,
11    /// Maximum image width or height.
12    pub max_dimension: u32,
13}
14
15impl Default for Limits {
16    fn default() -> Self {
17        // 1.2 G samples comfortably covers 400 MP multi-shot medium format (x3 linear).
18        Limits { max_samples: 1_200_000_000, max_dimension: 65_535 }
19    }
20}
21
22impl Limits {
23    /// These limits with a different sample cap.
24    pub fn with_max_samples(mut self, max_samples: u64) -> Self {
25        self.max_samples = max_samples;
26        self
27    }
28
29    /// These limits with a different width/height cap.
30    pub fn with_max_dimension(mut self, max_dimension: u32) -> Self {
31        self.max_dimension = max_dimension;
32        self
33    }
34
35    /// Check that an image of `width × height × cpp` samples is within the limits.
36    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
54/// Allocate a zero-initialised vector without aborting on allocation failure.
55///
56/// Large zeroed allocations are served by the OS as lazily-mapped zero pages
57/// (calloc semantics), avoiding an explicit memset of 100+ MB buffers that the
58/// decoder is about to overwrite anyway.
59pub 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    // SAFETY: `layout` has non-zero size (len > 0 and T is a non-ZST numeric type,
66    // enforced by the `Zeroable` bound which is only implemented for primitive
67    // integers and floats). `alloc_zeroed` returns either null (handled) or a block
68    // valid for `layout`, all-zero bytes, which is a valid bit pattern for every
69    // `Zeroable` type. Ownership transfers to the Vec with matching capacity/length
70    // and the global allocator, so Vec's drop deallocates with the same layout.
71    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/// Ask the kernel to back large buffers with transparent huge pages. Decoders write
82/// every byte of their output, so first-touch page faults dominate memory-bound paths
83/// (e.g. 16-bit uncompressed unpack); 2 MiB pages cut the fault count ~500x and avoid
84/// fault-lock contention between worker threads. Purely advisory.
85#[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        // SAFETY: [start, end) lies within the allocation we just obtained; madvise with
95        // MADV_HUGEPAGE only changes how the kernel backs these pages, never their contents
96        // or ownership, and failure is harmless.
97        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
106/// Marker for types whose all-zero bit pattern is a valid value.
107pub mod zeroable {
108    /// # Safety
109    /// Implementors must be plain numeric types for which all-zero bytes are valid
110    /// and that are not zero-sized.
111    pub unsafe trait Zeroable: Copy + Send + Sync + 'static {}
112    // SAFETY: `u8` is a primitive numeric type, not zero-sized; all-zero bytes are a valid value.
113    unsafe impl Zeroable for u8 {}
114    // SAFETY: `u16` is a primitive numeric type, not zero-sized; all-zero bytes are a valid value.
115    unsafe impl Zeroable for u16 {}
116    // SAFETY: `u32` is a primitive numeric type, not zero-sized; all-zero bytes are a valid value.
117    unsafe impl Zeroable for u32 {}
118    // SAFETY: `i32` is a primitive numeric type, not zero-sized; all-zero bytes are a valid value.
119    unsafe impl Zeroable for i32 {}
120    // SAFETY: `f32` is a primitive numeric type, not zero-sized; all-zero bytes are a valid value.
121    unsafe impl Zeroable for f32 {}
122}
123
124/// Sample storage for a decoded image.
125#[derive(Clone, PartialEq)]
126pub enum PixelData {
127    /// Integer samples.
128    U16(Vec<u16>),
129    /// Floating-point samples (e.g. floating-point DNG).
130    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    /// Number of samples (not pixels).
144    pub fn len(&self) -> usize {
145        match self {
146            PixelData::U16(v) => v.len(),
147            PixelData::F32(v) => v.len(),
148        }
149    }
150    /// True if there are no samples.
151    pub fn is_empty(&self) -> bool {
152        self.len() == 0
153    }
154    /// The samples if this is `U16` storage.
155    pub fn as_u16(&self) -> Option<&[u16]> {
156        match self {
157            PixelData::U16(v) => Some(v),
158            _ => None,
159        }
160    }
161    /// The samples, mutably, if this is `U16` storage.
162    pub fn as_u16_mut(&mut self) -> Option<&mut [u16]> {
163        match self {
164            PixelData::U16(v) => Some(v),
165            _ => None,
166        }
167    }
168    /// The samples if this is `F32` storage.
169    pub fn as_f32(&self) -> Option<&[f32]> {
170        match self {
171            PixelData::F32(v) => Some(v),
172            _ => None,
173        }
174    }
175    /// Size of one sample in bytes (2 or 4).
176    pub fn bytes_per_sample(&self) -> usize {
177        match self {
178            PixelData::U16(_) => 2,
179            PixelData::F32(_) => 4,
180        }
181    }
182}
183
184/// A decoded image buffer: `width x height` pixels of `cpp` interleaved samples.
185#[derive(Debug, Clone, PartialEq)]
186pub struct RawImage {
187    /// Width in pixels.
188    pub width: u32,
189    /// Height in pixels.
190    pub height: u32,
191    /// Components (samples) per pixel: 1 for CFA/mono, 3 for linear RGB / Foveon, 4 for some.
192    pub cpp: u32,
193    /// Row-major samples, `width * height * cpp` in total.
194    pub data: PixelData,
195    /// Largest non-negative, non-NaN sample of float data, when the decoder measured it
196    /// while decoding (saves a pass over the buffer when converting to integers).
197    pub float_max: Option<f32>,
198}
199
200/// Largest non-negative, non-NaN value of `v` as IEEE bits (0 when there is none). For
201/// such values bit patterns order like integers, so this runs on integer lanes
202/// (vectorisable, unlike `f32::max`); NaN patterns (above +inf) are masked out.
203#[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    /// Allocate a zeroed `u16` image, enforcing `limits`.
213    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    /// Allocate a zeroed `f32` image, enforcing `limits`.
219    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    /// Samples per row.
225    #[inline]
226    pub fn row_len(&self) -> usize {
227        self.width as usize * self.cpp as usize
228    }
229    /// `u16` samples. Panics if this is a float image (a decoder bug, not bad input).
230    pub fn u16(&self) -> &[u16] {
231        self.data.as_u16().expect("u16 image")
232    }
233    /// Mutable `u16` samples. Panics if this is a float image (a decoder bug, not bad input).
234    pub fn u16_mut(&mut self) -> &mut [u16] {
235        self.data.as_u16_mut().expect("u16 image")
236    }
237    /// Mutable `f32` samples. Panics if this is a `u16` image.
238    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    /// Copy out a sub-rectangle (rows copied in parallel for large images).
245    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    /// Sample at `(x, y, c)` as f32 (convenience for tests/tools; not for hot loops).
278    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}