#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#![forbid(clippy::unwrap_used)]
#![forbid(clippy::expect_used)]
use std::str::FromStr;
use std::sync::Arc;
use crate::error::IdenticonError;
use image::codecs::jpeg::JpegEncoder;
use image::codecs::png::PngEncoder;
use image::imageops::FilterType;
use image::{DynamicImage, GenericImage, ImageBuffer, ImageEncoder};
use theme::Theme;
pub mod error;
pub mod theme;
pub mod color;
mod grid;
mod hash;
mod map_values;
#[derive(Clone)]
pub struct Identicon {
hash: Vec<u8>,
border: u32,
size: u32,
scale: u32,
mirrored: bool,
theme: Arc<dyn Theme + Send + Sync>,
}
pub fn new(input_value: &str) -> Identicon {
Identicon::new(input_value)
}
impl Identicon {
pub fn new(input_value: &str) -> Identicon {
let mut identicon = Identicon::default();
identicon.set_input(input_value);
identicon
}
pub fn set_input(&mut self, input_value: &str) -> &mut Self {
self.hash = hash::hash_value(input_value);
self
}
pub fn border(&self) -> u32 {
self.border
}
pub fn set_border(&mut self, border: u32) -> &mut Self {
self.border = border;
self
}
pub fn size(&self) -> u32 {
self.size
}
pub fn set_size(&mut self, size: u32) -> Result<&mut Self, IdenticonError> {
if size <= self.scale {
self.size = size;
Ok(self)
} else {
Err(IdenticonError::SizeTooLargeError {
size,
scale: self.scale,
})
}
}
pub fn scale(&self) -> u32 {
self.scale
}
pub fn set_scale(&mut self, scale: u32) -> Result<&mut Self, IdenticonError> {
if scale >= self.size {
self.scale = scale;
Ok(self)
} else {
Err(IdenticonError::ScaleTooSmallError {
scale,
size: self.size,
})
}
}
pub fn mirrored(&self) -> bool {
self.mirrored
}
pub fn set_mirrored(&mut self, mirrored: bool) -> &mut Self {
self.mirrored = mirrored;
self
}
pub fn theme(&self) -> Arc<dyn Theme> {
self.theme.clone()
}
pub fn set_theme(&mut self, theme: Arc<dyn Theme + Send + Sync>) -> &mut Self {
self.theme = theme;
self
}
pub fn generate_image(&self) -> Result<DynamicImage, IdenticonError> {
let grid = grid::generate_full_grid(self.size, &self.hash);
let color_active = self.theme.main_color(&self.hash)?;
let color_background = self.theme.background_color(&self.hash)?;
let pixel_active = image::Rgb([color_active.red, color_active.green, color_active.blue]);
let pixel_background = image::Rgb([
color_background.red,
color_background.green,
color_background.blue,
]);
let image_buffer = ImageBuffer::from_fn(self.size, self.size, |x, y| {
let x_location = if self.mirrored && x > self.size / 2 {
self.size - x - 1
} else {
x
};
let grid_location = (x_location + y * self.size) % self.size.pow(2);
if grid[grid_location as usize] {
pixel_active
} else {
pixel_background
}
});
let scaled_image_buffer = DynamicImage::ImageRgb8(image_buffer)
.resize(self.scale, self.scale, FilterType::Nearest)
.to_rgb8();
let final_size = self.scale + (2 * self.border);
let mut bordered_image_buffer =
ImageBuffer::from_fn(final_size, final_size, |_, _| pixel_background);
match bordered_image_buffer.copy_from(&scaled_image_buffer, self.border, self.border) {
Ok(_) => Ok(DynamicImage::ImageRgb8(bordered_image_buffer)),
Err(_) => Err(error::IdenticonError::GenerateImageError),
}
}
pub fn save_image(&self, output_filename: &str) -> Result<(), error::IdenticonError> {
let image = self.generate_image()?;
image
.save(output_filename)
.map_err(|_| error::IdenticonError::SaveImageError)
}
pub fn export_png_data(&self) -> Result<Vec<u8>, error::IdenticonError> {
let image = self.generate_image()?;
let image_size = image.to_rgb8().width();
let mut buffer = Vec::new();
PngEncoder::new(&mut buffer)
.write_image(
image.to_rgb8().into_raw().as_slice(),
image_size,
image_size,
image::ExtendedColorType::Rgb8,
)
.map_err(|_| error::IdenticonError::EncodeImageError)?;
Ok(buffer)
}
pub fn export_jpeg_data(&self) -> Result<Vec<u8>, error::IdenticonError> {
let image = self.generate_image()?;
let image_size = image.to_rgb8().width();
let mut buffer = Vec::new();
JpegEncoder::new(&mut buffer)
.write_image(
image.to_rgb8().into_raw().as_slice(),
image_size,
image_size,
image::ExtendedColorType::Rgb8,
)
.map_err(|_| error::IdenticonError::EncodeImageError)?;
Ok(buffer)
}
}
impl Default for Identicon {
fn default() -> Self {
let theme = theme::default_theme();
Self {
hash: hash::hash_value(""),
border: 50,
size: 5,
scale: 500,
mirrored: true,
theme,
}
}
}
impl FromStr for Identicon {
type Err = IdenticonError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Identicon::new(s))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::{Identicon, color::RGB};
#[test]
fn consistency() {
let expected_color = RGB {
red: 183,
green: 212,
blue: 111,
};
let expected_grid = vec![
true, true, true, true, false, true, true, true, false, true, true, true, false, true,
true, false, true, true, true, true, true, true, false, true, true,
];
let image = Identicon::new("test");
let grid = crate::grid::generate_full_grid(image.size, &image.hash);
let color = crate::theme::default_theme()
.main_color(&image.hash)
.expect("could not get color");
assert_eq!(expected_color, color);
assert_eq!(expected_grid, grid);
}
#[test]
fn test_send() {
fn assert_send<T: Send>() {}
assert_send::<Identicon>();
}
#[test]
fn test_sync() {
fn assert_send<T: Sync>() {}
assert_send::<Identicon>();
}
#[test]
fn trim_of_input_works() {
let image_normal = Identicon::new("test").generate_image().unwrap();
let image_padded = Identicon::new(" test ").generate_image().unwrap();
assert_eq!(
image_normal.to_rgb8().into_raw(),
image_padded.to_rgb8().into_raw()
);
}
#[test]
fn trim_of_input_failure_works() {
let image_normal = Identicon::new("test").generate_image().unwrap();
let image_padded = Identicon::new(" test1 ").generate_image().unwrap();
assert_ne!(
image_normal.to_rgb8().into_raw(),
image_padded.to_rgb8().into_raw()
);
}
#[test]
fn chained_setters_work() {
let identicon_chained = Identicon::new("test")
.set_border(10)
.set_mirrored(false)
.clone();
let mut identicon_mutated = Identicon::new("test");
identicon_mutated.set_border(10);
identicon_mutated.set_mirrored(false);
assert_eq!(identicon_chained.border(), identicon_mutated.border());
assert_eq!(identicon_chained.mirrored(), identicon_mutated.mirrored());
}
#[test]
fn getters_work() {
let identicon = Identicon::new("test").set_border(10).clone();
assert_eq!(identicon.border(), identicon.border);
}
#[test]
fn from_str_works() {
let identicon = Identicon::new("test");
let identicon_from_str = Identicon::from_str("test").unwrap();
assert_eq!(identicon.hash, identicon_from_str.hash);
}
#[test]
fn from_str_failure_works() {
let identicon = Identicon::new("test");
let identicon_from_str = Identicon::from_str("test1").unwrap();
assert_ne!(identicon.hash, identicon_from_str.hash);
}
}