Skip to content

Commit

Permalink
feat(cli): added base preprocessors
Browse files Browse the repository at this point in the history
  • Loading branch information
SalOne22 committed Feb 26, 2024
1 parent 4f72b2f commit 99d6a33
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 3 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ codegen-units = 1
opt-level = "z"

[dependencies]
anyhow = "1.0.80"
clap = { version = "4.5.1", features = ["cargo", "string"] }
indoc = "2.0.4"
8 changes: 5 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::path::PathBuf;
use clap::{arg, command, value_parser, Command};
use indoc::indoc;

use crate::utils::threads;
use crate::{preprocessors::preprocessors, utils::threads};

pub fn cli() -> Command {
command!()
let cmd = command!()
.arg_required_else_help(true)
.arg(
arg!([FILES] ... "Input file(s) to process")
Expand Down Expand Up @@ -51,7 +51,9 @@ pub fn cli() -> Command {
Usage of multiple threads can speed up the execution of tasks, especially on multi-core processors.
By default, the number of available threads is utilized"})
.value_parser(value_parser!(u8).range(1..=threads::num_threads() as i64)),
)
);

preprocessors(cmd)
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use cli::cli;

mod cli;
mod preprocessors;
mod utils;

fn main() {
Expand Down
48 changes: 48 additions & 0 deletions src/preprocessors/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use clap::{arg, value_parser, ArgGroup, Command};
use indoc::indoc;

use resize::ResizeValue;

use crate::preprocessors::resize::ResizeFilter;

mod resize;

pub fn preprocessors(cmd: Command) -> Command {
cmd.group(ArgGroup::new("preprocessors").multiple(true))
.next_help_heading("Preprocessors")
.arg(
arg!(--resize <RESIZE> "Resize the image(s) according to the specified criteria")
.long_help(indoc! {"Resize the image(s) according to the specified criteria
Possible values:
- @1.5: Enlarge image size by this multiplier
- 150%: Adjust image size by this percentage
- 100x100: Resize image to these dimensions
- 200x_: Adjust image dimensions while maintaining the aspect ratio based on the specified dimension"})
.value_parser(value_parser!(ResizeValue)),
)
.arg(
arg!(--filter <FILTER> "Filter that used when resizing an image")
.value_parser(value_parser!(ResizeFilter))
.default_value("lanczos3")
.requires("resize"),
)
.arg(
arg!(--quantization [QUALITY] "Enables quantization with optional quality")
.long_help(indoc! {"Enables quantization with optional quality
If quality is not provided default 75% quality is used"})
.value_parser(value_parser!(u8).range(1..=100))
.default_missing_value("75")
)
.arg(
arg!(--dithering [QUALITY] "Enables dithering with optional quality")
.long_help(indoc! {"Enables dithering with optional quality
Used with --quantization flag.
If quality is not provided default 75% quality is used"})
.value_parser(value_parser!(u8).range(1..=100))
.default_missing_value("75")
.requires("quantization")
)
}
58 changes: 58 additions & 0 deletions src/preprocessors/resize/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::anyhow;
use clap::{builder::PossibleValue, ValueEnum};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ResizeFilter {
Box,
Bilinear,
Hamming,
CatmullRom,
Mitchell,
Lanczos3,
}

impl ValueEnum for ResizeFilter {
fn value_variants<'a>() -> &'a [Self] {
&[
ResizeFilter::Box,
ResizeFilter::Bilinear,
ResizeFilter::Hamming,
ResizeFilter::CatmullRom,
ResizeFilter::Mitchell,
ResizeFilter::Lanczos3,
]
}

fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(match self {
ResizeFilter::Box => PossibleValue::new("box").help("Each pixel contributes equally to destination. For upscaling, like Nearest."),
ResizeFilter::Bilinear => PossibleValue::new("bilinear").help("Uses linear interpolation among contributing pixels for output"),
ResizeFilter::Hamming => PossibleValue::new("hamming").help("Provides quality akin to bicubic for downscaling, sharper than Bilinear, but not optimal for upscaling"),
ResizeFilter::CatmullRom => PossibleValue::new("catmull-rom").help("Employs cubic interpolation for output pixel calculation"),
ResizeFilter::Mitchell => PossibleValue::new("mitchell").help("Utilizes cubic interpolation for output pixel calculation"),
ResizeFilter::Lanczos3 => PossibleValue::new("lanczos3").help("Applies high-quality Lanczos filter for output pixel calculation"),
})
}
}

impl std::fmt::Display for ResizeFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}

impl std::str::FromStr for ResizeFilter {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().unwrap().matches(s, false) {
return Ok(*variant);
}
}
Err(anyhow!("invalid variant: {s}"))
}
}
5 changes: 5 additions & 0 deletions src/preprocessors/resize/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod filter;
mod value;

pub use filter::ResizeFilter;
pub use value::ResizeValue;
156 changes: 156 additions & 0 deletions src/preprocessors/resize/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use anyhow::anyhow;

#[derive(Debug, Clone, PartialEq)]
pub enum ResizeValue {
Multiplier(f32),
Percentage(f32),
Dimensions(Option<u32>, Option<u32>),
}

impl std::fmt::Display for ResizeValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResizeValue::Multiplier(multiplier) => f.write_fmt(format_args!("@{multiplier}")),
ResizeValue::Percentage(percentage) => f.write_fmt(format_args!("{percentage}%")),
ResizeValue::Dimensions(Some(width), Some(height)) => {
f.write_fmt(format_args!("{width}x{height}"))
}
ResizeValue::Dimensions(Some(width), None) => f.write_fmt(format_args!("{width}x_")),
ResizeValue::Dimensions(None, Some(height)) => f.write_fmt(format_args!("_x{height}")),
ResizeValue::Dimensions(None, None) => f.write_fmt(format_args!("base")),
}
}
}

impl std::str::FromStr for ResizeValue {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
s if s.starts_with('@') => Ok(Self::Multiplier(s[1..].parse()?)),
s if s.ends_with('%') => Ok(Self::Percentage(s[..s.len() - 1].parse()?)),
s if s.contains('x') => {
let dimensions: Vec<&str> = s.split('x').collect();
if dimensions.len() > 2 {
return Err(anyhow!("There is more that 2 dimensions"));
}

let width = if dimensions[0] == "_" {
None
} else {
Some(dimensions[0].parse::<u32>()?)
};

let height = if dimensions[1] == "_" {
None
} else {
Some(dimensions[1].parse::<u32>()?)
};

Ok(Self::Dimensions(width, height))
}
_ => Err(anyhow!("Invalid resize value")),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn display() {
assert_eq!(ResizeValue::Multiplier(2.).to_string(), "@2");
assert_eq!(ResizeValue::Multiplier(1.5).to_string(), "@1.5");

assert_eq!(ResizeValue::Percentage(200.).to_string(), "200%");
assert_eq!(ResizeValue::Percentage(150.).to_string(), "150%");

assert_eq!(
ResizeValue::Dimensions(Some(200), Some(200)).to_string(),
"200x200"
);
assert_eq!(
ResizeValue::Dimensions(Some(150), Some(150)).to_string(),
"150x150"
);

assert_eq!(
ResizeValue::Dimensions(None, Some(200)).to_string(),
"_x200"
);
assert_eq!(
ResizeValue::Dimensions(None, Some(150)).to_string(),
"_x150"
);

assert_eq!(
ResizeValue::Dimensions(Some(200), None).to_string(),
"200x_"
);
assert_eq!(
ResizeValue::Dimensions(Some(150), None).to_string(),
"150x_"
);

assert_eq!(ResizeValue::Dimensions(None, None).to_string(), "base");
}

#[test]
fn from_str() {
assert_eq!(
"@2".parse::<ResizeValue>().unwrap(),
ResizeValue::Multiplier(2.)
);

assert_eq!(
"@1.5".parse::<ResizeValue>().unwrap(),
ResizeValue::Multiplier(1.5)
);

assert_eq!(
"200%".parse::<ResizeValue>().unwrap(),
ResizeValue::Percentage(200.)
);

assert_eq!(
"150%".parse::<ResizeValue>().unwrap(),
ResizeValue::Percentage(150.)
);

assert_eq!(
"200x200".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(Some(200), Some(200))
);

assert_eq!(
"150x150".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(Some(150), Some(150))
);

assert_eq!(
"200x_".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(Some(200), None)
);

assert_eq!(
"150x_".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(Some(150), None)
);

assert_eq!(
"_x200".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(None, Some(200))
);

assert_eq!(
"_x150".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(None, Some(150))
);

assert_eq!(
"_x_".parse::<ResizeValue>().unwrap(),
ResizeValue::Dimensions(None, None)
);
}
}

0 comments on commit 99d6a33

Please sign in to comment.