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/src/error.rs | 1 + dichroism/src/handlers.rs | 20 ++++----- dichroism/src/image_api.rs | 101 ++++++++++++++++++++++++++++++++------------- 3 files changed, 84 insertions(+), 38 deletions(-) (limited to 'dichroism/src') 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