Skip to main content

grixy/
transform.rs

1//! Transformation operations for grids.
2//!
3//! [`GridConvertExt`] is automatically implemented for all types that implement reading, or in the
4//! case of [`blend`][GridConvertExt::blend], reading _and_ writing from a grid, and provides
5//! additional methods for lazily transforming grids (leaving the original grid unchanged, and
6//! without any allocations).
7//!
8//! Operations include:
9//!
10//! - [`blend`](GridConvertExt::blend): Creates a blended version of the grid, applying a blend function when setting elements.
11//! - [`copied`](GridConvertExt::copied): Creates a grid that copies all of its elements.
12//! - [`flatten`](GridConvertExt::flatten): Collects the elements of the grid into a new buffer.
13//! - [`map`](GridConvertExt::map): Creates a grid that applies a mapping function to its elements.
14//! - [`scale`](GridConvertExt::scale): Creates a scaled version of the grid.
15//! - [`view`](GridConvertExt::view): Creates a view of the grid over a specified rectangular region.
16//!
17//! ## Chaining transformations
18//!
19//! Methods on [`GridConvertExt`] can be chained together to create complex transformations, as
20//! they consume the grid and return a new one. This allows for a functional style of programming
21//! where each transformation is applied in sequence, without modifying the original grid:
22//!
23//! ```rust
24//! use grixy::prelude::*;
25//!
26//! let grid = GridBuf::new_filled(3, 3, 1)
27//!   .copied()
28//!   .map(|x| x * 2)
29//!   .view(Rect::from_ltwh(0, 0, 2, 2))
30//!   .scale(2);
31//!
32//! assert_eq!(grid.get(Pos::new(1, 1)), Some(2));
33//! ```
34//!
35//! ## Sharing a grid
36//!
37//! To share the original grid, you can use `Rc` or `Arc` to wrap it:
38//!
39//! ```rust
40//! // Or alloc::rc::Rc;
41//! use std::rc::Rc;
42//! use grixy::prelude::*;
43//!
44//! let rc = Rc::new(GridBuf::new_filled(3, 3, 1));
45//!
46//! let rf = Rc::clone(&rc);
47//! let chained = rf
48//!   .copied()
49//!   .map(|x| x * 2)
50//!   .view(Rect::from_ltwh(0, 0, 2, 2))
51//!   .scale(2);
52//! assert_eq!(chained.get(Pos::new(1, 1)), Some(2));
53//!
54//! // Original grid is still accessible
55//! assert_eq!(rc.get(Pos::new(1, 1)), Some(&1));
56//! ```
57
58use core::marker::PhantomData;
59
60#[cfg(feature = "buffer")]
61use crate::ops::{ExactSizeGrid, layout};
62use crate::{
63    core::Rect,
64    ops::{GridRead, GridWrite},
65};
66
67mod blended;
68pub use blended::Blended;
69
70mod copied;
71pub use copied::Copied;
72
73mod mapped;
74pub use mapped::Mapped;
75
76mod scaled;
77pub use scaled::Scaled;
78
79mod viewed;
80pub use viewed::Viewed;
81
82/// Extension trait for converting grids into different forms.
83pub trait GridConvertExt: GridRead {
84    /// Creates a grid that copies all of its elements.
85    ///
86    /// This is useful when you have a `GridRead<&T>`, but need a `GridRead<T>`.
87    ///
88    /// ## Examples
89    ///
90    /// ```rust
91    /// use grixy::prelude::*;
92    ///
93    /// // By default, `GridBuf` returns references to its elements (similar to `Vec`).
94    /// let grid = GridBuf::new_filled(3, 3, 1);
95    /// assert_eq!(grid.get(Pos::new(1, 1)), Some(&1));
96    ///
97    /// // We can create a `GridRead` that returns owned copies of the elements.
98    /// let copied = grid.copied();
99    /// assert_eq!(copied.get(Pos::new(1, 1)), Some(1));
100    /// ```
101    fn copied<'a, T>(self) -> Copied<T, Self>
102    where
103        Self: Sized + GridRead<Element<'a> = &'a T> + 'a,
104        T: Copy + 'a,
105    {
106        Copied {
107            source: self,
108            _element: PhantomData,
109        }
110    }
111
112    /// Creates a grid that applies a mapping function to its elements.
113    ///
114    /// This is useful when you want to transform the elements of a grid lazily.
115    ///
116    /// ## Examples
117    ///
118    /// ```rust
119    /// use grixy::prelude::*;
120    ///
121    /// let grid = GridBuf::new_filled(3, 3, 1);
122    /// let mapped = grid.map(|&x| x * 2);
123    /// assert_eq!(mapped.get(Pos::new(1, 1)), Some(2));
124    /// ```
125    fn map<F, T>(self, map_fn: F) -> Mapped<F, Self, T>
126    where
127        Self: Sized,
128        F: Fn(Self::Element<'_>) -> T,
129    {
130        Mapped {
131            source: self,
132            map_fn,
133            _element: PhantomData,
134        }
135    }
136
137    /// Creates a view of the grid over a specified rectangular region.
138    ///
139    /// The view is a lightweight wrapper that allows access to a subset of the grid's elements.
140    ///
141    /// ## Examples
142    ///
143    /// ```rust
144    /// use grixy::prelude::*;
145    ///
146    /// let grid = GridBuf::new_filled(3, 3, 1);
147    /// let view = grid.view(Rect::from_ltwh(0, 0, 2, 2));
148    /// assert_eq!(view.get(Pos::new(1, 1)), Some(&1));
149    /// assert_eq!(view.get(Pos::new(2, 2)), None);
150    /// ```
151    fn view(self, bounds: Rect) -> Viewed<Self>
152    where
153        Self: Sized,
154    {
155        Viewed {
156            source: self,
157            bounds,
158        }
159    }
160
161    /// Creates a scaled version of the grid.
162    ///
163    /// The `scale` factor determines how many cells in the original grid correspond to one cell
164    /// in the scaled grid. For example, a scale factor of 2 means that each cell in the scaled grid
165    /// corresponds to a 2x2 block of cells in the original grid.
166    ///
167    /// ## Examples
168    ///
169    /// ```rust
170    /// use grixy::prelude::*;
171    ///
172    /// let grid = GridBuf::new_filled(2, 2, 1);
173    /// let scaled = grid.scale(2);
174    /// assert_eq!(scaled.get(Pos::new(0, 0)), Some(&1));
175    /// assert_eq!(scaled.get(Pos::new(1, 1)), Some(&1));
176    /// assert_eq!(scaled.get(Pos::new(2, 2)), Some(&1));
177    /// assert_eq!(scaled.get(Pos::new(3, 3)), Some(&1));
178    /// assert_eq!(scaled.get(Pos::new(4, 4)), None);
179    /// ```
180    fn scale(self, factor: usize) -> Scaled<Self>
181    where
182        Self: Sized,
183    {
184        Scaled {
185            source: self,
186            scale: factor,
187        }
188    }
189
190    /// Collects the elements of the grid into a new buffer.
191    ///
192    /// This method is only available when the `buffer` feature is enabled.
193    ///
194    /// ## Examples
195    ///
196    /// ```rust
197    /// use grixy::prelude::*;
198    ///
199    /// let grid = GridBuf::new_filled(3, 3, 1);
200    /// let collected = grid.copied().flatten::<Vec<_>, RowMajor>();
201    /// assert_eq!(collected.get(Pos::new(1, 1)), Some(&1));
202    /// assert_eq!(collected.get(Pos::new(3, 3)), None);
203    /// ```
204    #[cfg(feature = "buffer")]
205    fn flatten<'a, B, L>(&'a self) -> crate::buf::GridBuf<Self::Element<'a>, B, L>
206    where
207        B: FromIterator<Self::Element<'a>> + AsRef<[Self::Element<'a>]>,
208        L: layout::LinearLayout,
209        Self: Sized + ExactSizeGrid,
210        Self::Element<'a>: Copy,
211    {
212        use crate::core::Rect;
213
214        let iter = self.iter_rect(Rect::from_ltwh(0, 0, self.width(), self.height()));
215        let elem = iter.collect::<B>();
216        crate::buf::GridBuf::from_buffer(elem, self.width())
217    }
218
219    /// Creates a blended version of this grid, applying a blend function when setting elements.
220    ///
221    /// This is useful for operations like blending colors or combining values.
222    ///
223    /// ## Examples
224    ///
225    /// ```rust
226    /// use grixy::prelude::*;
227    ///
228    /// let mut grid = GridBuf::new_filled(3, 3, 1);
229    /// let blend_fn = |current: &i32, new: i32| current + new;
230    /// let mut blended = grid.blend(blend_fn);
231    ///
232    /// blended.set(Pos::new(1, 1), 5).unwrap();
233    /// assert_eq!(blended.get(Pos::new(1, 1)), Some(&6));
234    /// ```
235    fn blend<F>(&mut self, blend_fn: F) -> Blended<'_, Self, F>
236    where
237        Self: Sized + GridWrite,
238        F: Fn(
239            <Self as GridRead>::Element<'_>,
240            <Self as GridWrite>::Element,
241        ) -> <Self as GridWrite>::Element,
242    {
243        Blended {
244            source: self,
245            blend_fn,
246        }
247    }
248}
249
250impl<T> GridConvertExt for T where T: GridRead {}
251
252// The whole module exercises `GridBuf` (requires `buffer`) and `alloc::{rc, sync}` (requires
253// `alloc`), so gate it accordingly rather than assuming `--all-features` like the rest of the
254// crate's tests happen to (this is what let a `--no-default-features`/partial-feature build
255// silently fail to compile its own test suite).
256#[cfg(all(test, feature = "buffer", feature = "alloc"))]
257mod tests {
258    extern crate alloc;
259
260    use crate::{
261        buf::GridBuf,
262        core::{Pos, Rect},
263        ops::{GridBase as _, layout::RowMajor},
264    };
265    use alloc::{vec, vec::Vec};
266    use ixy::HasSize as _;
267
268    use super::*;
269
270    #[test]
271    fn grid_copied_size() {
272        let grid = GridBuf::<u8, _, _>::new(10, 10).copied();
273        let (size, _) = grid.size_hint();
274        assert_eq!(size.width(), 10);
275        assert_eq!(size.height(), 10);
276    }
277
278    #[test]
279    fn grid_copied_get() {
280        let grid = GridBuf::new_filled(3, 3, 1);
281        let copied = grid.copied();
282        assert_eq!(copied.get(Pos::new(1, 1)), Some(1));
283        assert_eq!(copied.get(Pos::new(3, 3)), None);
284    }
285
286    #[test]
287    fn grid_copied_iter_rect() {
288        let grid = GridBuf::new_filled(3, 3, 1);
289        let copied = grid.copied();
290        let elements: Vec<_> = copied.iter_rect(Rect::from_ltwh(0, 0, 2, 2)).collect();
291        assert_eq!(elements, vec![1, 1, 1, 1]);
292    }
293
294    #[test]
295    fn grid_mapped_size() {
296        let grid = GridBuf::<u8, _, _>::new(10, 10);
297        let mapped = grid.map(|x| x * 2);
298        let (size, _) = mapped.size_hint();
299        assert_eq!(size.width(), 10);
300        assert_eq!(size.height(), 10);
301    }
302
303    #[test]
304    fn grid_mapped_get() {
305        let grid = GridBuf::new_filled(3, 3, 1);
306        let mapped = grid.map(|x| x * 2);
307        assert_eq!(mapped.get(Pos::new(1, 1)), Some(2));
308        assert_eq!(mapped.get(Pos::new(3, 3)), None);
309    }
310
311    #[test]
312    fn grid_mapped_iter_rect() {
313        let grid = GridBuf::new_filled(3, 3, 1);
314        let mapped = grid.map(|x| x * 2);
315        let elements: Vec<_> = mapped.iter_rect(Rect::from_ltwh(0, 0, 2, 2)).collect();
316        assert_eq!(elements, vec![2, 2, 2, 2]);
317    }
318
319    #[test]
320    fn grid_view_size() {
321        let grid = GridBuf::<u8, _, _>::new(10, 10);
322        let view = grid.view(Rect::from_ltwh(0, 0, 5, 5));
323        let (size, _) = view.size_hint();
324        assert_eq!(size.width(), 5);
325        assert_eq!(size.height(), 5);
326    }
327
328    #[test]
329    fn grid_view_get() {
330        let grid = GridBuf::new_filled(3, 3, 1);
331        let view = grid.view(Rect::from_ltwh(0, 0, 2, 2));
332        assert_eq!(view.get(Pos::new(1, 1)), Some(&1));
333        assert_eq!(view.get(Pos::new(2, 2)), None);
334    }
335
336    #[test]
337    fn grid_view_iter_rect() {
338        let grid = GridBuf::new_filled(3, 3, 1);
339        let view = grid.view(Rect::from_ltwh(0, 0, 2, 2));
340        let elements: Vec<_> = view.iter_rect(Rect::from_ltwh(0, 0, 2, 2)).collect();
341        assert_eq!(elements, &[&1, &1, &1, &1]);
342    }
343
344    #[test]
345    fn grid_view_get_with_offset_bounds() {
346        // Regression test for https://github.com/crates-lurey-io/grixy/issues/16: `Viewed::get`
347        // used to double-offset `pos` (and underflow-panic near the origin) whenever
348        // `bounds.top_left() != Pos::ORIGIN`. Every other `view` test above only exercises
349        // origin-aligned bounds, where subtracting or adding the (zero) offset is indistinguishable.
350        #[rustfmt::skip]
351        let grid = GridBuf::<_, _, RowMajor>::from_buffer(vec![
352            1, 2, 3,
353            4, 5, 6,
354            7, 8, 9,
355        ], 3);
356        let view = grid.view(Rect::from_ltwh(1, 1, 2, 2));
357
358        // View-local (0, 0) is the view's own top-left, i.e. source (1, 1) = 5. Used to panic
359        // with "attempt to subtract with overflow".
360        assert_eq!(view.get(Pos::new(0, 0)), Some(&5));
361        // View-local (1, 1) is the view's own bottom-right, i.e. source (2, 2) = 9.
362        assert_eq!(view.get(Pos::new(1, 1)), Some(&9));
363        // Out of the view's own 2x2 bounds, even though it's a valid position in `grid`.
364        assert_eq!(view.get(Pos::new(2, 2)), None);
365    }
366
367    #[test]
368    fn grid_view_iter_rect_with_offset_bounds() {
369        #[rustfmt::skip]
370        let grid = GridBuf::<_, _, RowMajor>::from_buffer(vec![
371            1, 2, 3,
372            4, 5, 6,
373            7, 8, 9,
374        ], 3);
375        let view = grid.view(Rect::from_ltwh(1, 1, 2, 2));
376
377        // Used to underflow-panic translating (0, 0, 2, 2) by `bounds.top_left()` in the wrong
378        // direction.
379        let elements: Vec<_> = view.iter_rect(Rect::from_ltwh(0, 0, 2, 2)).collect();
380        assert_eq!(elements, &[&5, &6, &8, &9]);
381    }
382
383    #[test]
384    fn grid_view_iter_rect_clamps_oversized_query_to_view_bounds() {
385        #[rustfmt::skip]
386        let grid = GridBuf::<_, _, RowMajor>::from_buffer(vec![
387            1, 2, 3,
388            4, 5, 6,
389            7, 8, 9,
390        ], 3);
391        let view = grid.view(Rect::from_ltwh(1, 1, 2, 2));
392
393        // A query larger than the view's own 2x2 size must not leak cells from outside `bounds`.
394        let elements: Vec<_> = view.iter_rect(Rect::from_ltwh(0, 0, 10, 10)).collect();
395        assert_eq!(elements, &[&5, &6, &8, &9]);
396    }
397
398    #[test]
399    fn grid_scaled_size() {
400        let grid = GridBuf::<u8, _, _>::new(10, 10);
401        let scaled = grid.scale(2);
402        let (size, _) = scaled.size_hint();
403        assert_eq!(size.width(), 20);
404        assert_eq!(size.height(), 20);
405    }
406
407    #[test]
408    fn grid_scaled_get() {
409        let grid = GridBuf::<_, _, RowMajor>::from_buffer(vec![1, 2, 3, 4], 2);
410        let scaled = grid.scale(2);
411        assert_eq!(scaled.get(Pos::new(1, 1)), Some(&1));
412        assert_eq!(scaled.get(Pos::new(2, 2)), Some(&4));
413        assert_eq!(scaled.get(Pos::new(3, 3)), Some(&4));
414        assert_eq!(scaled.get(Pos::new(4, 4)), None);
415    }
416
417    #[test]
418    fn grid_scaled_iter_rect() {
419        let grid = GridBuf::<_, _, RowMajor>::from_buffer(vec![1, 2, 3, 4], 2);
420        let scaled = grid.scale(2);
421        let elements: Vec<_> = scaled.iter_rect(Rect::from_ltwh(0, 0, 4, 4)).collect();
422
423        #[rustfmt::skip]
424        assert_eq!(elements, &[
425            &1, &1, &2, &2,
426            &1, &1, &2, &2,
427            &3, &3, &4, &4,
428            &3, &3, &4, &4,
429        ]);
430    }
431
432    #[test]
433    fn grid_blended_size() {
434        let mut grid = GridBuf::<u8, _, _>::new(10, 10);
435        let mut blended = grid.blend(|current, new| current + new);
436        blended.set(Pos::new(1, 1), 5).unwrap();
437        let (size, _) = blended.size_hint();
438        assert_eq!(size.width(), 10);
439        assert_eq!(size.height(), 10);
440    }
441
442    #[test]
443    fn grid_write_blended_set() {
444        let mut grid = GridBuf::new_filled(3, 3, 0);
445        let mut blended = grid.blend(|current, new| current + new);
446        blended.set(Pos::new(1, 1), 5).unwrap();
447        assert_eq!(blended.get(Pos::new(1, 1)), Some(&5));
448        blended.set(Pos::new(1, 1), 3).unwrap();
449        assert_eq!(blended.get(Pos::new(1, 1)), Some(&8));
450    }
451
452    #[test]
453    fn grid_write_blended_iter_rect() {
454        let mut grid = GridBuf::new_filled(3, 3, 0);
455        let mut blended = grid.blend(|current, new| current + new);
456        blended.set(Pos::new(1, 1), 5).unwrap();
457        blended.set(Pos::new(2, 2), 3).unwrap();
458        let elements: Vec<_> = blended.iter_rect(Rect::from_ltwh(0, 0, 3, 3)).collect();
459        assert_eq!(elements, vec![&0, &0, &0, &0, &5, &0, &0, &0, &3]);
460    }
461
462    #[test]
463    fn grid_chained_operations() {
464        let grid = GridBuf::new_filled(3, 3, 1)
465            .copied()
466            .map(|x| x * 2)
467            .view(Rect::from_ltwh(0, 0, 2, 2))
468            .scale(2);
469
470        assert_eq!(grid.get(Pos::new(1, 1)), Some(2));
471    }
472
473    #[test]
474    fn grid_rc() {
475        use alloc::rc::Rc;
476
477        let rc = Rc::new(GridBuf::new_filled(3, 3, 1));
478        let rf = Rc::clone(&rc);
479        let chained = rf
480            .copied()
481            .map(|x| x * 2)
482            .view(Rect::from_ltwh(0, 0, 2, 2))
483            .scale(2);
484        assert_eq!(chained.get(Pos::new(1, 1)), Some(2));
485    }
486
487    #[test]
488    fn grid_arc() {
489        use alloc::sync::Arc;
490
491        let arc = Arc::new(GridBuf::new_filled(3, 3, 1));
492        let af = Arc::clone(&arc);
493        let chained = af
494            .copied()
495            .map(|x| x * 2)
496            .view(Rect::from_ltwh(0, 0, 2, 2))
497            .scale(2);
498        assert_eq!(chained.get(Pos::new(1, 1)), Some(2));
499    }
500}