From d3a28fde46bb06f084c74904fa8849b40e5f8c87 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Thu, 8 Oct 2020 21:56:57 -0400 Subject: refactored image_api into NewProductImageData and NewProductImages, ready for repo --- dichroism/Cargo.lock | 18 +++++++- dichroism/Cargo.toml | 1 + dichroism/src/error.rs | 1 + dichroism/src/handlers.rs | 20 ++++----- dichroism/src/image_api.rs | 101 ++++++++++++++++++++++++++++++++------------- 5 files changed, 102 insertions(+), 39 deletions(-) diff --git a/dichroism/Cargo.lock b/dichroism/Cargo.lock index 746457d..9184f40 100644 --- a/dichroism/Cargo.lock +++ b/dichroism/Cargo.lock @@ -733,6 +733,7 @@ dependencies = [ "serde", "serde_json", "toml", + "uuid 0.8.1", ] [[package]] @@ -1259,7 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "492158e732f2e2de81c592f0a2427e57e12cd3d59877378fe7af624b6bbe0ca1" dependencies = [ "libc", - "uuid", + "uuid 0.6.5", "winapi 0.3.9", ] @@ -1314,6 +1315,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "md5" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" + [[package]] name = "memchr" version = "2.3.3" @@ -2239,6 +2246,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "md5", +] + [[package]] name = "vcpkg" version = "0.2.10" diff --git a/dichroism/Cargo.toml b/dichroism/Cargo.toml index 3ecf4cb..f699617 100644 --- a/dichroism/Cargo.toml +++ b/dichroism/Cargo.toml @@ -18,3 +18,4 @@ regex = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" +uuid = { version = "0.8", features = ["v3"] } diff --git a/dichroism/src/error.rs b/dichroism/src/error.rs index 17c0024..43933d4 100644 --- a/dichroism/src/error.rs +++ b/dichroism/src/error.rs @@ -2,6 +2,7 @@ pub enum DichroismError { UriDataExtract, InvalidImageRoot, + ImageWrite, } impl std::error::Error for DichroismError {} diff --git a/dichroism/src/handlers.rs b/dichroism/src/handlers.rs index 0e2b2c3..f8f10d3 100644 --- a/dichroism/src/handlers.rs +++ b/dichroism/src/handlers.rs @@ -25,17 +25,17 @@ async fn get_images(pool: web::Data) -> Result { #[post("/images")] async fn create_image(_config: web::Data, req_body: String) -> impl Responder { - let data = match image_api::extract_data(&req_body) { - Err(e) => return HttpResponse::BadRequest().body(format!("fail: {}", e.to_string())), - Ok(d) => d, - }; + // let data = match image_api::extract_data(&req_body) { + // Err(e) => return HttpResponse::BadRequest().body(format!("fail: {}", e.to_string())), + // Ok(d) => d, + // }; - if let Err(e) = image_api::generate_images(data) { - return HttpResponse::BadRequest().body(format!( - "Unable to extract image from data URI: {}", - e.to_string() - )); - } + // if let Err(e) = image_api::generate_images(data) { + // return HttpResponse::BadRequest().body(format!( + // "Unable to extract image from data URI: {}", + // e.to_string() + // )); + // } HttpResponse::Ok().body("Image created.") } diff --git a/dichroism/src/image_api.rs b/dichroism/src/image_api.rs index 3eff88e..07b9552 100644 --- a/dichroism/src/image_api.rs +++ b/dichroism/src/image_api.rs @@ -1,36 +1,84 @@ 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 Regex.") + Regex::new("^data:image/(png|jpeg);base64,(?P.+)") + .expect("Couldn't parse data URI Regex.") }); -pub fn generate_images(data: &str) -> Result<()> { - let bytes = decode(data)?; - let img = image::load_from_memory(&bytes)?; - //img.save("test_full.jpg")?; - let _thumb = img.thumbnail(200, 200); - //thumb.save("test_thumbnail.jpg")?; - Ok(()) - - // TODO: gather and return related images as a tuple. then in another function, write them - // out to the filesystem and return NewImages using the resulting paths. The pathnames - // *probably* need to be UUIDs. +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 } -pub fn extract_data(uri: &str) -> Result<&str> { - let caps = DATA_URI_RE - .captures(uri) - .ok_or(DichroismError::UriDataExtract)?; +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); - Ok(caps - .name("data") - .expect("Should never fail if regex succeeded.") - .as_str()) + 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)] @@ -41,13 +89,10 @@ mod tests { const TEST_DATA_BASE64: &str = include_str!("unit_test_data/test_data_base64.txt"); #[test] - fn test_generate_images() { - generate_images(TEST_DATA_BASE64.trim()).unwrap(); - } - - #[test] - fn test_extract_data() { - let base64_data = extract_data(TEST_DATA_URI).unwrap(); - assert_eq!(TEST_DATA_BASE64.trim(), base64_data); + fn test_gen_product_images() { + NewProductImageData::from_data_uri(TEST_DATA_URI.trim()) + .unwrap() + .commit(".") + .unwrap(); } } -- cgit v1.2.3