eth_blockies/blockies/
blockies_base.rs

1use alloc::vec::Vec;
2
3/// Ethereum-style blockies data of type `T`
4///
5/// An alias type of square 2d-array
6///
7/// # Generic Parameters
8///
9/// * `S` (const) - Size of blockies data (number of elements in both width and height)
10///   * Equal to the `size` argument in other blockies implementation.
11/// * `T` - Type of each element
12pub type Blockies<const S: usize, T = ()> = [[T; S]; S];
13
14/// Additional helper functions for generated [`Blockies`] objects
15pub trait BlockiesHelper<const S: usize, T: Clone> {
16    /// Size of [`Blockies`]
17    ///
18    /// Same as `const S` value (const generic)
19    const SIZE: usize = S;
20
21    #[doc(hidden)]
22    #[deprecated(since = "1.1.0", note = "Use `BlockiesHelper::SIZE` instead")]
23    /// (`width`, `height`) constants of [`Blockies`]
24    const DIMENSION: (usize, usize) = (Self::SIZE, Self::SIZE);
25
26    #[doc(hidden)]
27    /// Create a new [`Blockies`] with given initialization function for each element
28    ///
29    /// # Arguments
30    ///
31    /// * `fn_init` - Initialization function, with following constraints:
32    ///   * Arguments
33    ///     * (`x`, `y`) - Coordinates, corresponding to the currently returned element
34    ///   * Return
35    ///     * An element of new [`Blockies`], for each (`x`, `y`)
36    ///
37    /// # Return
38    ///
39    /// * [`Blockies`], which has elements of return values from `fn_init`
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// use eth_blockies::*;
45    ///
46    /// let coords_arr: Blockies<8, (u32, u32)> =
47    ///     Blockies::new(|(x, y)| {
48    ///         (x as u32, y as u32)
49    ///     });
50    ///
51    /// assert_eq!(coords_arr, [
52    ///     [ (0,0), (1,0), (2,0), (3,0), (4,0), (5,0), (6,0), (7,0) ],
53    ///     [ (0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1) ],
54    ///     [ (0,2), (1,2), (2,2), (3,2), (4,2), (5,2), (6,2), (7,2) ],
55    ///     [ (0,3), (1,3), (2,3), (3,3), (4,3), (5,3), (6,3), (7,3) ],
56    ///     [ (0,4), (1,4), (2,4), (3,4), (4,4), (5,4), (6,4), (7,4) ],
57    ///     [ (0,5), (1,5), (2,5), (3,5), (4,5), (5,5), (6,5), (7,5) ],
58    ///     [ (0,6), (1,6), (2,6), (3,6), (4,6), (5,6), (6,6), (7,6) ],
59    ///     [ (0,7), (1,7), (2,7), (3,7), (4,7), (5,7), (6,7), (7,7) ],
60    /// ]);
61    /// ```
62    fn new<F>(fn_init: F) -> Blockies<S, T>
63    where
64        F: FnMut((usize, usize)) -> T;
65
66    #[doc(hidden)]
67    /// Map a new [`Blockies`] from original [`Blockies`],
68    /// with given initialization function for each element
69    ///
70    /// # Arguments
71    ///
72    /// * `fn_init` - Initialization function, with following constraints:
73    ///   * Arguments
74    ///     * `element` - Corresponding element of original [`Blockies`]
75    ///                   for the given coordinates
76    ///     * (`x`, `y`) - Coordinates, corresponding to the currently returned element
77    ///   * Return
78    ///     * A mapped element of new [`Blockies`], for each (`x`, `y`)
79    ///
80    /// # Return
81    ///
82    /// * [`Blockies`], which has elements of return values from `fn_init`
83    ///
84    /// # Example
85    ///
86    /// ```
87    /// use eth_blockies::*;
88    ///
89    /// let coords_arr: Blockies<8, (u32, u32)> = [
90    ///     [ (0,0), (1,0), (2,0), (3,0), (4,0), (5,0), (6,0), (7,0) ],
91    ///     [ (0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1) ],
92    ///     [ (0,2), (1,2), (2,2), (3,2), (4,2), (5,2), (6,2), (7,2) ],
93    ///     [ (0,3), (1,3), (2,3), (3,3), (4,3), (5,3), (6,3), (7,3) ],
94    ///     [ (0,4), (1,4), (2,4), (3,4), (4,4), (5,4), (6,4), (7,4) ],
95    ///     [ (0,5), (1,5), (2,5), (3,5), (4,5), (5,5), (6,5), (7,5) ],
96    ///     [ (0,6), (1,6), (2,6), (3,6), (4,6), (5,6), (6,6), (7,6) ],
97    ///     [ (0,7), (1,7), (2,7), (3,7), (4,7), (5,7), (6,7), (7,7) ],
98    /// ];
99    ///
100    /// let combined_idx_arr: Blockies<8, u32> =
101    ///     coords_arr.map_2d(|orig_elem, (_x, _y)| {
102    ///         orig_elem.1 * 8 + orig_elem.0
103    ///     });
104    ///
105    ///
106    /// assert_eq!(combined_idx_arr, [
107    ///     [   0,   1,   2,   3,   4,   5,   6,   7 ],
108    ///     [   8,   9,  10,  11,  12,  13,  14,  15 ],
109    ///     [  16,  17,  18,  19,  20,  21,  22,  23 ],
110    ///     [  24,  25,  26,  27,  28,  29,  30,  31 ],
111    ///     [  32,  33,  34,  35,  36,  37,  38,  39 ],
112    ///     [  40,  41,  42,  43,  44,  45,  46,  47 ],
113    ///     [  48,  49,  50,  51,  52,  53,  54,  55 ],
114    ///     [  56,  57,  58,  59,  60,  61,  62,  63 ],
115    /// ]);
116    /// ```
117    fn map_2d<U: Clone, F>(&self, fn_init: F) -> Blockies<S, U>
118    where
119        F: FnMut(&T, (usize, usize)) -> U;
120
121    #[doc(hidden)]
122    /// Create a new [`Blockies`] from original [`Blockies`],
123    /// with given initialization function
124    /// (which gets reference of original 2d-array as an argument) for each element
125    ///
126    /// # Arguments
127    ///
128    /// * `fn_init` - Initialization function, with following constraints:
129    ///   * Arguments
130    ///     * `original_blockies` - Reference of original [`Blockies`]
131    ///     * (`x`, `y`) - Coordinates, corresponding to the currently returned element
132    ///   * Return
133    ///     * A mapped element of new [`Blockies`], for each (`x`, `y`)
134    ///
135    /// # Return
136    ///
137    /// * [`Blockies`], which has elements of return values from `fn_init`
138    ///
139    /// # Example
140    ///
141    /// ```
142    /// use eth_blockies::*;
143    ///
144    /// let coords_arr: Blockies<8, (u32, u32)> = [
145    ///     [ (0,0), (1,0), (2,0), (3,0), (4,0), (5,0), (6,0), (7,0) ],
146    ///     [ (0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1) ],
147    ///     [ (0,2), (1,2), (2,2), (3,2), (4,2), (5,2), (6,2), (7,2) ],
148    ///     [ (0,3), (1,3), (2,3), (3,3), (4,3), (5,3), (6,3), (7,3) ],
149    ///     [ (0,4), (1,4), (2,4), (3,4), (4,4), (5,4), (6,4), (7,4) ],
150    ///     [ (0,5), (1,5), (2,5), (3,5), (4,5), (5,5), (6,5), (7,5) ],
151    ///     [ (0,6), (1,6), (2,6), (3,6), (4,6), (5,6), (6,6), (7,6) ],
152    ///     [ (0,7), (1,7), (2,7), (3,7), (4,7), (5,7), (6,7), (7,7) ],
153    /// ];
154    ///
155    /// let combined_idx_arr: Blockies<8, u32> =
156    ///     coords_arr.map_2d_with_ref(|orig_2d_arr, (x, y)| {
157    ///         orig_2d_arr[y][x].1 * 8 + orig_2d_arr[y][x].0
158    ///     });
159    ///
160    ///
161    /// assert_eq!(combined_idx_arr, [
162    ///     [   0,   1,   2,   3,   4,   5,   6,   7 ],
163    ///     [   8,   9,  10,  11,  12,  13,  14,  15 ],
164    ///     [  16,  17,  18,  19,  20,  21,  22,  23 ],
165    ///     [  24,  25,  26,  27,  28,  29,  30,  31 ],
166    ///     [  32,  33,  34,  35,  36,  37,  38,  39 ],
167    ///     [  40,  41,  42,  43,  44,  45,  46,  47 ],
168    ///     [  48,  49,  50,  51,  52,  53,  54,  55 ],
169    ///     [  56,  57,  58,  59,  60,  61,  62,  63 ],
170    /// ]);
171    /// ```
172    fn map_2d_with_ref<U: Clone, F>(&self, fn_init: F) -> Blockies<S, U>
173    where
174        F: FnMut(&Blockies<S, T>, (usize, usize)) -> U;
175
176    /// Flatten [`Blockies`] 2D data to 1D vector
177    ///
178    /// # Return
179    ///
180    /// * 1D vector of `T`
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use eth_blockies::{Blockies, BlockiesHelper};
186    ///
187    /// // original data (2d)
188    /// let blockies_data_2d: Blockies<4, usize>  = [
189    ///         [ 11, 12, 13, 14, ],
190    ///         [ 21, 22, 23, 24, ],
191    ///         [ 31, 32, 33, 34, ],
192    ///         [ 41, 42, 43, 44, ],
193    ///     ];
194    ///
195    /// // flatten data to 1d array
196    /// let blockies_data_1d: Vec<usize> =
197    ///     blockies_data_2d.flatten();
198    ///
199    /// assert_eq!(blockies_data_1d, vec![
200    ///     11, 12, 13, 14, 21, 22, 23, 24,
201    ///     31, 32, 33, 34, 41, 42, 43, 44,
202    /// ]);
203    /// ```
204    fn flatten(self) -> Vec<T>;
205
206    /// Scale [`Blockies`] data to given dimension
207    ///
208    /// * Note that this function does not perform any kind of pixel blending at edges.  
209    ///   Therefore, lower dimension may generate unbalanced image,  
210    ///   **only if both `(width, height)` are not multiples of [`SIZE`](Self::SIZE)**.
211    ///
212    /// # Arguments
213    ///
214    /// * `output_dim` - Width and height of output after scaling
215    ///
216    /// # Return
217    ///
218    /// * 2D vector of `T`
219    ///
220    /// # Example
221    ///
222    /// ```
223    /// use eth_blockies::{Blockies, BlockiesHelper};
224    ///
225    /// // original data (2d)
226    /// let blockies_data_4x4: Blockies<4, usize> = [
227    ///         [ 11, 12, 13, 14, ],
228    ///         [ 21, 22, 23, 24, ],
229    ///         [ 31, 32, 33, 34, ],
230    ///         [ 41, 42, 43, 44, ],
231    ///     ];
232    ///
233    /// // scale: 4x4 -> 8x8
234    /// let blockies_data_8x8: Vec<Vec<usize>> =
235    ///     blockies_data_4x4.scale((8, 8));
236    ///
237    /// assert_eq!(blockies_data_8x8, vec![
238    ///         vec![ 11, 11, 12, 12, 13, 13, 14, 14, ],
239    ///         vec![ 11, 11, 12, 12, 13, 13, 14, 14, ],
240    ///         vec![ 21, 21, 22, 22, 23, 23, 24, 24, ],
241    ///         vec![ 21, 21, 22, 22, 23, 23, 24, 24, ],
242    ///         vec![ 31, 31, 32, 32, 33, 33, 34, 34, ],
243    ///         vec![ 31, 31, 32, 32, 33, 33, 34, 34, ],
244    ///         vec![ 41, 41, 42, 42, 43, 43, 44, 44, ],
245    ///         vec![ 41, 41, 42, 42, 43, 43, 44, 44, ],
246    /// ]);
247    /// ```
248    fn scale(self, output_dim: (usize, usize)) -> Vec<Vec<T>>;
249}
250impl<T: Clone, const S: usize> BlockiesHelper<S, T> for Blockies<S, T> {
251    fn new<F>(mut fn_init: F) -> Blockies<S, T>
252    where
253        F: FnMut((usize, usize)) -> T,
254    {
255        [[(); S]; S].map_2d_with_ref(|_, coord| fn_init(coord))
256    }
257
258    fn map_2d<U: Clone, F>(&self, mut fn_init: F) -> Blockies<S, U>
259    where
260        F: FnMut(&T, (usize, usize)) -> U,
261    {
262        Blockies::<S, U>::new(|(x, y)| fn_init(&self[y][x], (x, y)))
263    }
264
265    fn map_2d_with_ref<U: Clone, F>(&self, mut fn_init: F) -> Blockies<S, U>
266    where
267        F: FnMut(&Blockies<S, T>, (usize, usize)) -> U,
268    {
269        /*
270        use core::mem::{transmute_copy, MaybeUninit};
271
272        // This usage of uninit().assume_init() is safe
273        //   because MaybeUninit does not have any initialization:
274        // https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element
275        let mut array_uninit: [[MaybeUninit<U>; S]; S] =
276            unsafe { MaybeUninit::uninit().assume_init() };
277
278        array_uninit
279            .iter_mut()
280            .enumerate()
281            .for_each(|(y, row_uninit)| {
282                row_uninit
283                    .iter_mut()
284                    .enumerate()
285                    .for_each(|(x, elem_uninit)| {
286                        elem_uninit.write(fn_init(self, (x, y)));
287                    });
288            });
289
290        unsafe { transmute_copy(&array_uninit) }
291         */
292
293        core::array::from_fn(|y| core::array::from_fn(|x| fn_init(self, (x, y))))
294    }
295
296    fn flatten(self) -> Vec<T> {
297        // waiting for nightly-only feature 'generic_const_exprs' to be stable...
298        // initialize ret_arr using MaybeUninit
299        /*
300        {
301            let mut ret_arr_uninit: MaybeUninit<[T; 64]> = MaybeUninit::uninit();
302            let ret_arr_ptr_casted: *mut T = ret_arr_uninit.as_mut_ptr().cast();
303
304            self.iter().enumerate().for_each(|(idx_row, row)| {
305                row.iter().enumerate().for_each(|(idx, elem)| unsafe {
306                    ret_arr_ptr_casted
307                        .add(idx_row * S + idx)
308                        .write_unaligned(elem.clone());
309                });
310            });
311
312            unsafe { ret_arr_uninit.assume_init() }.into()
313        }
314         */
315
316        self.into_iter().flatten().collect()
317    }
318
319    fn scale(self, output_dim: (usize, usize)) -> Vec<Vec<T>> {
320        // the base number of duplicates for each pixel
321        let scale = (output_dim.0 / S, output_dim.1 / S);
322
323        // if additional duplicates needed for each pixel
324        let extra_elem_needed = {
325            // calculate expected vs actual pixels for each step (== original pixel),
326            //   then returns array that shows if each step needs extra elem
327            fn calc_extra_elem_needed<const S: usize>(len: usize, scale: usize) -> [bool; S] {
328                match len.overflowing_rem(S).0 == 0 {
329                    true => [false; S],
330                    false => {
331                        let pixels_per_class = len as f64 / S as f64;
332                        // calculate for each class element
333                        (0..S)
334                            .fold(
335                                ([false; S], 0_isize),
336                                |(mut is_extra_needed, pixels_diff_prev), idx| {
337                                    // if [pixels_diff] => (
338                                    //   rounded difference of
339                                    //   'expected ending pixel for current elem'
340                                    //   and
341                                    //   'ending pixel when duplicate
342                                    //    each elem by factor of [scale]'
343                                    // )
344                                    // changes, current class elem needs extra elem
345
346                                    let pixels_diff = ((pixels_per_class - scale as f64)
347                                        * (idx + 1) as f64
348                                        + 0.5_f64)
349                                        as isize;
350
351                                    is_extra_needed[idx] = pixels_diff != pixels_diff_prev;
352
353                                    (is_extra_needed, pixels_diff)
354                                },
355                            )
356                            .0
357                    }
358                }
359            }
360
361            (
362                calc_extra_elem_needed::<S>(output_dim.0, scale.0),
363                calc_extra_elem_needed::<S>(output_dim.1, scale.1),
364            )
365        };
366
367        // template for vectors below
368        let vec_template = (
369            Vec::<T>::with_capacity(output_dim.0),      // for row
370            Vec::<Vec<T>>::with_capacity(output_dim.1), // for 2d vec
371        );
372
373        // build scaled 2d vec
374        self.iter()
375            .enumerate()
376            .fold(vec_template.1, |mut ret_vec, (idx_row, row)| {
377                // build a scaled row for the current source row
378                let new_row: Vec<T> = row.iter().enumerate().fold(
379                    vec_template.0.clone(),
380                    |mut ret_row, (idx, elem)| {
381                        // duplicate n elems at the end of the row
382                        // (n: scale.0 + extra_elem_needed.0[idx])
383                        ret_row.resize(
384                            ret_row.len() + scale.0 + extra_elem_needed.0[idx] as usize,
385                            elem.clone(),
386                        );
387                        ret_row
388                    },
389                );
390
391                // duplicate new rows by n, and append at the end
392                // (n: scale.1 + extra_elem_needed.1[idx])
393                ret_vec.resize(
394                    ret_vec.len() + scale.1 + extra_elem_needed.1[idx_row] as usize,
395                    new_row.clone(),
396                );
397
398                ret_vec
399            })
400    }
401}