use crate::fs_utils::find_extensions_icase;
use std::{fs, path::PathBuf};
use anyhow::{Result, bail};
use clap::{Parser, ValueEnum};
use fast_image_resize::{FilterType, ResizeAlg};
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Args {
#[arg(required = true)]
paths: Vec<PathBuf>,
#[arg(long)]
no_theme: bool,
#[arg(
long,
default_value = "lanczos3",
value_name = "ALGORITHM",
verbatim_doc_comment
)]
scale_with: ScalingAlgorithm,
#[arg(long, value_name = "ALGORITHM")]
upscale_with: Option<ScalingAlgorithm>,
#[arg(long, value_name = "ALGORITHM")]
downscale_with: Option<ScalingAlgorithm>,
#[arg(long, value_parser, num_args(1..), value_name = "F64_SCALE_FACTORS")]
scale_to: Vec<f64>,
#[arg(short, long, default_value = "./")]
out: PathBuf,
}
#[derive(Debug, Clone, ValueEnum)]
enum ScalingAlgorithm {
Nearest,
Box,
Bilinear,
Mitchell,
Lanczos3,
}
impl From<&ScalingAlgorithm> for FilterType {
fn from(alg: &ScalingAlgorithm) -> Self {
match alg {
ScalingAlgorithm::Nearest => unreachable!(),
ScalingAlgorithm::Box => Self::Box,
ScalingAlgorithm::Bilinear => Self::Bilinear,
ScalingAlgorithm::Mitchell => Self::Mitchell,
ScalingAlgorithm::Lanczos3 => Self::Lanczos3,
}
}
}
impl From<&ScalingAlgorithm> for ResizeAlg {
fn from(alg: &ScalingAlgorithm) -> Self {
match alg {
ScalingAlgorithm::Nearest => Self::Nearest,
v => Self::Convolution(FilterType::from(v)),
}
}
}
#[derive(Debug)]
pub struct ParsedArgs {
pub cursor_theme_dirs: Vec<PathBuf>,
pub cursor_files: Vec<PathBuf>,
pub scale_to: Vec<f64>,
pub upscale_with: ResizeAlg,
pub downscale_with: ResizeAlg,
pub out: PathBuf,
}
impl ParsedArgs {
pub fn from_args(args: Args) -> Result<Self> {
let paths = args.paths;
let mut cursor_theme_dirs = Vec::new();
let mut cursor_files = Vec::new();
for path in paths {
let path_display = path.display();
if !path.exists() {
#[cfg(windows)]
bail!(
"path={path_display} doesn't exist. \n\
note that if you use powershell and your path looks similar to the \
first, convert it to the second by removing the trailing backslash: \n\
.\\currust.exe '.\\a path\\to a\\dir\\' -> .\\currust.exe '.\\a path\\to a\\dir'"
);
bail!("path={path_display} doesn't exist");
}
if path.is_dir() {
cursor_theme_dirs.push(path);
} else if path.is_file() {
cursor_files.push(path);
} else {
bail!(
"provided path={} is neither a dir or a file",
path.display()
);
}
}
let mut scale_to = args.scale_to;
for &sf in &scale_to {
if sf.is_nan() || sf.is_infinite() {
bail!("invalid sf={sf}: can't be NaN or pos/neg infinity")
}
if sf <= 0.1 {
bail!("invalid sf={sf}: can't be 0.1 or less");
}
if sf > 100.0 {
bail!("invalid sf={sf}: can't be greater than 100.0")
}
}
scale_to.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
scale_to.dedup();
let (upscale_with, downscale_with) = (
ResizeAlg::from(args.upscale_with.as_ref().unwrap_or(&args.scale_with)),
ResizeAlg::from(args.downscale_with.as_ref().unwrap_or(&args.scale_with)),
);
let out = args.out;
fs::create_dir_all(&out)?;
if args.no_theme {
for theme in cursor_theme_dirs.drain(..) {
let cursors = find_extensions_icase(&theme, &["cur", "ani"])?;
cursor_files.extend(cursors);
}
}
Ok(Self {
cursor_theme_dirs,
cursor_files,
scale_to,
upscale_with,
downscale_with,
out,
})
}
#[must_use]
pub fn get_algorithm(&self, scale_factor: f64) -> ResizeAlg {
if scale_factor > 1.0 {
self.upscale_with
} else {
self.downscale_with
}
}
}