use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Parser, Debug)]
#[command(
name = "pixl",
version,
about = "Local pixel-art generator for Apple Silicon: SDXL + pixel-art LoRA, snapped to true pixel art.",
args_conflicts_with_subcommands = true
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>,
#[command(flatten)]
pub generate: GenerateArgs,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Gen(GenerateArgs),
Pixelize(PixelizeArgs),
Models {
#[command(subcommand)]
action: ModelsCmd,
},
View(ViewArgs),
}
#[derive(Subcommand, Debug)]
pub enum ModelsCmd {
Ls,
Clear {
#[arg(long, default_value_t = false)]
yes: bool,
},
Path,
}
#[derive(ValueEnum, Clone, Copy, Debug, Default)]
pub enum ModelArg {
#[default]
Turbo,
Sdxl,
}
#[derive(Args, Debug, Clone)]
pub struct GenerateArgs {
#[arg(value_name = "ARGS", num_args = 0..)]
pub positional: Vec<String>,
#[arg(long, value_enum, default_value_t = ModelArg::Turbo)]
pub model: ModelArg,
#[arg(short = 'c', long, default_value_t = 16)]
pub colors: u16,
#[arg(long)]
pub pixel_size: Option<u32>,
#[arg(long, default_value_t = 8)]
pub steps: u32,
#[arg(long, default_value_t = 0)]
pub seed: u64,
#[arg(long, default_value_t = 1.0)]
pub cfg: f32,
#[arg(long, default_value = "512x512")]
pub size: String,
#[arg(long, default_value_t = false)]
pub no_postprocess: bool,
#[arg(long, default_value_t = false)]
pub no_lora: bool,
#[arg(long, default_value_t = 1.0)]
pub lora_weight: f32,
#[arg(short = 'j', long, default_value_t = 0)]
pub jobs: usize,
#[arg(long, default_value_t = false)]
pub json: bool,
#[arg(long, default_value_t = false)]
pub quiet: bool,
#[arg(long, default_value_t = false)]
pub fail_fast: bool,
#[arg(long = "low-prio", visible_aliases = ["bg", "background"], default_value_t = false)]
pub low_prio: bool,
#[arg(long, default_value_t = false)]
pub view: bool,
#[arg(long, default_value_t = false)]
pub no_view: bool,
#[arg(long)]
pub saved_dir: Option<PathBuf>,
}
#[derive(Args, Debug, Clone)]
pub struct PixelizeArgs {
#[arg(required = true)]
pub inputs: Vec<PathBuf>,
#[arg(short = 'o', long)]
pub out: Option<PathBuf>,
#[arg(short = 'c', long, default_value_t = 16)]
pub colors: u16,
#[arg(long)]
pub pixel_size: Option<u32>,
#[arg(long, default_value_t = 128)]
pub target_cells: u32,
#[arg(long, default_value_t = 1)]
pub scale: u32,
}
#[derive(Args, Debug, Clone)]
pub struct ViewArgs {
pub dir: PathBuf,
#[arg(long)]
pub saved_dir: Option<PathBuf>,
}
pub fn parse_size(s: &str) -> Result<(u32, u32), String> {
let s = s.trim().to_lowercase();
let (w, h) = if let Some((w, h)) = s.split_once('x') {
(
w.trim()
.parse()
.map_err(|_| format!("bad width in {s:?}"))?,
h.trim()
.parse()
.map_err(|_| format!("bad height in {s:?}"))?,
)
} else {
let n = s.parse().map_err(|_| format!("bad size {s:?}"))?;
(n, n)
};
if w == 0 || h == 0 || w % 8 != 0 || h % 8 != 0 {
return Err(format!(
"size must be positive and divisible by 8, got {w}x{h}"
));
}
Ok((w, h))
}