extern crate image;
extern crate uuid;
extern crate crypto;
#[macro_use] extern crate failure;
use std::path::Path;
use image::imageops;
use image::GenericImageView;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use std::fs;
use std::u8;
use std::convert::From;
#[derive(Clone, Debug, Fail)]
pub enum Error {
#[fail(display = "could not open file: {}", filename)]
ReadingFromFileFailed {
filename: String,
},
#[fail(display = "could not write to file: {}", filename)]
WritingToFileFailed {
filename: String,
},
#[fail(display = "error decoding filename")]
DecodeError,
#[fail(display = "features not found in image directory")]
FeaturesNotFound,
#[fail(display = "dimensions must match")]
DimensionsDontMatch,
}
fn get_colors() -> Vec<Color> {
vec![
"#81bef1".into(),
"#ad8bf2".into(),
"#bff288".into(),
"#de7878".into(),
"#a5aac5".into(),
"#6ff2c5".into(),
"#f0da5e".into(),
"#eb5972".into(),
"#f6be5d".into(),
]
}
fn generate_result(face: &Face, color: &Color, size: &Size) -> image::DynamicImage {
let Size { width, height } = face.dimensions;
let canvas_raw = image::ImageBuffer::from_pixel(width, height, image::Rgb([color.r, color.g, color.b]));
let mut canvas = image::DynamicImage::ImageRgb8(canvas_raw);
imageops::overlay(&mut canvas, &face.eyes, 0, 0);
imageops::overlay(&mut canvas, &face.mouth, 0, 0);
imageops::overlay(&mut canvas, &face.nose, 0, 0);
let output_width = size.width;
let output_height = size.height;
let output = canvas.resize(output_width, output_height, image::FilterType::Lanczos3);
output
}
fn get_feature_image(path: &str) -> Result<image::DynamicImage, Error> {
let feature = image::open(&Path::new(path))
.or_else(|_err| Err(Error::ReadingFromFileFailed { filename: path.to_string() }))?;
Ok(feature)
}
fn get_filenames(dirpath: &str) -> Result<Vec<String>, Error> {
let paths = fs::read_dir(dirpath)
.or_else(|_err| Err(Error::ReadingFromFileFailed { filename: dirpath.to_string() }))?;
let mut entities: Vec<String> = vec![];
for path in paths {
let unwrapped_path = path
.or_else(|_err| Err(Error::ReadingFromFileFailed { filename: dirpath.to_string() }))?;
let full_path = unwrapped_path.path();
let filename = full_path.file_stem()
.ok_or_else(|| Error::DecodeError)?
.to_str()
.ok_or_else(|| Error::DecodeError)?;
entities.push(filename.to_string());
}
Ok(entities)
}
fn get_seed_as_nums(seed: &str) -> [u64;4] {
let mut hasher = Sha256::new();
hasher.input_str(seed);
let mut hash_result = [0u8;32];
hasher.result(&mut hash_result);
let mut output = [0u64;4];
for i in 0..4 {
let begin = i * 8;
let end = begin + 8;
let x = &hash_result[begin..end];
let mut y: u64 = 0;
y |= (x[0] as u64) << 56;
y |= (x[1] as u64) << 48;
y |= (x[2] as u64) << 40;
y |= (x[3] as u64) << 32;
y |= (x[4] as u64) << 24;
y |= (x[5] as u64) << 16;
y |= (x[6] as u64) << 8;
y |= x[7] as u64;
output[i] = y;
}
output
}
fn generate_pseudorandom_face_and_color(seed: &str) -> Result<(Face, Color), Error> {
let nums = get_seed_as_nums(seed);
let eyes = get_filenames("img/eyes/")?;
let mouthes = get_filenames("img/mouth/")?;
let noses = get_filenames("img/nose/")?;
if eyes.len() == 0 || mouthes.len() == 0 || noses.len() == 0 {
return Err(Error::FeaturesNotFound);
}
let random_eyes = &eyes[nums[0] as usize % eyes.len()];
let random_mouth = &mouthes[nums[1] as usize % mouthes.len()];
let random_nose = &noses[nums[2] as usize % noses.len()];
let random_face = Face::new(random_eyes, random_mouth, random_nose)?;
let colors = get_colors();
let random_color = (&colors[nums[3] as usize % colors.len()]).to_owned();
Ok((random_face, random_color))
}
fn generate_random_string() -> String {
uuid::Uuid::new_v4().to_string()
}
#[derive(Clone, PartialEq, Eq)]
pub struct Color {
r: u8,
g: u8,
b: u8,
}
impl From<&'static str> for Color {
fn from(hexcode: &str) -> Self {
let r = u8::from_str_radix(&hexcode[1..3], 16).unwrap_or_default();
let g = u8::from_str_radix(&hexcode[3..5], 16).unwrap_or_default();
let b = u8::from_str_radix(&hexcode[5..7], 16).unwrap_or_default();
Self { r, g, b, }
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Size {
width: u32,
height: u32,
}
impl From<(u32, u32)> for Size {
fn from(size: (u32, u32)) -> Self {
Self {
width: size.0,
height: size.1,
}
}
}
pub struct Face {
eyes: image::DynamicImage,
mouth: image::DynamicImage,
nose: image::DynamicImage,
dimensions: Size,
}
impl Face {
pub fn new(eyes: &str, mouth: &str, nose: &str) -> Result<Self, Error> {
let eyes_image = get_feature_image(&format!("img/eyes/{}.png", eyes))?;
let mouth_image = get_feature_image(&format!("img/mouth/{}.png", mouth))?;
let nose_image = get_feature_image(&format!("img/nose/{}.png", nose))?;
let eyes_dim: Size = eyes_image.dimensions().into();
let mouth_dim: Size = mouth_image.dimensions().into();
let nose_dim: Size = nose_image.dimensions().into();
if eyes_dim == mouth_dim && mouth_dim == nose_dim {
Ok(Self {
eyes: eyes_image,
mouth: mouth_image,
nose: nose_image,
dimensions: eyes_dim
})
} else {
Err(Error::DimensionsDontMatch)
}
}
}
pub struct Avatar {
output: image::DynamicImage,
}
impl Avatar {
pub fn generate_from_id<S>(id: &str, size: S) -> Result<Self, Error>
where Size: From<S>,
{
let (face, color) = generate_pseudorandom_face_and_color(id)?;
let final_size: Size = size.into();
Ok(Self { output: generate_result(&face, &color, &final_size) })
}
pub fn generate_random<S>(size: S) -> Result<Self, Error>
where Size: From<S>,
{
let (face, color) = generate_pseudorandom_face_and_color(&generate_random_string())?;
let final_size: Size = size.into();
Ok(Self { output: generate_result(&face, &color, &final_size) })
}
pub fn build_avatar<S>(face: Face, color: Color, size: S) -> Result<Self, Error>
where Size: From<S>,
{
let final_size: Size = size.into();
Ok(Self { output: generate_result(&face, &color, &final_size) })
}
pub fn output(self, filename: &str) -> Result<(), Error> {
self.output.save(filename)
.or_else(|_err| Err(Error::WritingToFileFailed { filename: filename.to_string() }))
.and_then(|_res| Ok(()))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn check_sha256_is_correct() {
let result = get_seed_as_nums("hunter2");
let formatted_result = format!("{:X?}", result);
assert_eq!(formatted_result, "[F52FBD32B2B3B86F, F88EF6C490628285, F482AF15DDCB2954, 1F94BCF526A3F6C7]");
}
}