use crate::error::DichroismError; use crate::result::Result; use base64::decode; use image::imageops::FilterType; use image::DynamicImage; use image::GenericImageView; use regex::Regex; use std::path::PathBuf; use uuid::Uuid; use once_cell::sync::Lazy; static DATA_URI_RE: Lazy = Lazy::new(|| { Regex::new("^data:image/(png|jpeg);base64,(?P.+)") .expect("Couldn't parse data URI Regex.") }); pub struct NewProductImageData { original: DynamicImage, // original, just for safe-keeping fullsize: DynamicImage, // full-size, "zoomed" view base: DynamicImage, // basic viewing thumbnail: DynamicImage, // tiny, square thumbnail } impl NewProductImageData { pub fn from_data_uri(uri: &str) -> Result { let data = DATA_URI_RE .captures(uri) .ok_or(DichroismError::UriDataExtract)? .name("data") .ok_or(DichroismError::UriDataExtract)? .as_str(); let original = image::load_from_memory(&decode(data)?)?; let fullsize = original.resize(1000, 1000, FilterType::Lanczos3); let base = original.resize(640, 640, FilterType::Lanczos3); let (width, height) = original.dimensions(); let thumbnail = if width > height { let offset = (width - height) / 2; original.crop_imm(offset, 0, width - offset * 2, height) } else { let offset = (height - width) / 2; original.crop_imm(0, offset, width, height - offset * 2) } .resize(300, 300, FilterType::Lanczos3); Ok(NewProductImageData { original, fullsize, base, thumbnail, }) } pub fn commit(self, prefix: &str) -> Result { Ok(NewProductImages { original: self.commit_single(prefix, &self.original)?, fullsize: self.commit_single(prefix, &self.fullsize)?, base: self.commit_single(prefix, &self.base)?, thumbnail: self.commit_single(prefix, &self.thumbnail)?, }) } fn commit_single(&self, prefix: &str, image: &DynamicImage) -> Result { let base_name = Uuid::new_v3(&Uuid::NAMESPACE_OID, &image.to_bytes()) .to_hyphenated() .to_string(); let mut path = PathBuf::new(); path.push(prefix); path.push(base_name); path.set_extension("jpg"); image.save(&path)?; Ok(path.to_str().ok_or(DichroismError::ImageWrite)?.to_string()) } } pub struct NewProductImages { original: String, fullsize: String, base: String, thumbnail: String, } #[cfg(test)] mod tests { use super::*; const TEST_DATA_URI: &str = include_str!("unit_test_data/img_data_uri.txt"); const TEST_DATA_BASE64: &str = include_str!("unit_test_data/test_data_base64.txt"); #[test] fn test_gen_product_images() { NewProductImageData::from_data_uri(TEST_DATA_URI.trim()) .unwrap() .commit(".") .unwrap(); } }