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}