1
2extern crate image;
3extern crate uuid;
4extern crate crypto;
5
6#[macro_use] extern crate failure;
7
8use std::path::Path;
9
10use image::imageops;
11use image::GenericImageView;
12use crypto::digest::Digest;
13use crypto::sha2::Sha256;
14
15use std::fs;
16use std::u8;
17
18use std::convert::From;
19
20#[derive(Clone, Debug, Fail)]
21pub enum Error {
22 #[fail(display = "could not open file: {}", filename)]
23 ReadingFromFileFailed {
24 filename: String,
25 },
26 #[fail(display = "could not write to file: {}", filename)]
27 WritingToFileFailed {
28 filename: String,
29 },
30 #[fail(display = "error decoding filename")]
31 DecodeError,
32 #[fail(display = "features not found in image directory")]
33 FeaturesNotFound,
34 #[fail(display = "dimensions must match")]
35 DimensionsDontMatch,
36}
37
38fn get_colors() -> Vec<Color> {
39 vec![
40 "#81bef1".into(),
41 "#ad8bf2".into(),
42 "#bff288".into(),
43 "#de7878".into(),
44 "#a5aac5".into(),
45 "#6ff2c5".into(),
46 "#f0da5e".into(),
47 "#eb5972".into(),
48 "#f6be5d".into(),
49 ]
50}
51
52fn generate_result(face: &Face, color: &Color, size: &Size) -> image::DynamicImage {
53
54 let Size { width, height } = face.dimensions;
55 let canvas_raw = image::ImageBuffer::from_pixel(width, height, image::Rgb([color.r, color.g, color.b]));
56 let mut canvas = image::DynamicImage::ImageRgb8(canvas_raw);
57 imageops::overlay(&mut canvas, &face.eyes, 0, 0);
58 imageops::overlay(&mut canvas, &face.mouth, 0, 0);
59 imageops::overlay(&mut canvas, &face.nose, 0, 0);
60
61 let output_width = size.width;
62 let output_height = size.height;
63
64 let output = canvas.resize(output_width, output_height, image::FilterType::Lanczos3);
65
66 output
67}
68
69fn get_feature_image(path: &str) -> Result<image::DynamicImage, Error> {
70 let feature = image::open(&Path::new(path))
71 .or_else(|_err| Err(Error::ReadingFromFileFailed { filename: path.to_string() }))?;
72 Ok(feature)
73}
74
75fn get_filenames(dirpath: &str) -> Result<Vec<String>, Error> {
76
77 let paths = fs::read_dir(dirpath)
78 .or_else(|_err| Err(Error::ReadingFromFileFailed { filename: dirpath.to_string() }))?;
79
80 let mut entities: Vec<String> = vec![];
81 for path in paths {
82 let unwrapped_path = path
83 .or_else(|_err| Err(Error::ReadingFromFileFailed { filename: dirpath.to_string() }))?;
84
85 let full_path = unwrapped_path.path();
86 let filename = full_path.file_stem()
87 .ok_or_else(|| Error::DecodeError)?
88 .to_str()
89 .ok_or_else(|| Error::DecodeError)?;
90
91
92 entities.push(filename.to_string());
93 }
94
95 Ok(entities)
96
97}
98
99fn get_seed_as_nums(seed: &str) -> [u64;4] {
100 let mut hasher = Sha256::new();
101 hasher.input_str(seed);
102
103 let mut hash_result = [0u8;32];
104 hasher.result(&mut hash_result);
105
106 let mut output = [0u64;4];
107 for i in 0..4 {
108 let begin = i * 8;
109 let end = begin + 8;
110 let x = &hash_result[begin..end];
111
112 let mut y: u64 = 0;
113 y |= (x[0] as u64) << 56;
114 y |= (x[1] as u64) << 48;
115 y |= (x[2] as u64) << 40;
116 y |= (x[3] as u64) << 32;
117 y |= (x[4] as u64) << 24;
118 y |= (x[5] as u64) << 16;
119 y |= (x[6] as u64) << 8;
120 y |= x[7] as u64;
121
122 output[i] = y;
123 }
124
125 output
126
127}
128
129fn generate_pseudorandom_face_and_color(seed: &str) -> Result<(Face, Color), Error> {
130 let nums = get_seed_as_nums(seed);
131
132 let eyes = get_filenames("img/eyes/")?;
133 let mouthes = get_filenames("img/mouth/")?;
134 let noses = get_filenames("img/nose/")?;
135
136 if eyes.len() == 0 || mouthes.len() == 0 || noses.len() == 0 {
137 return Err(Error::FeaturesNotFound);
138 }
139
140 let random_eyes = &eyes[nums[0] as usize % eyes.len()];
141 let random_mouth = &mouthes[nums[1] as usize % mouthes.len()];
142 let random_nose = &noses[nums[2] as usize % noses.len()];
143
144 let random_face = Face::new(random_eyes, random_mouth, random_nose)?;
145
146 let colors = get_colors();
147 let random_color = (&colors[nums[3] as usize % colors.len()]).to_owned();
148
149 Ok((random_face, random_color))
150}
151
152fn generate_random_string() -> String {
153 uuid::Uuid::new_v4().to_string()
154}
155
156
157#[derive(Clone, PartialEq, Eq)]
158pub struct Color {
159 r: u8,
160 g: u8,
161 b: u8,
162}
163
164impl From<&'static str> for Color {
165 fn from(hexcode: &str) -> Self {
166 let r = u8::from_str_radix(&hexcode[1..3], 16).unwrap_or_default();
167 let g = u8::from_str_radix(&hexcode[3..5], 16).unwrap_or_default();
168 let b = u8::from_str_radix(&hexcode[5..7], 16).unwrap_or_default();
169 Self { r, g, b, }
170 }
171}
172
173
174#[derive(Clone, PartialEq, Eq)]
175pub struct Size {
176 width: u32,
177 height: u32,
178}
179
180impl From<(u32, u32)> for Size {
181 fn from(size: (u32, u32)) -> Self {
182 Self {
183 width: size.0,
184 height: size.1,
185 }
186 }
187}
188
189pub struct Face {
190 eyes: image::DynamicImage,
191 mouth: image::DynamicImage,
192 nose: image::DynamicImage,
193 dimensions: Size,
194}
195
196impl Face {
197
198 pub fn new(eyes: &str, mouth: &str, nose: &str) -> Result<Self, Error> {
199 let eyes_image = get_feature_image(&format!("img/eyes/{}.png", eyes))?;
200 let mouth_image = get_feature_image(&format!("img/mouth/{}.png", mouth))?;
201 let nose_image = get_feature_image(&format!("img/nose/{}.png", nose))?;
202
203 let eyes_dim: Size = eyes_image.dimensions().into();
204 let mouth_dim: Size = mouth_image.dimensions().into();
205 let nose_dim: Size = nose_image.dimensions().into();
206
207 if eyes_dim == mouth_dim && mouth_dim == nose_dim {
208 Ok(Self {
209 eyes: eyes_image,
210 mouth: mouth_image,
211 nose: nose_image,
212 dimensions: eyes_dim
213 })
214 } else {
215 Err(Error::DimensionsDontMatch)
216 }
217 }
218}
219
220
221pub struct Avatar {
222 output: image::DynamicImage,
223}
224
225impl Avatar {
226
227 pub fn generate_from_id<S>(id: &str, size: S) -> Result<Self, Error>
228 where Size: From<S>,
229 {
230 let (face, color) = generate_pseudorandom_face_and_color(id)?;
231 let final_size: Size = size.into();
232 Ok(Self { output: generate_result(&face, &color, &final_size) })
233 }
234
235 pub fn generate_random<S>(size: S) -> Result<Self, Error>
236 where Size: From<S>,
237 {
238 let (face, color) = generate_pseudorandom_face_and_color(&generate_random_string())?;
239 let final_size: Size = size.into();
240 Ok(Self { output: generate_result(&face, &color, &final_size) })
241 }
242
243 pub fn build_avatar<S>(face: Face, color: Color, size: S) -> Result<Self, Error>
244 where Size: From<S>,
245 {
246 let final_size: Size = size.into();
247 Ok(Self { output: generate_result(&face, &color, &final_size) })
248 }
249
250 pub fn output(self, filename: &str) -> Result<(), Error> {
251 self.output.save(filename)
252 .or_else(|_err| Err(Error::WritingToFileFailed { filename: filename.to_string() }))
253 .and_then(|_res| Ok(()))
254 }
255}
256
257
258
259
260#[cfg(test)]
261mod test {
262 use super::*;
263
264 #[test]
265 fn check_sha256_is_correct() {
266 let result = get_seed_as_nums("hunter2");
267 let formatted_result = format!("{:X?}", result);
268
269 assert_eq!(formatted_result, "[F52FBD32B2B3B86F, F88EF6C490628285, F482AF15DDCB2954, 1F94BCF526A3F6C7]");
270 }
271}
272
273
274