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