image_texel/
matrix.rs

1// Distributed under The MIT License (MIT)
2//
3// Copyright (c) 2019 The `image-rs` developers
4use core::ops::{Index, IndexMut};
5use core::{cmp, fmt};
6
7use crate::buf::Buffer;
8use crate::image::{Image, RawImage};
9use crate::layout::Matrix as Layout;
10use crate::{layout, AsTexel, BufferReuseError, Texel, TexelBuffer};
11
12/// A 2d, width-major matrix of pixels.
13///
14/// The layout describes placement of samples within the memory buffer. An abstraction layer that
15/// provides strided access to such pixel data is not intended to be baked into this struct.
16/// Instead, it will always store the data in a row-major layout without holes.
17///
18/// There are two levels of control over the allocation behaviour of a `Matrix`. The direct
19/// methods, currently `with_width_and_height` only, lead to an image without intermediate steps
20/// but may panic due to an invalid layout. Manually using the intermediate [`Layout`] gives custom
21/// error handling options and additional offers inspection of the details of the to-be-allocated
22/// buffer. A third option is currently not available and depends on support from the Rust standard
23/// library, which could also handle allocation failures.
24///
25/// ## Usage for trusted inputs
26///
27/// Directly allocate your desired layout with `with_width_and_height`. This may panic when the
28/// allocation itself fails or when the allocation for the layout could not described, as the
29/// layout would not fit inside the available memory space (i.e. the indices would overflow a
30/// `usize`).
31///
32/// ## Usage for untrusted inputs
33///
34/// In some cases, for untrusted input such as in image parsing libraries, more control is desired.
35/// There is no way to currently catch an allocation failure in stable Rust. Thus, even reasonable
36/// bounds can lead to a `panic`, and this is unpreventable (note: when the `try_*` methods of
37/// `Vec` become stable this will change).  But one still may want to check the required size
38/// before allocation.
39///
40/// Firstly, no method will implicitly try to allocate memory and methods that will note the
41/// potential panic from allocation failure.
42///
43/// Secondly, an instance of [`Layout`] can be constructed in a panic free manner without any
44/// allocation and independently from the `Matrix` instance. By providing it to the `with_layout`
45/// constructor ensures that all potential intermediate failures–except as mentioned before–can be
46/// explicitly handled by the caller. Furthermore, some utility methods allow inspection of the
47/// eventual allocation size before the reservation of memory.
48///
49/// ## Restrictions
50///
51/// As previously mentioned, the samples in the internal buffer layout always appear without any
52/// holes. Therefore a fast `crop` operation requires wrapping the abstraction layer provided here
53/// into another layer describing the *accessible image*, independent from the layout of the actual
54/// *pixel data*. This separation of concern–layout versus access logic–simplifies the implementation
55/// and keeps it agnostic of the desired low-cost operations. Consider that other use cases may
56/// require operations other than `crop` with constant time. Instead of choosing some consistent by
57/// limited set here, the mechanism to achieve it is deferred to an upper layer for further
58/// freedom. Other structs may, in the future, provide other pixel layouts.
59///
60/// [`Layout`]: ./struct.Layout.html
61#[derive(Clone, PartialEq, Eq)]
62pub struct Matrix<P> {
63    inner: RawImage<Buffer, Layout<P>>,
64}
65
66/// Error representation for a failed buffer reuse for an image.
67///
68/// Emitted as a result of [`Matrix::from_buffer`] when the buffer capacity is not large enough to
69/// serve as an image of requested layout with causing a reallocation.
70///
71/// It is possible to retrieve the buffer that cause the failure with `into_buffer`. This allows one
72/// to manually try to correct the error with additional checks, or implement a fallback strategy
73/// which does not require the interpretation as a full image.
74///
75/// ```
76/// # use image_texel::{Matrix, layout, TexelBuffer};
77/// let buffer = TexelBuffer::<u8>::new(16);
78/// let allocation = buffer.as_bytes().as_ptr();
79///
80/// let bad_layout = layout::Matrix::width_and_height(buffer.capacity() + 1, 1).unwrap();
81/// let error = match Matrix::from_reused_buffer(buffer, bad_layout) {
82///     Ok(_) => unreachable!("The layout requires one too many pixels"),
83///     Err(error) => error,
84/// };
85///
86/// // Get back the original buffer.
87/// let buffer = error.into_buffer();
88/// assert_eq!(buffer.as_bytes().as_ptr(), allocation);
89/// ```
90///
91/// [`Matrix::from_buffer`]: ./struct.Matrix.html#method.from_buffer
92#[derive(PartialEq, Eq)]
93pub struct MatrixReuseError<P> {
94    buffer: TexelBuffer<P>,
95    layout: Layout<P>,
96}
97
98/// The image could not be mapped to another pixel type without reuse.
99///
100/// This may be caused since the layout would be invalid or due to the layout being too large for
101/// the current buffer allocation.
102///
103/// # Examples
104///
105/// Use the error type to conveniently enforce a custom policy for allowed and prohibited
106/// allocations.
107///
108/// ```
109/// # use image_texel::Matrix;
110/// # let image = Matrix::<u8>::with_width_and_height(2, 2);
111/// # struct RequiredAllocationTooLarge;
112///
113/// match image.map_reuse(f32::from) {
114///     // Everything worked fine.
115///     Ok(image) => Ok(image),
116///     Err(error) => {
117///         // Manually validate if this reallocation should be allowed?
118///         match error.layout() {
119///             // Accept an allocation only if its smaller than a page
120///             Some(layout) if layout.byte_len() <= (1 << 12)
121///                 => Ok(error.into_image().map(f32::from)),
122///             _ => Err(RequiredAllocationTooLarge),
123///         }
124///     },
125/// }
126///
127/// # ;
128/// ```
129#[derive(PartialEq, Eq)]
130pub struct MapReuseError<P, Q> {
131    buffer: Matrix<P>,
132    layout: Option<Layout<Q>>,
133}
134
135impl<P> Matrix<P> {
136    /// Allocate a image with specified layout.
137    ///
138    /// # Panics
139    /// When allocation of memory fails.
140    pub fn with_layout(layout: Layout<P>) -> Self {
141        let rec = TexelBuffer::bytes_for_texel(layout.pixel, layout.byte_len());
142        Self::new_raw(rec, layout)
143    }
144
145    /// Directly try to allocate an image from width and height.
146    ///
147    /// # Panics
148    /// This panics when the layout described by `width` and `height` can not be allocated, for
149    /// example due to it being an invalid layout. If you want to handle the layout being invalid,
150    /// consider using `Layout::from_width_and_height` and `Matrix::with_layout`.
151    ///
152    /// FIXME: on the layout this is named `width_and_height` and is fallible. We should align the
153    /// naming here and this isn't even a `with`-type builder. And do we need this?
154    pub fn with_width_and_height(width: usize, height: usize) -> Self
155    where
156        P: AsTexel,
157    {
158        let layout =
159            Layout::width_and_height(width, height).expect("Texel layout can not fit into memory");
160        Self::with_layout(layout)
161    }
162
163    /// Interpret an existing buffer as a pixel image.
164    ///
165    /// The data already contained within the buffer is not modified so that prior initialization
166    /// can be performed or one array of samples reinterpreted for an image of other sample type.
167    /// However, the `TexelBuffer` will be logically resized which will zero-initialize missing elements if
168    /// the current buffer is too short.
169    ///
170    /// # Panics
171    ///
172    /// This function will panic if resizing causes a reallocation that fails.
173    pub fn from_buffer(mut buffer: TexelBuffer<P>, layout: Layout<P>) -> Self {
174        buffer.resize_bytes(layout.byte_len());
175        Self::new_raw(buffer, layout)
176    }
177
178    /// Reuse an existing buffer for a pixel image.
179    ///
180    /// Similar to `from_buffer` but this function will never reallocate the inner buffer. Instead, it
181    /// will return the `TexelBuffer` unmodified if the creation fails. See [`MatrixReuseError`] for
182    /// further information on the error and retrieving the buffer.
183    ///
184    /// [`MatrixReuseError`]: ./struct.MatrixReuseError.html
185    pub fn from_reused_buffer(
186        mut buffer: TexelBuffer<P>,
187        layout: Layout<P>,
188    ) -> Result<Self, MatrixReuseError<P>> {
189        match buffer.reuse_bytes(layout.byte_len()) {
190            Ok(_) => (),
191            Err(_) => return Err(MatrixReuseError { buffer, layout }),
192        }
193        Ok(Self::new_raw(buffer, layout))
194    }
195
196    fn new_raw(inner: TexelBuffer<P>, layout: Layout<P>) -> Self {
197        assert_eq!(inner.len(), layout.len(), "Texel count agrees with buffer");
198        Matrix {
199            inner: RawImage::from_texel_buffer(inner, layout),
200        }
201    }
202
203    pub fn as_slice(&self) -> &[P] {
204        self.inner.as_slice()
205    }
206
207    pub fn as_mut_slice(&mut self) -> &mut [P] {
208        self.inner.as_mut_slice()
209    }
210
211    pub fn as_bytes(&self) -> &[u8] {
212        self.inner.as_bytes()
213    }
214
215    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
216        self.inner.as_bytes_mut()
217    }
218
219    /// Resize the buffer for a new image.
220    ///
221    /// # Panics
222    ///
223    /// This function will panic if an allocation is necessary but fails.
224    pub fn resize(&mut self, layout: Layout<P>) {
225        self.inner.grow(&layout);
226        *self.inner.layout_mut_unguarded() = layout;
227    }
228
229    /// Reuse the buffer for a new image layout.
230    pub fn reuse(&mut self, layout: Layout<P>) -> Result<(), BufferReuseError> {
231        self.inner.try_reuse(layout)
232    }
233
234    /// Reinterpret to another, same size pixel type.
235    ///
236    /// See [`Matrix::transmute_to`] for details.
237    pub fn transmute<Q: AsTexel>(self) -> Matrix<Q> {
238        self.transmute_to(Q::texel())
239    }
240
241    /// Reinterpret to another, same size pixel type.
242    ///
243    /// # Panics
244    ///
245    /// Like [`core::mem::transmute`], the size of the two types need to be equal. This ensures that
246    /// all indices are valid in both directions.
247    pub fn transmute_to<Q: AsTexel>(self, pixel: Texel<Q>) -> Matrix<Q> {
248        let layout = self.layout().transmute_to(pixel);
249        let inner = self.inner.mogrify_layout(|_| layout);
250        Matrix { inner }
251    }
252
253    /// Get the layout of the matrix.
254    fn layout(&self) -> Layout<P> {
255        *self.inner.layout()
256    }
257
258    pub fn into_buffer(self) -> TexelBuffer<P> {
259        self.inner.into_buffer()
260    }
261
262    fn index_of(&self, x: usize, y: usize) -> usize {
263        self.layout().index_of(x, y)
264    }
265
266    /// Apply a function to all pixel values.
267    ///
268    /// See [`Matrix::map_to`] for the details.
269    ///
270    /// # Panics
271    ///
272    /// This function will panic if the new layout would be invalid (because the new pixel type
273    /// requires a larger buffer than can be allocate) or if the reallocation fails.
274    pub fn map<F, Q>(self, map: F) -> Matrix<Q>
275    where
276        F: Fn(P) -> Q,
277        Q: AsTexel,
278    {
279        self.map_to(map, Q::texel())
280    }
281
282    /// Apply a function to all pixel values.
283    ///
284    /// Unlike [`Matrix::transmute_to`] there are no restrictions on the pixel types. This will
285    /// reuse the underlying buffer or resize it if that is not possible.
286    ///
287    /// # Panics
288    ///
289    /// This function will panic if the new layout would be invalid (because the new pixel type
290    /// requires a larger buffer than can be allocate) or if the reallocation fails.
291    pub fn map_to<F, Q>(self, map: F, pixel: Texel<Q>) -> Matrix<Q>
292    where
293        F: Fn(P) -> Q,
294    {
295        // First compute the new layout ..
296        let layout = self
297            .layout()
298            .map_to(pixel)
299            .expect("Texel layout can not fit into memory");
300        // .. then do the actual pixel mapping.
301        let inner = self.into_buffer().map_to(map, pixel);
302        Matrix::from_buffer(inner, layout)
303    }
304
305    pub fn map_reuse<F, Q>(self, map: F) -> Result<Matrix<Q>, MapReuseError<P, Q>>
306    where
307        F: Fn(P) -> Q,
308        Q: AsTexel,
309    {
310        self.map_reuse_to(map, Q::texel())
311    }
312
313    pub fn map_reuse_to<F, Q>(
314        self,
315        map: F,
316        pixel: Texel<Q>,
317    ) -> Result<Matrix<Q>, MapReuseError<P, Q>>
318    where
319        F: Fn(P) -> Q,
320    {
321        let layout = match self.layout().map_to(pixel) {
322            Some(layout) => layout,
323            None => {
324                return Err(MapReuseError {
325                    buffer: self,
326                    layout: None,
327                })
328            }
329        };
330
331        if self.inner.as_bytes().len() < layout.byte_len() {
332            return Err(MapReuseError {
333                buffer: self,
334                layout: Some(layout),
335            });
336        }
337
338        let inner = self.into_buffer().map_to(map, pixel);
339
340        Ok(Matrix::from_buffer(inner, layout))
341    }
342}
343
344impl<P> Layout<P> {
345    pub fn with_matrix(pixel: Texel<P>, matrix: layout::MatrixBytes) -> Option<Self> {
346        if pixel.size() == matrix.element.size() {
347            Some(Layout {
348                pixel,
349                width: matrix.first_dim,
350                height: matrix.second_dim,
351            })
352        } else {
353            None
354        }
355    }
356
357    pub(crate) fn index_of(self, x: usize, y: usize) -> usize {
358        assert!(self.in_bounds(x, y));
359
360        // Can't overflow, surely smaller than `layout.max_index()`.
361        y * self.width() + x
362    }
363
364    pub(crate) fn in_bounds(self, x: usize, y: usize) -> bool {
365        x < self.width && y < self.height
366    }
367
368    pub(crate) fn max_index(width: usize, height: usize) -> Option<usize> {
369        width.checked_mul(height)
370    }
371}
372
373impl<P> MatrixReuseError<P> {
374    /// Unwrap the original buffer.
375    pub fn into_buffer(self) -> TexelBuffer<P> {
376        self.buffer
377    }
378}
379
380impl<P, Q> MapReuseError<P, Q> {
381    /// Unwrap the original buffer.
382    pub fn into_image(self) -> Matrix<P> {
383        self.buffer
384    }
385
386    /// The layout that would be required to perform the map operation.
387    ///
388    /// Returns `Some(_)` if such a layout can be constructed in theory and return `None` if it
389    /// would exceed the platform address space.
390    pub fn layout(&self) -> Option<Layout<Q>> {
391        self.layout
392    }
393}
394
395impl<P> From<Image<Layout<P>>> for Matrix<P> {
396    fn from(image: Image<Layout<P>>) -> Self {
397        let layout = *image.layout();
398        let rec = image.into_buffer();
399        Self::new_raw(rec, layout)
400    }
401}
402
403impl<P> From<Matrix<P>> for Image<Layout<P>> {
404    fn from(matrix: Matrix<P>) -> Self {
405        let layout = matrix.layout();
406        let rec = matrix.into_buffer();
407        Image::from_buffer(rec, layout)
408    }
409}
410
411impl<P> layout::Layout for Layout<P> {
412    fn byte_len(&self) -> usize {
413        Layout::byte_len(*self)
414    }
415}
416
417impl<P> layout::SliceLayout for Layout<P> {
418    type Sample = P;
419
420    fn sample(&self) -> Texel<P> {
421        self.pixel
422    }
423}
424
425impl<P: AsTexel> Default for Layout<P> {
426    fn default() -> Self {
427        Self::empty(P::texel())
428    }
429}
430
431impl<P> layout::Take for Layout<P> {
432    fn take(&mut self) -> Self {
433        core::mem::replace(self, Self::empty(self.pixel))
434    }
435}
436
437impl<P> fmt::Debug for Layout<P> {
438    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
439        f.debug_struct("Layout")
440            .field("width", &self.width)
441            .field("height", &self.height)
442            .field("pixel", &self.pixel)
443            .finish()
444    }
445}
446
447impl<P> Clone for Layout<P> {
448    fn clone(&self) -> Self {
449        Layout { ..*self }
450    }
451}
452
453impl<P> Copy for Layout<P> {}
454
455impl<P> cmp::PartialEq for Layout<P> {
456    fn eq(&self, other: &Self) -> bool {
457        (self.width, self.height) == (other.width, other.height)
458    }
459}
460
461impl<P> cmp::Eq for Layout<P> {}
462
463impl<P> cmp::PartialOrd for Layout<P> {
464    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
465        if self.width < other.width && self.height < other.height {
466            Some(cmp::Ordering::Less)
467        } else if self.width > other.width && self.height > other.height {
468            Some(cmp::Ordering::Greater)
469        } else if self.width == other.width && self.height == other.height {
470            Some(cmp::Ordering::Equal)
471        } else {
472            None
473        }
474    }
475}
476
477impl<P: AsTexel> Default for Matrix<P> {
478    fn default() -> Self {
479        Matrix::from_buffer(TexelBuffer::default(), Layout::default())
480    }
481}
482
483impl<P> Index<(usize, usize)> for Matrix<P> {
484    type Output = P;
485
486    fn index(&self, (x, y): (usize, usize)) -> &P {
487        &self.as_slice()[self.index_of(x, y)]
488    }
489}
490
491impl<P> IndexMut<(usize, usize)> for Matrix<P> {
492    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut P {
493        let index = self.index_of(x, y);
494        &mut self.as_mut_slice()[index]
495    }
496}
497
498impl<P: fmt::Debug> fmt::Debug for Matrix<P> {
499    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
500        f.debug_struct("Matrix")
501            .field("layout", self.inner.layout())
502            .field("content", &self.inner.as_slice())
503            .finish()
504    }
505}
506
507impl<P> fmt::Debug for MatrixReuseError<P> {
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        write!(
510            f,
511            "Matrix requires {} elements but buffer has capacity for only {}",
512            self.layout.len(),
513            self.buffer.capacity()
514        )
515    }
516}
517
518impl<P, Q> fmt::Debug for MapReuseError<P, Q> {
519    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520        match self.layout {
521            Some(layout) => write!(
522                f,
523                "Mapping image requires {} bytes but current buffer has a capacity of {}",
524                layout.byte_len(),
525                self.buffer.inner.as_capacity_bytes().len(),
526            ),
527            None => write!(f, "Mapped image can not be allocated"),
528        }
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn buffer_reuse() {
538        let rec = TexelBuffer::<u8>::new(4);
539        assert!(rec.capacity() >= 4);
540        let layout = Layout::width_and_height(2, 2).unwrap();
541        let mut image =
542            Matrix::from_reused_buffer(rec, layout).expect("Rec is surely large enough");
543        image
544            .reuse(Layout::width_and_height(1, 1).unwrap())
545            .expect("Can scale down the image");
546        image.resize(Layout::width_and_height(0, 0).unwrap());
547        image
548            .reuse(layout)
549            .expect("Can still reuse original allocation");
550    }
551}