image_texel/
stride.rs

1//! Byte-based, stride operations on an image.
2//!
3//! This is the most general, uniform source of pixel data. The design allows pixels to alias each
4//! other even for mutable operations. The result is always as if performing pixel wise operations
5//! row-for-row and column-by-column, except where otherwise noted.
6//!
7//! In comparison to the standard `Image`, the reference types do not need to rely on the
8//! container and can be constructed from (suitably aligned) byte data. This makes it possible
9//! initialize an image, for example. They internally contain a simple byte slice which allows
10//! viewing any source buffer as a strided matrix even when it was not allocated with the special
11//! allocator.
12use crate::image::Image;
13use crate::layout;
14use crate::layout::{Layout, MismatchedPixelError, TexelLayout, TryMend};
15use crate::texel::{AsTexel, Texel};
16use core::ops::Range;
17
18/// A simple layout describing some pixels as a byte matrix.
19#[derive(Clone, Copy, PartialEq, Eq)]
20pub struct StrideSpec {
21    /// The number of pixels in width direction.
22    pub width: usize,
23    /// The number of pixels in height direction.
24    pub height: usize,
25    /// The number of bytes of a single pixel.
26    ///
27    /// If this differs from both `width_stride` and `height_stride` the any copy must loop over
28    /// individual pixels. Otherwise, whole rows or columns of contiguous data may be inspected.
29    pub element: TexelLayout,
30    /// The number of bytes to go one pixel along the width.
31    pub width_stride: usize,
32    /// The number of bytes to go one pixel along the height.
33    pub height_stride: usize,
34    /// Offset of this matrix from the start.
35    pub offset: usize,
36}
37
38/// A validated layout of a rectangular matrix of pixels, treated as bytes.
39///
40/// The invariants are that the whole layout fits into memory, additionally ensuring that all
41/// indices within have proper indices into the byte slice containing the data.
42///
43/// The related containers [`StridedBufferRef`] and [`StridedBufferMut`] can be utilized to setup
44/// efficient initialization of data from different stride sources. Since they require only the
45/// alignment according to their elements, not according to the maximum alignment, they may be used
46/// for external data that is copied to an image.
47#[derive(Clone, Copy, PartialEq, Eq)]
48pub struct StridedBytes {
49    spec: StrideSpec,
50    /// The total number of bytes, as proof of calculation basically.
51    total: usize,
52}
53
54/// A validated layout of a rectangular matrix of texels.
55///
56/// Similar to [`StridedBytes`] but with a strong type associated to the texel, instead of a mere
57/// layout descriptor for it. This type is still flexible, i.e. you can relax the layout to a pure
58/// byte layout and upgrade to a different texel, for example.
59#[derive(Clone, Copy, PartialEq, Eq)]
60pub struct Strides<T> {
61    inner: StridedBytes,
62    texel: Texel<T>,
63}
64
65/// Error that occurs when a [`StrideSpec`] is invalid.
66#[derive(Debug)]
67pub struct BadStrideError {
68    /// The inner reason for the error.
69    /// Note: used for `Debug` but nowhere else.
70    #[allow(dead_code)]
71    kind: BadStrideKind,
72}
73
74#[derive(Debug)]
75enum BadStrideKind {
76    UnalignedOffset,
77    UnalignedWidthStride,
78    UnalignedHeightStride,
79    OutOfMemory,
80}
81
82/// A reference to byte of a strided matrix.
83pub struct StridedBufferRef<'data> {
84    layout: StridedBytes,
85    data: &'data [u8],
86}
87
88/// A reference to mutable byte of a strided matrix.
89///
90/// This can be constructed from a mutably borrowed image that is currently set to a strided
91/// layout such as a matrix. It can be regarded as a generalization to the standard matrix layout.
92/// Alternatively, it can be constructed directly from a mutable reference to raw bytes.
93///
94/// # Usage
95///
96/// Here is an example of filling a matrix-like image with a constant value.
97///
98/// ```
99/// use image_texel::layout::Matrix;
100/// use image_texel::image::{StridedBufferRef, StridedBufferMut, Image};
101///
102/// let layout = Matrix::<u32>::width_and_height(4, 4).unwrap();
103/// let mut image = Image::new(layout);
104///
105/// let fill = StridedBufferRef::with_repeated_element(&0x42u32, 4, 4);
106/// StridedBufferMut::new(&mut image).copy_from_image(fill);
107///
108/// assert_eq!(image.as_slice(), &[0x42; 16]);
109/// ```
110pub struct StridedBufferMut<'data> {
111    layout: StridedBytes,
112    data: &'data mut [u8],
113}
114
115impl StrideSpec {
116    /// Compare sizes without taking into account the offset or strides.
117    fn matches(&self, other: &Self) -> bool {
118        self.element.size() == other.element.size()
119            && self.width == other.width
120            && self.height == other.height
121    }
122
123    fn has_contiguous_rows(&self) -> bool {
124        self.element.size() == self.width_stride
125    }
126
127    fn has_contiguous_cols(&self) -> bool {
128        self.element.size() == self.height_stride
129    }
130
131    fn element_start(&self, row: usize, col: usize) -> usize {
132        (row * self.height_stride) + (col * self.width_stride) + self.offset
133    }
134
135    fn element(&self, row: usize, col: usize) -> Range<usize> {
136        let start = self.element_start(row, col);
137        start..start + self.element.size()
138    }
139
140    fn contiguous_row(&self, row: usize) -> Range<usize> {
141        let start = self.element_start(row, 0);
142        let length = self.width * self.element.size();
143        start..start + length
144    }
145
146    fn contiguous_col(&self, col: usize) -> Range<usize> {
147        let start = self.element_start(0, col);
148        let length = self.height * self.element.size();
149        start..start + length
150    }
151
152    fn end(&self) -> Option<usize> {
153        if self.height == 0 || self.width == 0 {
154            return Some(self.offset);
155        }
156
157        let max_w = self.width - 1;
158        let max_h = self.height - 1;
159
160        let max_w_offset = max_w.checked_mul(self.width_stride)?;
161        let max_h_offset = max_h.checked_mul(self.height_stride)?;
162
163        let relative_past_end = self
164            .element
165            .size()
166            .checked_add(max_h_offset)?
167            .checked_add(max_w_offset)?;
168
169        // We wouldn't need to validated if there are no elements. However, this is basically the
170        // caller's responsibility. It's more consistent if we keep the offset. For future
171        // additions such as calculating free space (?) this would also be required.
172        let total = relative_past_end.checked_add(self.offset)?;
173        Some(total)
174    }
175}
176
177impl StridedBytes {
178    /// Try to create a new layout from a specification.
179    ///
180    /// This fails if the specification does not describe a valid layout. The reasons for this
181    /// include the element being misaligned according to the provided offsets/strides or the
182    /// layout not describing a memory size expressible on the current architecture.
183    pub fn new(spec: StrideSpec) -> Result<Self, BadStrideError> {
184        if spec.offset % spec.element.align() != 0 {
185            return Err(BadStrideKind::UnalignedOffset.into());
186        }
187
188        if spec.width_stride % spec.element.align() != 0 {
189            return Err(BadStrideKind::UnalignedWidthStride.into());
190        }
191
192        if spec.height_stride % spec.element.align() != 0 {
193            return Err(BadStrideKind::UnalignedHeightStride.into());
194        }
195
196        let total = spec.end().ok_or(BadStrideKind::OutOfMemory)?;
197
198        Ok(StridedBytes { spec, total })
199    }
200
201    /// Construct a layout with zeroed strides, repeating one element.
202    pub fn with_repeated_width_and_height(
203        element: TexelLayout,
204        width: usize,
205        height: usize,
206    ) -> Self {
207        StridedBytes {
208            spec: StrideSpec {
209                element,
210                width,
211                height,
212                height_stride: 0,
213                width_stride: 0,
214                offset: 0,
215            },
216            total: element.size(),
217        }
218    }
219
220    /// Construct from a packed matrix of elements in column major layout.
221    ///
222    /// This is guaranteed to succeed and will construct the strides such that a packed column
223    /// major matrix of elements at offset zero is described.
224    pub fn with_column_major(matrix: layout::MatrixBytes) -> Self {
225        StridedBytes {
226            spec: StrideSpec {
227                element: matrix.element(),
228                width: matrix.width(),
229                height: matrix.height(),
230                height_stride: matrix.element().size(),
231                // Overflow can't happen because all of `matrix` fits in memory according to its own
232                // internal invariant.
233                width_stride: matrix.height() * matrix.element().size(),
234                offset: 0,
235            },
236            total: matrix.byte_len(),
237        }
238    }
239
240    /// Construct from a packed matrix of elements in row major layout.
241    ///
242    /// This is guaranteed to succeed and will construct the strides such that a packed row major
243    /// matrix of elements at offset zero is described.
244    pub fn with_row_major(matrix: layout::MatrixBytes) -> Self {
245        StridedBytes {
246            spec: StrideSpec {
247                element: matrix.element(),
248                width: matrix.width(),
249                height: matrix.height(),
250                // Overflow can't happen because all of `matrix` fits in memory according to its own
251                // internal invariant.
252                height_stride: matrix.width() * matrix.element().size(),
253                width_stride: matrix.element().size(),
254                offset: 0,
255            },
256            total: matrix.byte_len(),
257        }
258    }
259
260    /// Get the specification of this matrix.
261    pub fn spec(&self) -> StrideSpec {
262        self.spec
263    }
264
265    /// Shrink the element's size or alignment.
266    ///
267    /// This is always valid since the new layout is strictly contained within the old one.
268    pub fn shrink_element(&mut self, new: TexelLayout) {
269        self.spec.element = self.spec.element.infimum(new);
270    }
271
272    fn matches(&self, other: &Self) -> bool {
273        self.spec.matches(&other.spec)
274    }
275
276    fn contiguous_rows(&self) -> Option<impl Iterator<Item = Range<usize>> + '_> {
277        if self.spec.has_contiguous_rows() {
278            Some((0..self.spec.height).map(move |row| self.spec.contiguous_row(row)))
279        } else {
280            None
281        }
282    }
283
284    fn contiguous_columns(&self) -> Option<impl Iterator<Item = Range<usize>> + '_> {
285        if self.spec.has_contiguous_cols() {
286            Some((0..self.spec.width).map(move |row| self.spec.contiguous_col(row)))
287        } else {
288            None
289        }
290    }
291
292    fn pixel(&self, x: usize, y: usize) -> Range<usize> {
293        self.spec.element(x, y)
294    }
295}
296
297impl<T> Strides<T> {
298    /// Upgrade a byte specification to a strong typed texel one.
299    ///
300    /// Requires that the element is _exactly_ equivalent to the provided texel.
301    pub fn with_texel(texel: Texel<T>, bytes: StridedBytes) -> Option<Self> {
302        if TexelLayout::from(texel) == bytes.spec.element {
303            Some(Strides {
304                inner: bytes,
305                texel,
306            })
307        } else {
308            None
309        }
310    }
311
312    pub fn spec(&self) -> StrideSpec {
313        self.inner.spec()
314    }
315
316    pub fn texel(&self) -> Texel<T> {
317        self.texel
318    }
319}
320
321impl<'data> StridedBufferRef<'data> {
322    /// Construct a reference to a strided image buffer.
323    pub fn new(image: &'data Image<impl StridedLayout>) -> Self {
324        let layout = image.layout().strided();
325        let data = &image.as_bytes()[..layout.total];
326        StridedBufferRef { layout, data }
327    }
328
329    /// View bytes under a certain strided layout.
330    ///
331    /// Unlike an image, the data need only be aligned to the `element` mentioned in the layout and
332    /// not to the maximum alignment.
333    pub fn with_bytes(layout: StridedBytes, content: &'data [u8]) -> Option<Self> {
334        let data = content
335            .get(..layout.total)
336            .filter(|data| data.as_ptr() as usize % layout.spec.element.align() == 0)?;
337        Some(StridedBufferRef { layout, data })
338    }
339
340    pub fn with_repeated_element<T: AsTexel>(el: &'data T, width: usize, height: usize) -> Self {
341        let texel = T::texel();
342        let layout = StridedBytes::with_repeated_width_and_height(texel.into(), width, height);
343        let data = texel.to_bytes(core::slice::from_ref(el));
344        StridedBufferRef { layout, data }
345    }
346
347    /// Shrink the element's size or alignment.
348    pub fn shrink_element(&mut self, new: TexelLayout) -> TexelLayout {
349        self.layout.shrink_element(new);
350        self.layout.spec.element
351    }
352
353    /// Borrow this as a reference to a strided byte matrix.
354    pub fn as_ref(&self) -> StridedBufferRef<'_> {
355        StridedBufferRef {
356            layout: self.layout,
357            data: &*self.data,
358        }
359    }
360}
361
362impl<'data> StridedBufferMut<'data> {
363    /// Construct a mutable reference to a strided image buffer.
364    pub fn new(image: &'data mut Image<impl StridedLayout>) -> Self {
365        let layout = image.layout().strided();
366        let data = &mut image.as_bytes_mut()[..layout.total];
367        StridedBufferMut { layout, data }
368    }
369
370    /// View bytes mutably under a certain strided layout.
371    ///
372    /// Unlike an image, the data need only be aligned to the `element` mentioned in the layout and
373    /// not to the maximum alignment.
374    pub fn with_bytes(layout: StridedBytes, content: &'data mut [u8]) -> Option<Self> {
375        let data = content
376            .get_mut(..layout.total)
377            .filter(|data| data.as_ptr() as usize % layout.spec.element.align() == 0)?;
378        Some(StridedBufferMut { layout, data })
379    }
380
381    /// Shrink the element's size or alignment.
382    pub fn shrink_element(&mut self, new: TexelLayout) -> TexelLayout {
383        self.layout.shrink_element(new);
384        self.layout.spec.element
385    }
386
387    /// Copy the bytes from another image.
388    ///
389    /// The source must have the same width, height, and element size.
390    pub fn copy_from_image(&mut self, source: StridedBufferRef<'_>) {
391        assert!(self.layout.matches(&source.layout), "Mismatching layouts.");
392        // FIXME: Special case copying for 100% contiguous layouts.
393
394        if let Some(rows) = self.layout.contiguous_rows() {
395            if let Some(src_rows) = source.layout.contiguous_rows() {
396                for (row, src) in rows.zip(src_rows) {
397                    self.data[row].copy_from_slice(&source.data[src]);
398                }
399                return;
400            }
401        }
402
403        if let Some(cols) = self.layout.contiguous_columns() {
404            if let Some(src_cols) = source.layout.contiguous_columns() {
405                for (col, src) in cols.zip(src_cols) {
406                    self.data[col].copy_from_slice(&source.data[src]);
407                }
408                return;
409            }
410        }
411
412        // Panics: we've validated that the widths and heights match.
413        for x in 0..self.layout.spec.width {
414            for y in 0..self.layout.spec.height {
415                let into = self.layout.pixel(x, y);
416                let from = source.layout.pixel(x, y);
417                // Panics: we've validated that the element sizes match.
418                self.data[into].copy_from_slice(&source.data[from]);
419            }
420        }
421    }
422
423    /// Borrow this as a reference to an immutable byte matrix.
424    pub fn as_ref(&self) -> StridedBufferRef<'_> {
425        StridedBufferRef {
426            layout: self.layout,
427            data: &*self.data,
428        }
429    }
430
431    /// Convert this into a reference to an immutable byte matrix.
432    pub fn into_ref(self) -> StridedBufferRef<'data> {
433        StridedBufferRef {
434            layout: self.layout,
435            data: self.data,
436        }
437    }
438}
439
440/// A layout that is a strided matrix of elements.
441///
442/// Like all layout traits, implementations should ensure that the layout returned in these methods
443/// occupied a subset of pixels of their original layout.
444pub trait StridedLayout: Layout {
445    /// The valid strided specification of this layout.
446    ///
447    /// This call should not fail, or panic. Otherwise, prefer an optional getter for the
448    /// `StridedBytes` and have the caller decay their own buffer.
449    fn strided(&self) -> StridedBytes;
450}
451
452impl Layout for StridedBytes {
453    fn byte_len(&self) -> usize {
454        self.total
455    }
456}
457
458impl StridedLayout for StridedBytes {
459    fn strided(&self) -> StridedBytes {
460        *self
461    }
462}
463
464impl<T: StridedLayout> StridedLayout for &'_ T {
465    fn strided(&self) -> StridedBytes {
466        (**self).strided()
467    }
468}
469
470impl<T: StridedLayout> StridedLayout for &'_ mut T {
471    fn strided(&self) -> StridedBytes {
472        (**self).strided()
473    }
474}
475
476impl<T: StridedLayout> layout::Decay<T> for StridedBytes {
477    fn decay(from: T) -> Self {
478        from.strided()
479    }
480}
481
482impl<P: AsTexel> StridedLayout for layout::Matrix<P> {
483    fn strided(&self) -> StridedBytes {
484        let matrix: layout::MatrixBytes = self.clone().into();
485        StridedBytes::with_row_major(matrix)
486    }
487}
488
489impl<P> Layout for Strides<P> {
490    fn byte_len(&self) -> usize {
491        self.inner.total
492    }
493}
494
495impl<P> StridedLayout for Strides<P> {
496    fn strided(&self) -> StridedBytes {
497        self.inner.clone()
498    }
499}
500
501impl From<BadStrideKind> for BadStrideError {
502    fn from(kind: BadStrideKind) -> Self {
503        BadStrideError { kind }
504    }
505}
506
507impl From<&'_ StridedBytes> for StrideSpec {
508    fn from(layout: &'_ StridedBytes) -> Self {
509        layout.spec()
510    }
511}
512
513/// Try to use the matrix with a specific pixel type.
514impl<P> TryMend<StridedBytes> for Texel<P> {
515    type Into = Strides<P>;
516    type Err = MismatchedPixelError;
517
518    fn try_mend(self, matrix: &StridedBytes) -> Result<Strides<P>, Self::Err> {
519        Strides::with_texel(self, *matrix).ok_or_else(MismatchedPixelError::default)
520    }
521}
522
523#[test]
524fn align_validation() {
525    // Setup a good base specification.
526    let matrix = layout::MatrixBytes::from_width_height(TexelLayout::from_pixel::<u16>(), 2, 2)
527        .expect("Valid matrix");
528    let layout = StridedBytes::with_row_major(matrix);
529
530    let bad_offset = StrideSpec {
531        offset: 1,
532        ..layout.spec
533    };
534    assert!(StridedBytes::new(bad_offset).is_err());
535    let bad_pitch = StrideSpec {
536        width_stride: 5,
537        ..layout.spec
538    };
539    assert!(StridedBytes::new(bad_pitch).is_err());
540}
541
542#[test]
543fn image_copies() {
544    let matrix = layout::MatrixBytes::from_width_height(TexelLayout::from_pixel::<u8>(), 2, 2)
545        .expect("Valid matrix");
546    let row_layout = StridedBytes::with_row_major(matrix);
547    let col_layout = StridedBytes::with_column_major(matrix);
548
549    let src = Image::with_bytes(row_layout, &[0u8, 1, 2, 3]);
550
551    let mut dst = Image::new(row_layout);
552    StridedBufferMut::new(&mut dst).copy_from_image(StridedBufferRef::new(&src));
553    assert_eq!(dst.as_bytes(), &[0u8, 1, 2, 3], "Still in same order");
554
555    let mut dst = Image::new(col_layout);
556    StridedBufferMut::new(&mut dst).copy_from_image(StridedBufferRef::new(&src));
557    assert_eq!(
558        dst.as_bytes(),
559        &[0u8, 2, 1, 3],
560        "In transposed matrix order"
561    );
562}