identicon_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![forbid(missing_docs)]
4#![forbid(clippy::unwrap_used)]
5#![forbid(clippy::expect_used)]
6
7use std::str::FromStr;
8use std::sync::Arc;
9
10use crate::error::IdenticonError;
11use image::codecs::jpeg::JpegEncoder;
12use image::codecs::png::PngEncoder;
13use image::imageops::FilterType;
14use image::{DynamicImage, GenericImage, ImageBuffer, ImageEncoder};
15use theme::Theme;
16
17/// Identicon errors
18pub mod error;
19
20/// Theme Trait and Structs
21pub mod theme;
22
23/// Color Structs and Implementations
24pub mod color;
25
26mod grid;
27mod hash;
28mod map_values;
29
30/// Generic Identicon struct.
31///
32/// This is the base struct to be used.
33#[derive(Clone)]
34pub struct Identicon {
35    hash: Vec<u8>,
36    border: u32,
37    size: u32,
38    scale: u32,
39    mirrored: bool,
40    theme: Arc<dyn Theme + Send + Sync>,
41}
42
43/// Generates a new identicon.
44///
45/// This is a wrapper around [`identicon_rs::Identicon::new`].
46///
47/// [`identicon_rs::Identicon::new`]: struct.Identicon.html#method.new
48pub fn new(input_value: &str) -> Identicon {
49    Identicon::new(input_value)
50}
51
52impl Identicon {
53    /// Generates a new identicon from an input value.
54    ///
55    /// The defaults are:
56    /// - border: 50
57    /// - size: 5
58    /// - scale: 500
59    /// - background_color: (240, 240, 240)
60    /// - mirrored: true
61    pub fn new(input_value: &str) -> Identicon {
62        let mut identicon = Identicon::default();
63        identicon.set_input(input_value);
64        identicon
65    }
66
67    /// Sets the identicon input value, regenerating the hash.
68    pub fn set_input(&mut self, input_value: &str) -> &mut Self {
69        self.hash = hash::hash_value(input_value);
70        self
71    }
72
73    /// Gets the identicon border size.
74    pub fn border(&self) -> u32 {
75        self.border
76    }
77
78    /// Sets the identicon border size.
79    ///
80    /// Default is 5
81    pub fn set_border(&mut self, border: u32) -> &mut Self {
82        self.border = border;
83        self
84    }
85
86    /// Gets the identicon size.
87    ///
88    /// The size represents the number of viewable blocks of the identicon.
89    pub fn size(&self) -> u32 {
90        self.size
91    }
92
93    /// Sets the number of viewable blocks of the identicon.
94    ///
95    /// This must be <= the scale.
96    ///
97    /// Default is 5, representing an identicon with a grid of 5x5.
98    pub fn set_size(&mut self, size: u32) -> Result<&mut Self, IdenticonError> {
99        if size <= self.scale {
100            self.size = size;
101            Ok(self)
102        } else {
103            Err(IdenticonError::SizeTooLargeError {
104                size,
105                scale: self.scale,
106            })
107        }
108    }
109
110    /// Gets the identicon scale.
111    ///
112    /// The scale represents the height and width of the identicon portion of any generated image.
113    ///
114    /// The full image size is: `scale + ( 2 * border )`
115    pub fn scale(&self) -> u32 {
116        self.scale
117    }
118
119    /// Sets the scale of the image.
120    ///
121    /// The full image size is: `scale + ( 2 * border )`
122    ///
123    /// This must be >= the size.
124    pub fn set_scale(&mut self, scale: u32) -> Result<&mut Self, IdenticonError> {
125        if scale >= self.size {
126            self.scale = scale;
127            Ok(self)
128        } else {
129            Err(IdenticonError::ScaleTooSmallError {
130                scale,
131                size: self.size,
132            })
133        }
134    }
135
136    /// Gets if the identicon is mirrored.
137    pub fn mirrored(&self) -> bool {
138        self.mirrored
139    }
140
141    /// Sets whether the identicon is mirrored along the y axis.
142    ///
143    /// This is a boolean.
144    pub fn set_mirrored(&mut self, mirrored: bool) -> &mut Self {
145        self.mirrored = mirrored;
146        self
147    }
148
149    /// Gets the current theme.
150    pub fn theme(&self) -> Arc<dyn Theme> {
151        self.theme.clone()
152    }
153
154    /// Sets the current identicon theme.
155    pub fn set_theme(&mut self, theme: Arc<dyn Theme + Send + Sync>) -> &mut Self {
156        self.theme = theme;
157        self
158    }
159
160    /// Generates the DynamicImage representing the Identicon.
161    pub fn generate_image(&self) -> Result<DynamicImage, IdenticonError> {
162        // Create a new grid
163        let grid = grid::generate_full_grid(self.size, &self.hash);
164
165        // Create pixel objects
166        let color_active = self.theme.main_color(&self.hash)?;
167        let color_background = self.theme.background_color(&self.hash)?;
168        let pixel_active = image::Rgb([color_active.red, color_active.green, color_active.blue]);
169        let pixel_background = image::Rgb([
170            color_background.red,
171            color_background.green,
172            color_background.blue,
173        ]);
174
175        // Create image buffer from grid
176        let image_buffer = ImageBuffer::from_fn(self.size, self.size, |x, y| {
177            let x_location = if self.mirrored && x > self.size / 2 {
178                self.size - x - 1
179            } else {
180                x
181            };
182
183            // Get location within the generated grid
184            let grid_location = (x_location + y * self.size) % self.size.pow(2);
185
186            // Set the pixel color based on the value within the grid at the given position
187            if grid[grid_location as usize] {
188                pixel_active
189            } else {
190                pixel_background
191            }
192        });
193
194        let scaled_image_buffer = DynamicImage::ImageRgb8(image_buffer)
195            .resize(self.scale, self.scale, FilterType::Nearest)
196            .to_rgb8();
197
198        let final_size = self.scale + (2 * self.border);
199        let mut bordered_image_buffer =
200            ImageBuffer::from_fn(final_size, final_size, |_, _| pixel_background);
201
202        match bordered_image_buffer.copy_from(&scaled_image_buffer, self.border, self.border) {
203            Ok(_) => Ok(DynamicImage::ImageRgb8(bordered_image_buffer)),
204            Err(_) => Err(error::IdenticonError::GenerateImageError),
205        }
206    }
207
208    /// Saves the generated image to the given filename.
209    ///
210    /// The file formats `.png`, `.jpg`, `.jpeg`, `.bmp`, and `.ico` work.
211    pub fn save_image(&self, output_filename: &str) -> Result<(), error::IdenticonError> {
212        let image = self.generate_image()?;
213        image
214            .save(output_filename)
215            .map_err(|_| error::IdenticonError::SaveImageError)
216    }
217
218    /// Export a PNG file buffer as a `Vec<u8>`.
219    ///
220    /// This is for creating a file for a buffer or network response without creating a file on the
221    /// filesystem.
222    pub fn export_png_data(&self) -> Result<Vec<u8>, error::IdenticonError> {
223        let image = self.generate_image()?;
224        let image_size = image.to_rgb8().width();
225        let mut buffer = Vec::new();
226
227        PngEncoder::new(&mut buffer)
228            .write_image(
229                image.to_rgb8().into_raw().as_slice(),
230                image_size,
231                image_size,
232                image::ExtendedColorType::Rgb8,
233            )
234            .map_err(|_| error::IdenticonError::EncodeImageError)?;
235        Ok(buffer)
236    }
237
238    /// Export a JPEG file buffer as a `Vec<u8>`.
239    ///
240    /// This is for creating a file for a buffer or network response without creating a file on the
241    /// filesystem.
242    pub fn export_jpeg_data(&self) -> Result<Vec<u8>, error::IdenticonError> {
243        let image = self.generate_image()?;
244        let image_size = image.to_rgb8().width();
245        let mut buffer = Vec::new();
246
247        JpegEncoder::new(&mut buffer)
248            .write_image(
249                image.to_rgb8().into_raw().as_slice(),
250                image_size,
251                image_size,
252                image::ExtendedColorType::Rgb8,
253            )
254            .map_err(|_| error::IdenticonError::EncodeImageError)?;
255        Ok(buffer)
256    }
257}
258
259impl Default for Identicon {
260    fn default() -> Self {
261        let theme = theme::default_theme();
262        Self {
263            hash: hash::hash_value(""),
264            border: 50,
265            size: 5,
266            scale: 500,
267            mirrored: true,
268            theme,
269        }
270    }
271}
272
273impl FromStr for Identicon {
274    type Err = IdenticonError;
275
276    fn from_str(s: &str) -> Result<Self, Self::Err> {
277        Ok(Identicon::new(s))
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use std::str::FromStr;
284
285    use crate::{Identicon, color::RGB};
286
287    #[test]
288    fn consistency() {
289        let expected_color = RGB {
290            red: 183,
291            green: 212,
292            blue: 111,
293        };
294        let expected_grid = vec![
295            true, true, true, true, false, true, true, true, false, true, true, true, false, true,
296            true, false, true, true, true, true, true, true, false, true, true,
297        ];
298
299        let image = Identicon::new("test");
300        let grid = crate::grid::generate_full_grid(image.size, &image.hash);
301        let color = crate::theme::default_theme()
302            .main_color(&image.hash)
303            .expect("could not get color");
304
305        assert_eq!(expected_color, color);
306
307        assert_eq!(expected_grid, grid);
308    }
309
310    #[test]
311    fn test_send() {
312        fn assert_send<T: Send>() {}
313
314        assert_send::<Identicon>();
315    }
316
317    #[test]
318    fn test_sync() {
319        fn assert_send<T: Sync>() {}
320
321        assert_send::<Identicon>();
322    }
323
324    #[test]
325    fn trim_of_input_works() {
326        let image_normal = Identicon::new("test").generate_image().unwrap();
327        let image_padded = Identicon::new("  test  ").generate_image().unwrap();
328        assert_eq!(
329            image_normal.to_rgb8().into_raw(),
330            image_padded.to_rgb8().into_raw()
331        );
332    }
333
334    #[test]
335    fn trim_of_input_failure_works() {
336        let image_normal = Identicon::new("test").generate_image().unwrap();
337        let image_padded = Identicon::new("  test1  ").generate_image().unwrap();
338        assert_ne!(
339            image_normal.to_rgb8().into_raw(),
340            image_padded.to_rgb8().into_raw()
341        );
342    }
343
344    #[test]
345    fn chained_setters_work() {
346        let identicon_chained = Identicon::new("test")
347            .set_border(10)
348            .set_mirrored(false)
349            .clone();
350
351        let mut identicon_mutated = Identicon::new("test");
352        identicon_mutated.set_border(10);
353        identicon_mutated.set_mirrored(false);
354
355        assert_eq!(identicon_chained.border(), identicon_mutated.border());
356        assert_eq!(identicon_chained.mirrored(), identicon_mutated.mirrored());
357    }
358
359    #[test]
360    fn getters_work() {
361        let identicon = Identicon::new("test").set_border(10).clone();
362
363        assert_eq!(identicon.border(), identicon.border);
364    }
365
366    #[test]
367    fn from_str_works() {
368        let identicon = Identicon::new("test");
369        let identicon_from_str = Identicon::from_str("test").unwrap();
370        assert_eq!(identicon.hash, identicon_from_str.hash);
371    }
372
373    #[test]
374    fn from_str_failure_works() {
375        let identicon = Identicon::new("test");
376        let identicon_from_str = Identicon::from_str("test1").unwrap();
377        assert_ne!(identicon.hash, identicon_from_str.hash);
378    }
379}