From 839540aa0fc7f10254d22e7206209831401dfd69 Mon Sep 17 00:00:00 2001 From: peshwar9 Date: Wed, 22 Jul 2020 23:58:39 +0530 Subject: [PATCH] added chapter 4 --- .DS_Store | Bin 0 -> 6148 bytes chapter4/.DS_Store | Bin 0 -> 6148 bytes chapter4/.gitignore | 2 + chapter4/Cargo.toml | 19 ++++ chapter4/src/imagecli.rs | 63 ++++++++++++ chapter4/src/imagix/error.rs | 39 ++++++++ chapter4/src/imagix/mod.rs | 3 + chapter4/src/imagix/resize.rs | 176 ++++++++++++++++++++++++++++++++++ chapter4/src/imagix/stats.rs | 24 +++++ 9 files changed, 326 insertions(+) create mode 100644 .DS_Store create mode 100644 chapter4/.DS_Store create mode 100644 chapter4/.gitignore create mode 100644 chapter4/Cargo.toml create mode 100644 chapter4/src/imagecli.rs create mode 100644 chapter4/src/imagix/error.rs create mode 100644 chapter4/src/imagix/mod.rs create mode 100644 chapter4/src/imagix/resize.rs create mode 100644 chapter4/src/imagix/stats.rs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..02fed40cf72cc30e68a88445e05f528c335556c5 GIT binary patch literal 6148 zcmeHK%}N6?5T4Ns?Y>kl!C}~(R5+X6CmdtM|!3M-O>I2rI6?^PRZV{ zXiGbKqMio(pRcxE+Yh!}@M^od-t4;#ysDe`*V}dTwkYRoyXKpo{qAh#!6saF2AlzB zz!`7`e#`*wY?10((R*jW8E^)^7?ATJpb199q?nHmbh!io$}>6(bg3mICKyJ;qzDUy zH590!Y$XP3IOc=-MZ=`1;lx&au&w-Ayl`F}`9pRmj*8wp1J1yhflD1u<^F%bFHa(cu>jlcHpi S`5X@Phd?I8J7?e*82A99LM?s( literal 0 HcmV?d00001 diff --git a/chapter4/.DS_Store b/chapter4/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..748a747140720e1a7879c5f0a8428801d621b7df GIT binary patch literal 6148 zcmeHK%}T>S5T3CSf(7Z(;~qQpHMtakA{OUXG`Wrrz{y@>fmr z>=~WWl2&w|KL5*O*H<@XIbSz(*z%*})%#WTcAomHAN`w~4mXF$BpT3`?r4!#J-yv| zx-~DKUO%3%V@=Den_b0yZ&HtDQqRR1a0Z+KXTTW{25@JKWDodIXS8Te*E&WC^@ zSQxg7@#w%5TL54Na}?;(OGr*IEDT#kcp$8)Kuu+9F<8@K4;EJ#wu+igY|RH-<*(+2 zV|DBwVmNW3=(98645SPUbUBjy{{o*(ZJ7 s3=xTIMS(z1egd$NbL1pC)gMGhTw&NM$}VD$=|KMxh=lm$4EzEEpO;=jZ~y=R literal 0 HcmV?d00001 diff --git a/chapter4/.gitignore b/chapter4/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/chapter4/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/chapter4/Cargo.toml b/chapter4/Cargo.toml new file mode 100644 index 0000000..cbbb971 --- /dev/null +++ b/chapter4/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "imagecli" +version = "0.1.0" +authors = ["peshwar9"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "imagix" +path = "src/imagix/mod.rs" + +[[bin]] +name = "imagecli" +path = "src/imagecli.rs" + +[dependencies] +image = "0.23.7" +structopt = "0.3.15" diff --git a/chapter4/src/imagecli.rs b/chapter4/src/imagecli.rs new file mode 100644 index 0000000..3838897 --- /dev/null +++ b/chapter4/src/imagecli.rs @@ -0,0 +1,63 @@ +mod imagix; +use ::imagix::error::ImagixError; +use ::imagix::resize::{process_resize_request, Mode, SizeOption}; +use ::imagix::stats::get_stats; +use std::path::PathBuf; +use structopt::StructOpt; +// Define commandline arguments in a struct + +#[derive(StructOpt, Debug)] +#[structopt( + name = "resize", + about = "This is a tool for image resizing and stats", + help = "Specify subcommand resize or stats. For help, type imagecli resize --help or imagecli stats --help" +)] +enum Commandline { + #[structopt(help = "Specify size(small/medium/large) , mode(single/all) and srcfolder")] + Resize { + #[structopt(long)] + size: SizeOption, + #[structopt(long)] + mode: Mode, + #[structopt(long, parse(from_os_str))] + srcfolder: PathBuf, + }, + #[structopt(help = "Specify srcfolder")] + Stats { + #[structopt(long, parse(from_os_str))] + srcfolder: PathBuf, + }, +} + +fn main() { + let args: Commandline = Commandline::from_args(); + match args { + Commandline::Resize { + size, + mode, + srcfolder, + } => { + let mut src_folder = srcfolder; + match process_resize_request(size, mode, &mut src_folder) { + Ok(_) => println!("Image(s) resized successfully"), + Err(e) => match e { + ImagixError::FileIOError(e) => println!("{}", e), + ImagixError::UserInputError(e) => println!("{}", e), + ImagixError::ImageResizingError(e) => println!("{}", e), + _ => println!("Error in processing"), + }, + }; + } + Commandline::Stats { srcfolder } => match get_stats(srcfolder) { + Ok((count, size)) => println!( + "Found {:?} image files with aggregate size of {:?} MB", + count, size + ), + Err(e) => match e { + ImagixError::FileIOError(e) => println!("{}", e), + ImagixError::UserInputError(e) => println!("{}", e), + _ => println!("Error in processing"), + }, + }, + } +} diff --git a/chapter4/src/imagix/error.rs b/chapter4/src/imagix/error.rs new file mode 100644 index 0000000..901c83d --- /dev/null +++ b/chapter4/src/imagix/error.rs @@ -0,0 +1,39 @@ +use image::error; +use std::convert::From; +use std::{fmt, io}; + +#[derive(Debug)] +pub enum ImagixError { + FileIOError(String), + UserInputError(String), + ImageResizingError(String), + FormatError(String), +} + +impl From for ImagixError { + fn from(error: io::Error) -> Self { + ImagixError::FileIOError("Error in File I/O".to_string()) + } +} + +impl From for ImagixError { + fn from(error: error::ImageError) -> Self { + ImagixError::ImageResizingError("Error in image processing".to_string()) + } +} + +impl From for ImagixError { + fn from(error: io::ErrorKind) -> Self { + ImagixError::UserInputError("Error in user input".to_string()) + } +} + +impl fmt::Display for ImagixError { + fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + out, + "{}", + ImagixError::FormatError("Error occurred".to_string()) + ) + } +} diff --git a/chapter4/src/imagix/mod.rs b/chapter4/src/imagix/mod.rs new file mode 100644 index 0000000..2e0e46f --- /dev/null +++ b/chapter4/src/imagix/mod.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod resize; +pub mod stats; diff --git a/chapter4/src/imagix/resize.rs b/chapter4/src/imagix/resize.rs new file mode 100644 index 0000000..6fc952a --- /dev/null +++ b/chapter4/src/imagix/resize.rs @@ -0,0 +1,176 @@ +use image::ImageFormat; +use std::path::PathBuf; +use std::result::Result; +use std::str::FromStr; +use std::time::{Duration, Instant}; +use std::{fmt, fs, io}; + +use super::error::ImagixError; + +struct Elapsed(Duration); + +impl Elapsed { + fn from(start: &Instant) -> Self { + Elapsed(start.elapsed()) + } +} +#[derive(Debug)] +pub enum SizeOption { + Small, + Medium, + Large, +} +impl fmt::Display for Elapsed { + fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match (self.0.as_secs(), self.0.subsec_nanos()) { + (0, n) if n < 1000 => write!(out, "{} ns", n), + (0, n) if n < 1000_000 => write!(out, "{} µs", n / 1000), + (0, n) => write!(out, "{} ms", n / 1000_000), + (s, n) if s < 10 => write!(out, "{}.{:02} s", s, n / 10_000_000), + (s, _) => write!(out, "{} s", s), + } + } +} +impl FromStr for SizeOption { + type Err = ImagixError; + fn from_str(s: &str) -> Result { + match s { + "small" => Ok(SizeOption::Small), + "medium" => Ok(SizeOption::Medium), + "large" => Ok(SizeOption::Large), + _ => Err(ImagixError::UserInputError( + "Invalid input for size".to_string(), + )), + //default + } + } +} + +impl FromStr for Mode { + type Err = ImagixError; + fn from_str(s: &str) -> Result { + match s { + "single" => Ok(Mode::Single), + "all" => Ok(Mode::All), + _ => Err(ImagixError::UserInputError( + "Wrong value for mode".to_string(), + )), + } + } +} +#[derive(Debug, PartialEq)] +pub enum Mode { + Single, + All, +} + +pub fn process_resize_request( + size: SizeOption, + mode: Mode, + src_folder: &mut PathBuf, +) -> Result<(), ImagixError> { + let size = match size { + SizeOption::Small => 200, + SizeOption::Medium => 400, + SizeOption::Large => 800, + }; + let _ = match mode { + Mode::All => resize_all(size, src_folder)?, + Mode::Single => resize_single(size, src_folder)?, + }; + Ok(()) +} + +fn resize_single(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> { + let mut src_folder = src_folder; + // Get file stem from src_folder + resize_image(size, &mut src_folder)?; + Ok(()) +} + +fn resize_all(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> { + if let Ok(entries) = get_image_files(src_folder.to_path_buf()) { + for mut entry in entries { + resize_image(size, &mut entry)?; + } + }; + Ok(()) +} + +fn resize_image(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> { + // Construct destination file name th .png extension + let new_file_name = src_folder + .file_stem() + .unwrap() + .to_str() + .ok_or(std::io::ErrorKind::InvalidInput) + // .map(|f| format!("{}-{:?}.png", f, size)); + .map(|f| format!("{}.png", f)); + + // Construct path to destination folder i.e. create /tmp under source folder if not exists + let mut dest_folder = src_folder.clone(); + dest_folder.pop(); + dest_folder.push("tmp/"); + if !dest_folder.exists() { + fs::create_dir(&dest_folder)?; + } + dest_folder.pop(); + dest_folder.push("tmp/tmp.png"); + dest_folder.set_file_name(new_file_name?.as_str()); + + // Open source image file, scale it to desired size and write output to destination-folder/destination-file + let timer = Instant::now(); + let img = image::open(&src_folder)?; + let scaled = img.thumbnail(size, size); + let mut output = fs::File::create(&dest_folder)?; + scaled.write_to(&mut output, ImageFormat::Png)?; + println!( + "Thumbnailed file: {:?} to size {}x{} in {}. Output file in {:?}", + src_folder, + size, + size, + Elapsed::from(&timer), + dest_folder + ); + Ok(()) +} + +pub fn get_image_files(src_folder: PathBuf) -> Result, ImagixError> { + let entries = fs::read_dir(src_folder) + .map_err(|e| ImagixError::UserInputError("Invalid source folder".to_string()))? + .map(|res| res.map(|e| e.path())) + .collect::, io::Error>>()? + .into_iter() + .filter(|r| { + r.extension() == Some("JPG".as_ref()) + || r.extension() == Some("jpg".as_ref()) + || r.extension() == Some("PNG".as_ref()) + || r.extension() == Some("png".as_ref()) + }) + .collect(); + Ok(entries) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_single_image_resize() { + let mut path = PathBuf::from("/tmp/images/image1.jpg"); + let destination_path = PathBuf::from("/tmp/images/tmp/image1.png"); + match process_resize_request(SizeOption::Small, Mode::Single, &mut path) { + Ok(dest) => assert_eq!(true, destination_path.exists()), + Err(e) => println!("Error in single image: {:?}", e), + } + assert_eq!(true, destination_path.exists()); + } + #[test] + fn test_multiple_image_resize() { + let mut path = PathBuf::from("/tmp/images/"); + let _res = process_resize_request(SizeOption::Small, Mode::All, &mut path); + let destination_path1 = PathBuf::from("/tmp/images/tmp/image1.png"); + let destination_path2 = PathBuf::from("/tmp/images/tmp/image2.png"); + assert_eq!(true, destination_path1.exists()); + assert_eq!(true, destination_path2.exists()); + } +} diff --git a/chapter4/src/imagix/stats.rs b/chapter4/src/imagix/stats.rs new file mode 100644 index 0000000..4184dd1 --- /dev/null +++ b/chapter4/src/imagix/stats.rs @@ -0,0 +1,24 @@ +use super::error::ImagixError; +use super::resize::get_image_files; +use std::path::PathBuf; + +pub fn get_stats(src_folder: PathBuf) -> Result<(usize, u64), ImagixError> { + let image_files = get_image_files(src_folder.to_path_buf())?; + let size = image_files + .iter() + .map(move |f| f.metadata().unwrap().len()) + .sum::(); + Ok((image_files.len(), size / 1000000)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_get_stats() { + let path = PathBuf::from("/tmp/images"); + let (count, size) = get_stats(path).unwrap(); + assert_eq!(count, 2); + assert_eq!(size, 8); + } +}