Skip to main content

zenjxl_decoder/image/
typed.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6use std::{fmt::Debug, marker::PhantomData};
7
8use crate::{
9    error::Result,
10    image::internal::DistinctRowsIndexes,
11    util::{CACHE_LINE_BYTE_SIZE, MemoryGuard, MemoryTracker, tracing_wrappers::*},
12};
13
14use super::{ImageDataType, OwnedRawImage, RawImageRect, RawImageRectMut, Rect};
15
16/// Cast a `&[u8]` row to `&[T]`.
17///
18/// With `allow-unsafe`, uses a direct pointer cast (zero overhead). The caller
19/// must uphold the alignment/size invariants established at construction time.
20/// Without `allow-unsafe`, delegates to `bytemuck::cast_slice` which validates
21/// alignment and size on every call.
22#[cfg(feature = "allow-unsafe")]
23#[inline(always)]
24pub(super) fn cast_row<T: ImageDataType>(row: &[u8]) -> &[T] {
25    let new_len = row.len() / std::mem::size_of::<T>();
26    debug_assert!(row.len().is_multiple_of(std::mem::size_of::<T>()));
27    debug_assert!((row.as_ptr() as usize).is_multiple_of(std::mem::align_of::<T>()));
28    #[allow(unsafe_code)]
29    // SAFETY: Alignment and size invariants verified at Image/ImageRect construction
30    // (from_raw asserts both data.is_aligned(T::DATA_TYPE_ID.size()) and that the byte
31    // offset is a multiple of the element size, so every row pointer is aligned to
32    // align_of::<T>()).
33    // The underlying buffer is cache-line aligned (64 bytes ≥ align_of::<T>()),
34    // bytes_per_row = width * sizeof(T), bytes_between_rows is a multiple of
35    // CACHE_LINE_BYTE_SIZE.
36    unsafe {
37        std::slice::from_raw_parts(row.as_ptr().cast::<T>(), new_len)
38    }
39}
40
41#[cfg(not(feature = "allow-unsafe"))]
42#[inline(always)]
43pub(super) fn cast_row<T: ImageDataType>(row: &[u8]) -> &[T] {
44    bytemuck::cast_slice(row)
45}
46
47/// Cast a `&mut [u8]` row to `&mut [T]`. See [`cast_row`] for safety rationale.
48#[cfg(feature = "allow-unsafe")]
49#[inline(always)]
50pub(super) fn cast_row_mut<T: ImageDataType>(row: &mut [u8]) -> &mut [T] {
51    let new_len = row.len() / std::mem::size_of::<T>();
52    debug_assert!(row.len().is_multiple_of(std::mem::size_of::<T>()));
53    debug_assert!((row.as_ptr() as usize).is_multiple_of(std::mem::align_of::<T>()));
54    #[allow(unsafe_code)]
55    // SAFETY: Same invariants as cast_row, plus exclusive (&mut) access.
56    unsafe {
57        std::slice::from_raw_parts_mut(row.as_mut_ptr().cast::<T>(), new_len)
58    }
59}
60
61#[cfg(not(feature = "allow-unsafe"))]
62#[inline(always)]
63pub(super) fn cast_row_mut<T: ImageDataType>(row: &mut [u8]) -> &mut [T] {
64    bytemuck::cast_slice_mut(row)
65}
66
67#[repr(transparent)]
68pub struct Image<T: ImageDataType> {
69    // Safety invariant: self.raw.data and self.raw.byte_offset().0 are aligned to
70    // T::DATA_TYPE_ID.size().
71    raw: OwnedRawImage,
72    _ph: PhantomData<T>,
73}
74
75impl<T: ImageDataType> Image<T> {
76    #[instrument(ret, err)]
77    pub fn new_with_padding(
78        size: (usize, usize),
79        offset: (usize, usize),
80        padding: (usize, usize),
81    ) -> Result<Image<T>> {
82        let s = T::DATA_TYPE_ID.size();
83        let img = OwnedRawImage::new_zeroed_with_padding(
84            (size.0 * s, size.1),
85            (offset.0 * s, offset.1),
86            (padding.0 * s, padding.1),
87        )?;
88        Ok(Self::from_raw(img))
89    }
90
91    #[instrument(ret, err)]
92    pub fn new(size: (usize, usize)) -> Result<Image<T>> {
93        Self::new_with_padding(size, (0, 0), (0, 0))
94    }
95
96    /// Allocates an uninitialized image buffer.
97    ///
98    /// With the `allow-unsafe` feature, the memory is left truly uninitialized
99    /// (saving page-fault and zeroing costs). Without it, the buffer is zeroed.
100    ///
101    /// # Safety contract
102    /// The caller MUST write every pixel before reading it.
103    pub fn new_uninit(size: (usize, usize)) -> Result<Image<T>> {
104        let s = T::DATA_TYPE_ID.size();
105        let img = OwnedRawImage::new_uninit((size.0 * s, size.1))?;
106        Ok(Self::from_raw(img))
107    }
108
109    pub fn new_with_value(size: (usize, usize), value: T) -> Result<Image<T>> {
110        // TODO(veluca): skip zero-initializing the allocation if this becomes
111        // performance-sensitive.
112        let mut ret = Self::new(size)?;
113        ret.fill(value);
114        Ok(ret)
115    }
116
117    /// Computes the allocation size in bytes for an image of the given dimensions.
118    /// This accounts for cache-line alignment of rows.
119    pub fn allocation_size(size: (usize, usize)) -> u64 {
120        let (width, height) = size;
121        if width == 0 || height == 0 {
122            return 0;
123        }
124        let bytes_per_row = width.saturating_mul(T::DATA_TYPE_ID.size());
125        let bytes_between_rows =
126            bytes_per_row.div_ceil(CACHE_LINE_BYTE_SIZE) * CACHE_LINE_BYTE_SIZE;
127        let total = (height - 1)
128            .saturating_mul(bytes_between_rows)
129            .saturating_add(bytes_per_row);
130        total as u64
131    }
132
133    /// Creates a new image after checking the memory tracker budget.
134    /// The image tracks its allocation and releases the budget on drop.
135    #[instrument(ret, err)]
136    pub fn new_tracked(size: (usize, usize), tracker: &MemoryTracker) -> Result<Image<T>> {
137        let alloc_size = Self::allocation_size(size);
138        tracker.try_allocate(alloc_size)?;
139        // Guard ensures budget is rolled back if the allocation below fails.
140        let guard = MemoryGuard::new(tracker.clone(), alloc_size);
141        let mut img = Self::new(size)?;
142        img.raw.set_tracker(tracker.clone(), alloc_size);
143        guard.disarm(); // Ownership transferred to OwnedRawImage's Drop.
144        Ok(img)
145    }
146
147    /// Creates a new image with padding after checking the memory tracker budget.
148    /// The image tracks its allocation and releases the budget on drop.
149    #[instrument(ret, err)]
150    pub fn new_with_padding_tracked(
151        size: (usize, usize),
152        offset: (usize, usize),
153        padding: (usize, usize),
154        tracker: &MemoryTracker,
155    ) -> Result<Image<T>> {
156        let total_width = size.0.saturating_add(offset.0).saturating_add(padding.0);
157        let total_height = size.1.saturating_add(offset.1).saturating_add(padding.1);
158        let alloc_size = Self::allocation_size((total_width, total_height));
159        tracker.try_allocate(alloc_size)?;
160        let guard = MemoryGuard::new(tracker.clone(), alloc_size);
161        let mut img = Self::new_with_padding(size, offset, padding)?;
162        img.raw.set_tracker(tracker.clone(), alloc_size);
163        guard.disarm();
164        Ok(img)
165    }
166
167    #[inline]
168    pub fn size(&self) -> (usize, usize) {
169        (
170            self.raw.byte_size().0 / T::DATA_TYPE_ID.size(),
171            self.raw.byte_size().1,
172        )
173    }
174
175    pub fn offset(&self) -> (usize, usize) {
176        (
177            self.raw.byte_offset().0 / T::DATA_TYPE_ID.size(),
178            self.raw.byte_offset().1,
179        )
180    }
181
182    pub fn padding(&self) -> (usize, usize) {
183        (
184            self.raw.byte_padding().0 / T::DATA_TYPE_ID.size(),
185            self.raw.byte_padding().1,
186        )
187    }
188
189    pub fn fill(&mut self, v: T) {
190        if self.size().0 == 0 {
191            return;
192        }
193        for y in 0..self.size().1 {
194            self.row_mut(y).fill(v);
195        }
196    }
197
198    #[inline]
199    pub fn get_rect_including_padding_mut(&mut self, rect: Rect) -> ImageRectMut<'_, T> {
200        ImageRectMut::from_raw(
201            self.raw
202                .get_rect_including_padding_mut(rect.to_byte_rect(T::DATA_TYPE_ID)),
203        )
204    }
205
206    #[inline]
207    pub fn get_rect_including_padding(&mut self, rect: Rect) -> ImageRect<'_, T> {
208        ImageRect::from_raw(
209            self.raw
210                .get_rect_including_padding(rect.to_byte_rect(T::DATA_TYPE_ID)),
211        )
212    }
213
214    #[inline]
215    pub fn get_rect_mut(&mut self, rect: Rect) -> ImageRectMut<'_, T> {
216        ImageRectMut::from_raw(self.raw.get_rect_mut(rect.to_byte_rect(T::DATA_TYPE_ID)))
217    }
218
219    #[inline]
220    pub fn get_rect(&self, rect: Rect) -> ImageRect<'_, T> {
221        ImageRect::from_raw(self.raw.get_rect(rect.to_byte_rect(T::DATA_TYPE_ID)))
222    }
223
224    pub fn try_clone(&self) -> Result<Self> {
225        Ok(Self::from_raw(self.raw.try_clone()?))
226    }
227
228    pub fn into_raw(self) -> OwnedRawImage {
229        self.raw
230    }
231
232    pub fn from_raw(raw: OwnedRawImage) -> Self {
233        const { assert!(CACHE_LINE_BYTE_SIZE.is_multiple_of(T::DATA_TYPE_ID.size())) };
234        assert!(raw.data.is_aligned(T::DATA_TYPE_ID.size()));
235        assert!(
236            raw.byte_offset().0.is_multiple_of(T::DATA_TYPE_ID.size()),
237            "image byte offset must be aligned to element size"
238        );
239        Image {
240            raw,
241            _ph: PhantomData,
242        }
243    }
244
245    #[inline(always)]
246    pub fn row(&self, row: usize) -> &[T] {
247        cast_row(self.raw.row(row))
248    }
249
250    #[inline(always)]
251    pub fn row_mut(&mut self, row: usize) -> &mut [T] {
252        cast_row_mut(self.raw.row_mut(row))
253    }
254
255    /// Note: this is quadratic in the number of rows. Indexing *ignores any padding rows*, i.e.
256    /// the row at index 0 will be the first row of the *padding*, unlike with all the other row
257    /// accessors.
258    #[inline(always)]
259    pub fn distinct_full_rows_mut<I: DistinctRowsIndexes>(
260        &mut self,
261        rows: I,
262    ) -> I::CastOutput<'_, T> {
263        let rows = self.raw.data.distinct_rows_mut(rows);
264        I::cast_rows(rows)
265    }
266
267    /// Returns mutable slices for all rows. Each slice has exactly `width`
268    /// elements where width = self.size().0. Rows are disjoint within the
269    /// underlying buffer (separated by cache-line-aligned stride).
270    pub fn all_rows_mut(&mut self) -> Vec<&mut [T]> {
271        let (bytes_per_row, num_rows, bytes_between_rows) = self.raw.data.dimensions();
272        if num_rows == 0 {
273            return Vec::new();
274        }
275        let data = self.raw.data.data_slice_mut();
276        // Use a recursive split pattern that the borrow checker can track.
277        // split_rows_recursive handles the "remaining slice" ownership chain.
278        let mut result = Vec::with_capacity(num_rows);
279        split_rows_into(
280            data,
281            bytes_per_row,
282            bytes_between_rows,
283            num_rows,
284            &mut result,
285        );
286        result
287    }
288}
289
290/// Splits a byte slice into per-row mutable `&mut [T]` slices.
291/// Each row is `bytes_per_row` bytes wide, rows are `bytes_between_rows` apart,
292/// and the total spans `num_rows` rows.
293///
294/// Uses a `while let` loop with `Option<&mut [u8]>` to satisfy the borrow
295/// checker: `take()` moves ownership out, `split_at_mut` produces two disjoint
296/// halves, and we re-insert the remainder for the next iteration.
297fn split_rows_into<'a, T: ImageDataType>(
298    data: &'a mut [u8],
299    bytes_per_row: usize,
300    bytes_between_rows: usize,
301    num_rows: usize,
302    out: &mut Vec<&'a mut [T]>,
303) {
304    let mut remaining: Option<&'a mut [u8]> = Some(data);
305    for i in 0..num_rows {
306        let data = remaining.take().unwrap();
307        if i < num_rows - 1 {
308            let (head, tail) = data.split_at_mut(bytes_between_rows);
309            out.push(cast_row_mut(&mut head[..bytes_per_row]));
310            remaining = Some(tail);
311        } else {
312            out.push(cast_row_mut(&mut data[..bytes_per_row]));
313        }
314    }
315}
316
317#[derive(Clone, Copy)]
318pub struct ImageRect<'a, T: ImageDataType> {
319    // Invariant: self.raw.is_aligned(T::DATA_TYPE_ID.size()) is true.
320    raw: RawImageRect<'a>,
321    _ph: PhantomData<T>,
322}
323
324impl<'a, T: ImageDataType> ImageRect<'a, T> {
325    #[inline(always)]
326    pub fn rect(&self, rect: Rect) -> ImageRect<'a, T> {
327        Self::from_raw(self.raw.rect(rect.to_byte_rect(T::DATA_TYPE_ID)))
328    }
329
330    #[inline]
331    pub fn size(&self) -> (usize, usize) {
332        (
333            self.raw.byte_size().0 / T::DATA_TYPE_ID.size(),
334            self.raw.byte_size().1,
335        )
336    }
337
338    #[inline(always)]
339    pub fn row(&self, row: usize) -> &'a [T] {
340        // RawImageRect::row() returns &'a [u8] (the lifetime of the underlying storage),
341        // and cast_row preserves that lifetime.
342        cast_row(self.raw.row(row))
343    }
344
345    pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
346        (0..self.size().1).flat_map(|x| self.row(x).iter().cloned())
347    }
348
349    pub fn into_raw(self) -> RawImageRect<'a> {
350        self.raw
351    }
352
353    #[inline]
354    pub fn from_raw(raw: RawImageRect<'a>) -> Self {
355        const { assert!(CACHE_LINE_BYTE_SIZE.is_multiple_of(T::DATA_TYPE_ID.size())) };
356        assert!(raw.is_aligned(T::DATA_TYPE_ID.size()));
357        ImageRect {
358            raw,
359            _ph: PhantomData,
360        }
361    }
362}
363
364pub struct ImageRectMut<'a, T: ImageDataType> {
365    // Invariant: self.raw.is_aligned(T::DATA_TYPE_ID.size()) is true.
366    raw: RawImageRectMut<'a>,
367    _ph: PhantomData<T>,
368}
369
370impl<'a, T: ImageDataType> ImageRectMut<'a, T> {
371    #[inline]
372    pub fn rect(&'a mut self, rect: Rect) -> ImageRectMut<'a, T> {
373        Self::from_raw(self.raw.rect_mut(rect.to_byte_rect(T::DATA_TYPE_ID)))
374    }
375
376    #[inline]
377    pub fn size(&self) -> (usize, usize) {
378        (
379            self.raw.byte_size().0 / T::DATA_TYPE_ID.size(),
380            self.raw.byte_size().1,
381        )
382    }
383
384    #[inline(always)]
385    pub fn row(&mut self, row: usize) -> &mut [T] {
386        cast_row_mut(self.raw.row(row))
387    }
388
389    pub fn as_rect(&'a self) -> ImageRect<'a, T> {
390        ImageRect::from_raw(self.raw.as_rect())
391    }
392
393    pub fn into_raw(self) -> RawImageRectMut<'a> {
394        self.raw
395    }
396
397    #[inline]
398    pub fn from_raw(raw: RawImageRectMut<'a>) -> Self {
399        const { assert!(CACHE_LINE_BYTE_SIZE.is_multiple_of(T::DATA_TYPE_ID.size())) };
400        assert!(raw.is_aligned(T::DATA_TYPE_ID.size()));
401        ImageRectMut {
402            raw,
403            _ph: PhantomData,
404        }
405    }
406}
407
408impl<T: ImageDataType> Debug for Image<T> {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        write!(
411            f,
412            "{:?} {}x{}",
413            T::DATA_TYPE_ID,
414            self.size().0,
415            self.size().1
416        )
417    }
418}
419
420impl<T: ImageDataType> Debug for ImageRect<'_, T> {
421    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422        write!(
423            f,
424            "{:?} rect {}x{}",
425            T::DATA_TYPE_ID,
426            self.size().0,
427            self.size().1
428        )
429    }
430}
431
432impl<T: ImageDataType> Debug for ImageRectMut<'_, T> {
433    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434        write!(
435            f,
436            "{:?} mutrect {}x{}",
437            T::DATA_TYPE_ID,
438            self.size().0,
439            self.size().1
440        )
441    }
442}