added chapter 4

master
peshwar9 4 years ago
parent 32ce7ac4d5
commit 839540aa0f

BIN
.DS_Store vendored

Binary file not shown.

BIN
chapter4/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,2 @@
/target
Cargo.lock

@ -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"

@ -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"),
},
},
}
}

@ -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<io::Error> for ImagixError {
fn from(error: io::Error) -> Self {
ImagixError::FileIOError("Error in File I/O".to_string())
}
}
impl From<error::ImageError> for ImagixError {
fn from(error: error::ImageError) -> Self {
ImagixError::ImageResizingError("Error in image processing".to_string())
}
}
impl From<io::ErrorKind> 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())
)
}
}

@ -0,0 +1,3 @@
pub mod error;
pub mod resize;
pub mod stats;

@ -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<Self, Self::Err> {
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<Self, Self::Err> {
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<Vec<PathBuf>, 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::<Result<Vec<_>, 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());
}
}

@ -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::<u64>();
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);
}
}
Loading…
Cancel
Save