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
17pub mod error;
19
20pub mod theme;
22
23pub mod color;
25
26mod grid;
27mod hash;
28mod map_values;
29
30#[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
43pub fn new(input_value: &str) -> Identicon {
49 Identicon::new(input_value)
50}
51
52impl Identicon {
53 pub fn new(input_value: &str) -> Identicon {
62 let mut identicon = Identicon::default();
63 identicon.set_input(input_value);
64 identicon
65 }
66
67 pub fn set_input(&mut self, input_value: &str) -> &mut Self {
69 self.hash = hash::hash_value(input_value);
70 self
71 }
72
73 pub fn border(&self) -> u32 {
75 self.border
76 }
77
78 pub fn set_border(&mut self, border: u32) -> &mut Self {
82 self.border = border;
83 self
84 }
85
86 pub fn size(&self) -> u32 {
90 self.size
91 }
92
93 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 pub fn scale(&self) -> u32 {
116 self.scale
117 }
118
119 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 pub fn mirrored(&self) -> bool {
138 self.mirrored
139 }
140
141 pub fn set_mirrored(&mut self, mirrored: bool) -> &mut Self {
145 self.mirrored = mirrored;
146 self
147 }
148
149 pub fn theme(&self) -> Arc<dyn Theme> {
151 self.theme.clone()
152 }
153
154 pub fn set_theme(&mut self, theme: Arc<dyn Theme + Send + Sync>) -> &mut Self {
156 self.theme = theme;
157 self
158 }
159
160 pub fn generate_image(&self) -> Result<DynamicImage, IdenticonError> {
162 let grid = grid::generate_full_grid(self.size, &self.hash);
164
165 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 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 let grid_location = (x_location + y * self.size) % self.size.pow(2);
185
186 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 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 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 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}