// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
use std::{
alloc::{Layout, alloc, alloc_zeroed, dealloc},
fmt::Debug,
mem::MaybeUninit,
ptr::null_mut,
};
#[derive(Debug, Clone, Copy)] pub(super) struct RawImageBuffer { // Safety invariant: // - uninit data is never written to `buf`. // - `buf` is valid for reads for all bytes in the ranges // `buf[i*bytes_between_rows..i*bytes_between_rows+bytes_per_row]` for all values of `i` // from `0` to `num_rows-1`. // - all the bytes in those ranges (and in between) are part of the same allocated object. // - if `num_rows > 0`, then `bytes_per_row > 0`, `bytes_per_row <= bytes_between_rows`. // - The computation `E = bytes_between_rows * (num_rows-1) + bytes_per_row` does not // overflow and has a result that is at most `isize::MAX`, or num_rows is 0. // We will call the bytes in the range above the "accessible bytes" of a RawImageBuffer. // Note that wrapper structs around RawImageBuffer might have a safety invariant that declares // that the struct has write access to the accessible bytes of a RawImageBuffer; in that case, // that is equivalent to saying that `buf` is *also* valid for *writes*.
buf: *mut MaybeUninit<u8>,
bytes_per_row: usize,
num_rows: usize,
bytes_between_rows: usize,
}
// SAFETY: The safety invariant on RawImageBuffer enforces ownership rules on the contained data, // there is no reason for it not to be Send. unsafeimpl Send for RawImageBuffer {}
// SAFETY: RawImageBuffer does not use any kind of interior mutability, so it is safe to share // between threads. unsafeimpl Sync for RawImageBuffer {}
/// Checks that self.buf, self.bytes_per_row, self.bytes_between_rows are all multiple of align. /// This guarantees that `self.get_row()` and `self.get_row_mut()` return slices aligned to /// `align`. #[inline(always)] pub(super) fn is_aligned(&self, align: usize) -> bool { self.bytes_per_row.is_multiple_of(align)
&& self.bytes_between_rows.is_multiple_of(align)
&& (self.buf as usize).is_multiple_of(align)
}
/// Creates a new RawImageBuffer from raw pointers. /// It is guaranteed that `buf` will never be used to write uninitialized data. /// /// # Safety /// - `buf` must be valid for reads for all bytes in the range /// `buf[i*bytes_between_rows..i*bytes_between_rows+bytes_per_row]` for all values of `i` /// from `0` to `num_rows-1`. /// - The bytes in these ranges must not be accessed as long as the returned `Self` is in scope. /// - All the bytes in those ranges (and in between) must be part of the same allocated object. /// /// Note: these safety requirements match those of JxlOutputBuffer::new_from_ptr, except we /// only request validity for reads. pub(super) unsafefn new_from_ptr(
buf: *mut MaybeUninit<u8>,
num_rows: usize,
bytes_per_row: usize,
bytes_between_rows: usize,
) -> Self {
RawImageBuffer::check_vals(num_rows, bytes_per_row, bytes_between_rows); // SAFETY: caller guarantees the buf-related requirements, and other safety invariants are // checked in check_vals. Self {
buf,
bytes_per_row,
bytes_between_rows,
num_rows,
}
}
/// Returns the minimum size that the allocation containing the image data must have, or 0 if /// this is an empty image. pub(super) fn minimum_allocation_size(&self) -> usize { ifself.num_rows == 0 { 0
} else { // Note: the safety invariant guarantees no overflow.
(self.num_rows - 1) * self.bytes_between_rows + self.bytes_per_row
}
}
/// # Safety /// - No uninit data must be written to the returned slice. /// - The caller must ensure that ownership rules are respected (for example, because they /// have exclusive access to the data). #[inline(always)] pub(super) unsafefn row_mut(&mutself, row: usize) -> &mut [MaybeUninit<u8>] { // SAFETY: The safety requirements for distinct_rows_mut match the ones for row_mut. unsafe { self.distinct_rows_mut([row])[0] }
}
/// Note: this is quadratic in the number of rows. /// # Safety /// - No uninit data must be written to the returned slice. /// - The caller must ensure that ownership rules are respected (for example, because they /// have exclusive access to the data). #[inline(always)] pub(super) unsafefn distinct_rows_mut<I: DistinctRowsIndexes>(
&mutself,
rows: I,
) -> I::Output<'_, MaybeUninit<u8>> { // SAFETY: the safety requirements of `get_rows_mut` are the same as the ones for // `distinct_rows_mut`. unsafe { rows.get_rows_mut(self) }
}
/// # Safety /// The caller must ensure that ownership and lifetime rules are respected (for example, /// because they have shared access to the data). #[inline(always)] pub(super) unsafefn row(&self, row: usize) -> &[MaybeUninit<u8>] {
assert!(row < self.num_rows); let start = row * self.bytes_between_rows; // SAFETY: `start` is guaranteed to be <= isize::MAX, and `self.buf + start` is guaranteed // to fit within the same allocated object, as per safety invariants of this struct. // We checked above that `row` and `cols` satisfy the requirements to apply the safety // invariant. let start = unsafe { self.buf.add(start) }; // SAFETY: due to the struct safety invariant, we know the entire slice is in a range of // memory valid for reads. The caller guarantees aliasing rules will not be violated. unsafe { std::slice::from_raw_parts(start, self.bytes_per_row) }
}
/// Extracts a sub-rectangle from this buffer. Rectangle coordinates are in bytes. /// Safety note: the returned RawImageBuffer retains the same kind of access (read or write) as `self`. pub(super) fn rect(&self, rect: Rect) -> Self { if rect.size.0 == 0 || rect.size.1 == 0 { returnSelf::empty();
} // More helpful message in debug builds (not needed for safety). if cfg!(debug_assertions) {
rect.check_within(self.byte_size());
}
assert!(rect.origin.1 < self.num_rows);
assert!(rect.origin.1.checked_add(rect.size.1).unwrap() <= self.num_rows);
assert!(rect.origin.0 < self.bytes_per_row);
assert!(rect.origin.0.checked_add(rect.size.0).unwrap() <= self.bytes_per_row); // SAFETY: the safety invariant of `self`, together with the above check, guarantees that // the calculation does not overflow and that the new pointer stays within the bounds of // the allocation. let start_ptr = unsafe { self.buf
.add(rect.origin.1 * self.bytes_between_rows + rect.origin.0)
}; // SAFETY: Thanks to the check above, all the bytes accessible from `buf` at the // appropriate ranges are a subset of the ones accessible from `self.buf` at the // correct ranges for `self`. Thus, the safety invariant of `self` ensures that // the safety preconditions of this call are satisfied. unsafe { Self::new_from_ptr(start_ptr, rect.size.1, rect.size.0, self.bytes_between_rows) }
}
/// Returns zeroed memory if `uninit` is `false`, otherwise it returns uninitialized /// memory. The returned buffer is aligned to CACHE_LINE_BYTE_SIZE bytes. /// The returned RawImageBuffer owns the memory it references, which belongs to a single /// allocation of size minimum_allocation_size(). pub(super) fn try_allocate(byte_size: (usize, usize), uninit: bool) -> Result<RawImageBuffer> { let (bytes_per_row, num_rows) = byte_size; // To simplify modular transform logic, we allow empty images, because some modular // meta-images can have 0 bytes_per_row or num_rows (e.g. delta-palette, reference property image). if bytes_per_row == 0 || num_rows == 0 { return Ok(RawImageBuffer::empty());
} // These limits let us not worry about overflows. if bytes_per_row as u64 >= i64::MAX as u64 / 4 || num_rows as u64 >= i64::MAX as u64 / 4 { return Err(Error::ImageSizeTooLarge(bytes_per_row, num_rows));
}
debug!("trying to allocate image"); let bytes_between_rows =
bytes_per_row.div_ceil(CACHE_LINE_BYTE_SIZE) * CACHE_LINE_BYTE_SIZE; // Note: matches RawImageBuffer::minimum_allocation_size. let allocation_len = (num_rows - 1)
.checked_mul(bytes_between_rows)
.unwrap()
.checked_add(bytes_per_row)
.unwrap();
assert_ne!(allocation_len, 0); let layout = Layout::from_size_align(allocation_len, CACHE_LINE_BYTE_SIZE).unwrap(); // SAFETY: we just checked that allocation_len is not 0. let memory = unsafe { if uninit {
alloc(layout)
} else {
alloc_zeroed(layout)
}
}; if memory.is_null() { return Err(Error::ImageOutOfMemory(bytes_per_row, num_rows));
} // SAFETY: `memory` points to a contiguous array of size minimum_allocation_size(), and we // transfer ownership so the validity requirements are satisfied.
Ok(unsafe {
RawImageBuffer::new_from_ptr(
memory as *mut MaybeUninit<u8>,
num_rows,
bytes_per_row,
bytes_between_rows,
)
})
}
/// Returns a copy of the current buffer contents in a new buffer that owns the returned data. /// The data is allocated with `try_allocate` so that it matches the size of the current image. /// /// This function is meant to be used when `self` is an owned buffer, and will panic if the /// bytes between rows of a newly allocated image with the same size does not match the value /// for `self`. /// /// # Safety /// The caller must ensure that the data referenced by self -- *all* /// self.minimum_allocation_size() bytes starting from self.buf, not just the accessible bytes /// -- can be read. pub(super) unsafefn try_clone(&self) -> Result<Self> { let out = RawImageBuffer::try_allocate(self.byte_size(), true)?;
assert_eq!(self.bytes_per_row, out.bytes_per_row);
assert_eq!(self.bytes_between_rows, out.bytes_between_rows);
assert_eq!(self.num_rows, out.num_rows); let data_len = self.minimum_allocation_size();
assert_eq!( self.minimum_allocation_size(),
out.minimum_allocation_size()
); if data_len != 0 { // SAFETY: since both `self` and `out` own the allocation, which has size `data_len`, this copy // is safe. unsafe {
std::ptr::copy_nonoverlapping(self.buf, out.buf, data_len);
}
}
Ok(out)
}
/// Deallocates an owning buffer that was allocated by try_allocate. /// /// # Safety /// The data referenced by `self` must have been allocated with Self::try_allocate. pub(super) unsafefn deallocate(&mutself) { if !self.buf.is_null() { let allocation_len = self.minimum_allocation_size(); let layout = Layout::from_size_align(allocation_len, CACHE_LINE_BYTE_SIZE).unwrap(); // SAFETY: the buffer was allocated in `try_allocate` with the same layout. unsafe {
dealloc(self.buf as *mut u8, layout);
}
}
}
}
#[allow(private_interfaces)] pubtrait DistinctRowsIndexes { type Output<'a, T: 'static>;
/// # Safety /// - No uninit data must be written to the returned slice. /// - The caller must ensure that ownership rules are respected (for example, because they /// have exclusive access to the data). unsafefn get_rows_mut<'a>(
&self,
image: &'a mut RawImageBuffer,
) -> Self::Output<'a, MaybeUninit<u8>>;
/// # Safety /// - The rows are properly aligned /// - The rows contain data that is valid for type T (and thus initialized). unsafefn transmute_rows<'a, T: 'static>(
rows: Self::Output<'a, MaybeUninit<u8>>,
) -> Self::Output<'a, T>;
}
#[allow(private_interfaces)] impl<const S: usize> DistinctRowsIndexes for [usize; S] { type Output<'a, T: 'static> = [&'a mut [T]; S];
#[inline(always)] unsafefn get_rows_mut<'a>(
&self,
image: &'a mut RawImageBuffer,
) -> Self::Output<'a, MaybeUninit<u8>> { for i in0..S {
assert!(self[i] < image.num_rows); for j in i + 1..S {
assert_ne!(self[i], self[j]);
}
} let start = self.map(|row| row * image.bytes_between_rows); let start = start.map(|start| { // SAFETY: `start` is guaranteed to be <= isize::MAX, and `self.buf + start` is guaranteed // to fit within the same allocated object, as per safety invariants of the image struct. // We checked above that `row` satisfies the requirements to apply the safety invariant. unsafe { image.buf.add(start) }
});
start.map(|start| { // SAFETY: due to the struct safety invariant, we know the entire slice is in a range of // memory valid for writes. Moreover, the caller promises not to write uninitialized // data in the returned slice. Finally, the caller guarantees aliasing rules will not // be violated outside of this struct, and since we checked that all the values of // `self` are distinct they are also not violated across the various slices returned by unsafe { std::slice::from_raw_parts_mut(start, image.bytes_per_row) }
})
}
#[inline(always)] unsafefn transmute_rows<'a, T: 'static>(
rows: Self::Output<'a, MaybeUninit<u8>>,
) -> Self::Output<'a, T> {
rows.map(|row| { // SAFETY: The caller guarantees the transmute is safe and proper alignment. unsafe {
std::slice::from_raw_parts_mut(
row.as_mut_ptr().cast::<T>(),
row.len() / std::mem::size_of::<T>(),
)
}
})
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.