#![feature(rust_2018_preview, const_fn)]
use digest::{ExtendableOutput, Input, XofReader};
use failure::Fail;
use image::{Rgb, RgbImage};
use log::{debug, log};
use sha3::Shake128;
mod hsl;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "The chunk size must be 1 or above.")]
InvalidChunkSizeError,
#[fail(display = "The dimensions must be 1 or above.")]
InvalidDimensionsError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChunkConfig {
Square(u32),
Rectangle(u32, u32),
}
impl ChunkConfig {
fn validate(self) -> Result<(u32, u32), Error> {
match self {
ChunkConfig::Square(s) => if s > 0 {
Ok((s, s))
} else {
Err(Error::InvalidChunkSizeError)
},
ChunkConfig::Rectangle(w, h) => if w > 0 && h > 0 {
Ok((w, h))
} else {
Err(Error::InvalidChunkSizeError)
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Dimensions(pub u32, pub u32);
impl Dimensions {
fn validate(self) -> Result<(u32, u32), Error> {
if self.0 > 0 && self.1 > 0 {
Ok((self.0, self.1))
} else {
Err(Error::InvalidDimensionsError)
}
}
}
fn is_colored_chunk<M, R>(bitmap_data_matrix: M, width: u32, x: u32, y: u32) -> bool
where
M: AsRef<[R]>,
R: AsRef<[bool]>,
{
fn f(x: u32, w: u32) -> u32 {
if x < w / 2 {
w - (x + 1)
} else {
x
}
};
fn g(x: u32, w: u32, c: u32) -> u32 {
if w % 2 == 0 {
x - c
} else {
x - (c - 1)
}
}
let chunk_size = (width as f32 / 2.0).ceil() as u32;
let x = g(f(x, width), width, chunk_size) as usize;
let y = y as usize;
let chunk = bitmap_data_matrix.as_ref()[y].as_ref();
chunk[x]
}
fn convert_bitmap_data_matrix<M, R>(origin: M) -> Vec<Vec<bool>>
where
M: AsRef<[R]>,
R: AsRef<[bool]>,
{
let origin = origin.as_ref();
let inner_size = origin[0].as_ref().len();
let mut output = Vec::with_capacity(inner_size);
let outer_size = origin.len();
for j in 0..inner_size {
let mut inner = Vec::with_capacity(inner_size);
for i in 0..outer_size {
let value = origin[i].as_ref()[j];
inner.push(value)
}
output.push(inner);
}
output
}
fn chunk_coordinates_from_pixel_point(
chunk_size_width_in_pixel: u32,
chunk_size_height_in_pixel: u32,
pixel_x: u32,
pixel_y: u32,
) -> (u32, u32) {
let x = pixel_x / chunk_size_width_in_pixel;
let y = pixel_y / chunk_size_height_in_pixel;
(x, y)
}
fn bytes_array_to_hex_string(bytes: &[u8]) -> String {
bytes
.iter()
.fold(String::new(), |mut accm, v| {
accm.push_str(&format!("{:02x}", v));
accm
})
}
pub fn generate_identicon(
data_to_digest: &[u8],
chunk_config: ChunkConfig,
dimensions: Dimensions,
) -> Result<RgbImage, failure::Error> {
let (chunk_size_width_in_pixel, chunk_size_height_in_pixel) = chunk_config.validate()?;
let (width, height) = dimensions.validate()?;
let matrix_length = height * (width as f32 / 2.0).ceil() as u32;
let capacity = (((matrix_length as f32 + 3.0 + 2.0 + 2.0) / 2.0).ceil()) as usize;
let mut buffer = vec![0u8; capacity];
let mut hasher = Shake128::default();
hasher.process(data_to_digest);
hasher.xof_result().read(&mut buffer);
let digest_string = bytes_array_to_hex_string(&buffer);
debug!("digest: {} - {} chars", digest_string, digest_string.len());
let mut digest = digest_string.chars();
let bitmap_data_matrix = digest
.by_ref()
.take(matrix_length as usize)
.filter_map(|c| c.to_digit(16).map(|v| v % 2 == 0))
.collect::<Vec<_>>();
let bitmap_data_matrix = bitmap_data_matrix
.chunks(height as usize)
.collect::<Vec<_>>();
let bitmap_data_matrix = convert_bitmap_data_matrix(&bitmap_data_matrix);
let hue = digest.by_ref().take(3).collect::<String>();
debug!("hue: {}", hue);
let hue = u32::from_str_radix(&hue, 16).map(|v| v as f32 * (360.0 / 4095.0))?;
let saturation = digest.by_ref().take(2).collect::<String>();
debug!("saturation: {}", saturation);
let saturation =
u32::from_str_radix(&saturation, 16).map(|v| 65.0 - v as f32 * 20.0 / 255.0)?;
let lightness = digest.take(2).collect::<String>();
debug!("lightness: {}", lightness);
let lightness = u32::from_str_radix(&lightness, 16).map(|v| 75.0 - v as f32 * 20.0 / 255.0)?;
let color = hsl::convert_hsl_to_rgb(hue, saturation, lightness);
let image_width_in_pixel = chunk_size_width_in_pixel * width;
let image_height_in_pixel = chunk_size_height_in_pixel * height;
let image = RgbImage::from_fn(image_width_in_pixel, image_height_in_pixel, |x, y| {
let (chunk_x, chunk_y) = chunk_coordinates_from_pixel_point(
chunk_size_width_in_pixel,
chunk_size_height_in_pixel,
x,
y,
);
if is_colored_chunk(&bitmap_data_matrix, width, chunk_x, chunk_y) {
color
} else {
Rgb {
data: [0xff, 0xff, 0xff],
}
}
});
Ok(image)
}
#[test]
fn test_is_colored_chunk_5x2() {
let linear_array = [true, false, false, true, false, false];
let linear_array = linear_array.chunks(2).collect::<Vec<_>>();
let linear_array = convert_bitmap_data_matrix(&linear_array);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 1), false);
}
#[test]
fn test_is_colored_chunk_5x5() {
let linear_array = [
true, false, false, true, false, false, true, true, true, false, true, false, true, false,
false,
];
let linear_array = linear_array.chunks(5).collect::<Vec<_>>();
let linear_array = convert_bitmap_data_matrix(&linear_array);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 2), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 3), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 3), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 3), true);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 0, 4), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 1, 4), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 2, 4), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 3, 4), false);
assert_eq!(is_colored_chunk(&linear_array, 5, 4, 4), false);
}
#[test]
fn test_is_colored_chunk_6x6() {
let linear_array = [
true, false, false, true, true, false, false, true, true, false, true, false, true, true,
false, false, true, false,
];
let linear_array = linear_array.chunks(6).collect::<Vec<_>>();
let linear_array = convert_bitmap_data_matrix(&linear_array);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 0), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 0), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 1), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 1), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 2), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 2), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 2), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 2), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 2), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 3), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 3), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 3), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 4), true);
assert_eq!(is_colored_chunk(&linear_array, 6, 0, 5), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 1, 5), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 2, 5), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 3, 5), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 4, 5), false);
assert_eq!(is_colored_chunk(&linear_array, 6, 5, 5), false);
}
#[test]
fn test_convert_bitmap_data_matrix() {
let chunks = [
[true, true, false, true, false],
[false, true, false, false, true],
[true, false, true, true, false],
];
let result = convert_bitmap_data_matrix(&chunks);
assert_eq!(result[0], &[true, false, true]);
assert_eq!(result[1], &[true, true, false]);
assert_eq!(result[2], &[false, false, true]);
assert_eq!(result[3], &[true, false, true]);
assert_eq!(result[4], &[false, true, false]);
}
#[test]
fn test_chunk_coordinates_from_pixel_point() {
assert_eq!(chunk_coordinates_from_pixel_point(10, 10, 0, 0), (0, 0));
assert_eq!(chunk_coordinates_from_pixel_point(10, 10, 9, 9), (0, 0));
assert_eq!(chunk_coordinates_from_pixel_point(10, 10, 10, 10), (1, 1));
assert_eq!(chunk_coordinates_from_pixel_point(10, 10, 11, 11), (1, 1));
assert_eq!(
chunk_coordinates_from_pixel_point(10, 10, 123, 123),
(12, 12)
);
assert_eq!(chunk_coordinates_from_pixel_point(70, 70, 0, 0), (0, 0));
assert_eq!(chunk_coordinates_from_pixel_point(70, 70, 70, 70), (1, 1));
assert_eq!(chunk_coordinates_from_pixel_point(70, 70, 95, 10), (1, 0));
assert_eq!(chunk_coordinates_from_pixel_point(70, 70, 300, 200), (4, 2));
assert_eq!(chunk_coordinates_from_pixel_point(70, 70, 349, 349), (4, 4));
}
#[test]
fn test_generate_identicon() {
let chunk_config = ChunkConfig::Square(1);
let dimensions = Dimensions(5, 5);
let image = generate_identicon(&[], chunk_config, dimensions);
assert!(image.is_ok());
let image = image.unwrap();
const PURPLE: [u8; 3] = [222, 133, 220];
const WHITE: [u8; 3] = [255, 255, 255];
const TEST_IMAGE: [[u8; 3]; 25] = [
PURPLE, WHITE, WHITE, WHITE, PURPLE, WHITE, PURPLE, WHITE, PURPLE, WHITE, PURPLE, PURPLE,
WHITE, PURPLE, PURPLE, PURPLE, PURPLE, PURPLE, PURPLE, PURPLE, WHITE, PURPLE, PURPLE,
PURPLE, WHITE,
];
let test_image = TEST_IMAGE
.iter()
.flatten()
.map(|v| *v)
.collect::<Vec<_>>();
assert_eq!(image.into_raw(), test_image);
}