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}