summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock195
-rw-r--r--Cargo.toml2
-rw-r--r--src/main.rs195
3 files changed, 257 insertions, 135 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 29219a1..1d83038 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18,23 +18,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
-name = "ansi_term"
-version = "0.12.1"
+name = "anstream"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
- "winapi",
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
]
[[package]]
-name = "atty"
-version = "0.2.14"
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+dependencies = [
+ "anstyle",
+ "windows-sys",
]
[[package]]
@@ -81,26 +109,57 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
-version = "2.34.0"
+version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
dependencies = [
- "ansi_term",
- "atty",
- "bitflags 1.3.2",
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
"strsim",
- "textwrap",
- "unicode-width",
- "vec_map",
]
[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -250,13 +309,10 @@ dependencies = [
]
[[package]]
-name = "hermit-abi"
-version = "0.1.19"
+name = "heck"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "image"
@@ -540,9 +596,9 @@ dependencies = [
[[package]]
name = "strsim"
-version = "0.8.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
@@ -567,15 +623,6 @@ dependencies = [
]
[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-dependencies = [
- "unicode-width",
-]
-
-[[package]]
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -613,16 +660,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
-name = "unicode-width"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
-
-[[package]]
-name = "vec_map"
-version = "0.8.2"
+name = "utf8parse"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
@@ -659,6 +700,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 3b57b13..dd270a9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
acap = "0.3.0"
-clap = "2.34.0"
+clap = { version = "4.4.8", features = ["derive"] }
image = "0.24.7"
rand = "0.8.5"
rand_pcg = "0.3.1"
diff --git a/src/main.rs b/src/main.rs
index e9df476..204a582 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,8 @@ use crate::frontier::mean::MeanFrontier;
use crate::frontier::min::MinFrontier;
use crate::frontier::Frontier;
-use clap::{self, clap_app, crate_authors, crate_name, crate_version};
+use clap::{ArgAction, CommandFactory, Parser, ValueEnum};
+use clap::error::ErrorKind;
use image::{self, ColorType, ImageEncoder, ImageError, Rgba, RgbaImage};
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
@@ -24,7 +25,6 @@ use std::error::Error;
use std::io::{self, BufWriter, IsTerminal, Write};
use std::path::PathBuf;
use std::process::exit;
-use std::str::FromStr;
use std::time::Instant;
/// The color source specified on the command line.
@@ -50,29 +50,107 @@ enum OrderArg {
}
/// The frontier implementation.
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
enum FrontierArg {
/// Pick a neighbor of the closest pixel so far.
Min,
/// Pick the pixel with the closest mean color of all its neighbors.
Mean,
/// Target the closest pixel on an image.
+ #[value(skip)]
Image(PathBuf),
}
/// The color space to operate in.
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ColorSpaceArg {
/// sRGB space.
+ #[value(name = "RGB")]
Rgb,
/// CIE L*a*b* space.
+ #[value(name = "Lab")]
Lab,
/// CIE L*u*v* space.
+ #[value(name = "Luv")]
Luv,
/// Oklab space.
+ #[value(name = "Oklab")]
Oklab,
}
+/// k-d forests.
+#[derive(Debug, Parser)]
+#[command(author, version, about, disable_help_flag = true)]
+struct Cli {
+ /// Use all <DEPTH>-bit colors.
+ #[arg(short, long, group = "source", value_name = "DEPTH", default_value = "24")]
+ bit_depth: Option<String>,
+ /// use colors from the <INPUT> image.
+ #[arg(short, long, group = "source", value_name = "INPUT")]
+ input: Option<PathBuf>,
+
+ /// Sort colors by hue [default].
+ #[arg(short = 's', long, group = "order", default_value_t = true)]
+ hue_sort: bool,
+ /// Randomize colors.
+ #[arg(short, long, group = "order")]
+ random: bool,
+ /// Place colors in Morton order (Z-order).
+ #[arg(short = 'M', long, group = "order")]
+ morton: bool,
+ /// Place colors in Hilbert curve order
+ #[arg(short = 'H', long, group = "order")]
+ hilbert: bool,
+
+ /// Reduce artifacts by iterating through the colors in multiple stripes [default].
+ #[arg(short = 't', long, group = "stripe?", default_value_t = true)]
+ stripe: bool,
+ /// Don't stripe.
+ #[arg(short = 'T', long, group = "stripe?")]
+ no_stripe: bool,
+
+ /// Specify the selection mode.
+ #[arg(short = 'l', long, group = "frontier", value_name = "MODE", default_value = "min")]
+ selection: FrontierArg,
+ /// Place colors on the closest pixels of the <TARGET> image.
+ #[arg(short = 'g', long, group = "frontier", value_name = "TARGET")]
+ target: Option<PathBuf>,
+
+ /// Use the given color space.
+ #[arg(short, long, value_name = "SPACE", default_value = "Lab")]
+ color_space: ColorSpaceArg,
+
+ /// The width of the generated image.
+ #[arg(short, long)]
+ width: Option<u32>,
+ /// The height of the generated image.
+ #[arg(short, long)]
+ height: Option<u32>,
+
+ /// The x coordinate of the first pixel.
+ #[arg(short, value_name = "X")]
+ x0: Option<u32>,
+ /// The y coordinate of the first pixel.
+ #[arg(short, value_name = "Y")]
+ y0: Option<u32>,
+
+ /// Generate frames of an animation.
+ #[arg(short, long)]
+ animate: bool,
+
+ /// Save the image to <PATH>.
+ #[arg(short, long, value_name = "PATH", default_value = "kd-forest.png")]
+ output: PathBuf,
+
+ /// Seed the random number generator.
+ #[arg(short = 'e', long, default_value_t = 0)]
+ seed: u64,
+
+ /// Print help.
+ #[arg(short = '?', long, action = ArgAction::Help)]
+ help: (),
+}
+
/// Error type for this app.
#[derive(Debug)]
enum AppError {
@@ -83,10 +161,7 @@ enum AppError {
impl AppError {
/// Create an error for an invalid argument.
fn invalid_value(msg: &str) -> Self {
- Self::ArgError(clap::Error::with_description(
- msg,
- clap::ErrorKind::InvalidValue,
- ))
+ Self::ArgError(Cli::command().error(ErrorKind::InvalidValue, msg))
}
/// Exit the program with this error.
@@ -128,19 +203,6 @@ impl From<rand::Error> for AppError {
/// Result type for this app.
type AppResult<T> = Result<T, AppError>;
-/// Parse an argument into the appropriate type.
-fn parse_arg<F>(arg: Option<&str>) -> AppResult<Option<F>>
-where
- F: FromStr,
- F::Err: Error,
-{
- match arg.map(|s| s.parse()) {
- Some(Ok(f)) => Ok(Some(f)),
- Some(Err(e)) => Err(AppError::invalid_value(&e.to_string())),
- None => Ok(None),
- }
-}
-
/// The parsed command line arguments.
#[derive(Debug)]
struct Args {
@@ -160,49 +222,14 @@ struct Args {
impl Args {
fn parse() -> AppResult<Self> {
- let args = clap_app!((crate_name!()) =>
- (version: crate_version!())
- (author: crate_authors!())
- (@setting ColoredHelp)
- (@setting DeriveDisplayOrder)
- (@setting UnifiedHelpMessage)
- (@group source =>
- (@arg DEPTH: -b --("bit-depth") +takes_value "Use all DEPTH-bit colors")
- (@arg INPUT: -i --input +takes_value "Use colors from the INPUT image")
- )
- (@group order =>
- (@arg HUE: -s --hue-sort "Sort colors by hue [default]")
- (@arg RANDOM: -r --random "Randomize colors")
- (@arg MORTON: -M --morton "Place colors in Morton order (Z-order)")
- (@arg HILBERT: -H --hilbert "Place colors in Hilbert curve order")
- )
- (@group stripe =>
- (@arg STRIPE: -t --stripe "Reduce artifacts by iterating through the colors in multiple stripes [default]")
- (@arg NOSTRIPE: -T --("no-stripe") "Don't stripe")
- )
- (@group frontier =>
- (@arg MODE: -l --selection +takes_value possible_value[min mean] "Specify the selection mode")
- (@arg TARGET: -g --target +takes_value "Place colors on the closest pixels of the TARGET image")
- )
- (@arg SPACE: -c --("color-space") default_value("Lab") possible_value[RGB Lab Luv Oklab] "Use the given color space")
- (@arg WIDTH: -w --width +takes_value "The width of the generated image")
- (@arg HEIGHT: -h --height +takes_value "The height of the generated image")
- (@arg X: -x +takes_value "The x coordinate of the first pixel")
- (@arg Y: -y +takes_value "The y coordinate of the first pixel")
- (@arg ANIMATE: -a --animate "Generate frames of an animation")
- (@arg PATH: -o --output default_value("kd-forest.png") "Save the image to PATH")
- (@arg SEED: -e --seed default_value("0") "Seed the random number generator")
- )
- .get_matches_safe()?;
+ let args = Cli::try_parse()?;
- let source = if let Some(input) = args.value_of("INPUT") {
- SourceArg::Image(PathBuf::from(input))
+ let source = if let Some(input) = args.input {
+ SourceArg::Image(input)
} else {
- let arg = args.value_of("DEPTH");
+ let arg = args.bit_depth.unwrap();
let depths: Vec<_> = arg
- .iter()
- .map(|s| s.split(','))
- .flatten()
+ .split(',')
.map(|n| n.parse().ok())
.collect();
@@ -216,62 +243,50 @@ impl Args {
_ => {
return Err(AppError::invalid_value(
- &format!("invalid bit depth {}", arg.unwrap()),
+ &format!("invalid bit depth {}", arg),
));
}
};
if r > 8 || g > 8 || b > 8 {
return Err(AppError::invalid_value(
- &format!("bit depth of {} is too deep!", arg.unwrap()),
+ &format!("bit depth of {} is too deep!", arg),
));
}
SourceArg::AllRgb(r, g, b)
};
- let order = if args.is_present("RANDOM") {
+ let order = if args.random {
OrderArg::Random
- } else if args.is_present("MORTON") {
+ } else if args.morton {
OrderArg::Morton
- } else if args.is_present("HILBERT") {
+ } else if args.hilbert {
OrderArg::Hilbert
} else {
OrderArg::HueSort
};
- let stripe = !args.is_present("NOSTRIPE") && order != OrderArg::Random;
+ let stripe = !args.no_stripe && order != OrderArg::Random;
- let frontier = if let Some(target) = args.value_of("TARGET") {
- FrontierArg::Image(PathBuf::from(target))
+ let frontier = if let Some(target) = args.target {
+ FrontierArg::Image(target)
} else {
- match args.value_of("MODE") {
- Some("min") | None => FrontierArg::Min,
- Some("mean") => FrontierArg::Mean,
- _ => unreachable!(),
- }
+ args.selection
};
- let space = match args.value_of("SPACE").unwrap() {
- "RGB" => ColorSpaceArg::Rgb,
- "Lab" => ColorSpaceArg::Lab,
- "Luv" => ColorSpaceArg::Luv,
- "Oklab" => ColorSpaceArg::Oklab,
- _ => unreachable!(),
- };
+ let space = args.color_space;
- let width = parse_arg(args.value_of("WIDTH"))?;
- let height = parse_arg(args.value_of("HEIGHT"))?;
- let x0 = parse_arg(args.value_of("X"))?;
- let y0 = parse_arg(args.value_of("Y"))?;
+ let width = args.width;
+ let height = args.height;
+ let x0 = args.x0;
+ let y0 = args.y0;
- let animate = args.is_present("ANIMATE");
+ let animate = args.animate;
- let output = args.value_of("PATH")
- .map(PathBuf::from)
- .unwrap();
+ let output = args.output;
- let seed = parse_arg(args.value_of("SEED"))?.unwrap_or(0);
+ let seed = args.seed;
Ok(Self {
source,