agent-image-diff 0.2.4

Structured image diff with JSON output for agent workflows
Documentation
use clap::{Parser, ValueEnum};
use std::path::PathBuf;

const HELP_TEMPLATE: &str = "\
{bin} - {about}

Usage: {usage}

Arguments:
  <baseline>                 Path to the baseline (expected) image
  [candidate]                Path to the candidate (actual) image (required for diff)

Output:
  -f, --format <format>      Output format: json, summary, image [default: json]
  -o, --output <path>        Write visual diff image (works alongside any format)
  -v, --verbose              Include all diagnostic fields in JSON
      --pretty               Pretty-print JSON output
  -q, --quiet                Suppress stdout; check JSON output for results

Tuning:
  -t, --threshold <float>    Color difference sensitivity, 0.0-1.0 [default: 0.1]
      --denoise <n>          Remove noise clusters smaller than N pixels [default: 25]
      --dilate <n>           Expand diff mask by N pixels [default: 0]
      --merge-distance <n>   Merge regions within N pixels of each other [default: 50]
      --min-region-size <n>  Filter out regions smaller than N pixels [default: 25]
      --connectivity <n>     Pixel connectivity: 4 (cross) or 8 (diagonals) [default: 8]
      --detect-antialias     Ignore anti-aliased edge pixels [default: true]

Crop:
      --crop                 Crop a region from the baseline image instead of diffing
      --x <n>                X coordinate of crop region (screenshot pixels)
      --y <n>                Y coordinate of crop region (screenshot pixels)
      --crop-width <n>       Width of crop region (screenshot pixels)
      --crop-height <n>      Height of crop region (screenshot pixels)

Options:
  -h, --help                 Print help
  -V, --version              Print version

Examples:
  {bin} baseline.png candidate.png
  {bin} baseline.png candidate.png -v --pretty
  {bin} baseline.png candidate.png -o diff.png
  {bin} baseline.png candidate.png -q -o diff.png
  {bin} screenshot.png --crop --x 100 --y 200 --crop-width 400 --crop-height 300 -o cropped.png";

#[derive(Parser)]
#[command(
    name = "agent-image-diff",
    version,
    about = "structured image diff with JSON output for agent workflows",
    help_template = HELP_TEMPLATE,
)]
pub struct Cli {
    /// Path to the baseline (expected) image
    pub baseline: PathBuf,

    /// Path to the candidate (actual) image (required for diff, unused in crop mode)
    pub candidate: Option<PathBuf>,

    /// Crop mode: extract a region from the baseline image instead of diffing
    #[arg(long)]
    pub crop: bool,

    /// X coordinate of crop region (screenshot pixels, requires --crop)
    #[arg(long, requires = "crop")]
    pub x: Option<u32>,

    /// Y coordinate of crop region (screenshot pixels, requires --crop)
    #[arg(long, requires = "crop")]
    pub y: Option<u32>,

    /// Width of crop region (screenshot pixels, requires --crop)
    #[arg(long, requires = "crop")]
    pub crop_width: Option<u32>,

    /// Height of crop region (screenshot pixels, requires --crop)
    #[arg(long, requires = "crop")]
    pub crop_height: Option<u32>,

    /// Output format (written to stdout)
    #[arg(short = 'f', long, default_value = "json", value_enum, hide = true)]
    pub format: OutputFormat,

    /// Path to write visual diff image (works alongside any output format)
    #[arg(short = 'o', long = "output", hide = true)]
    pub output: Option<PathBuf>,

    /// Color difference threshold (0.0 = exact match, 1.0 = everything matches)
    #[arg(short = 't', long, default_value = "0.1", hide = true)]
    pub threshold: f64,

    /// Enable anti-aliasing detection (reduces false positives at edges)
    #[arg(long, default_value = "true", hide = true)]
    pub detect_antialias: bool,

    /// Minimum region size in pixels (regions smaller than this are filtered out)
    #[arg(long, default_value = "25", hide = true)]
    pub min_region_size: u32,

    /// Connectivity for region clustering: 4 (cross) or 8 (including diagonals)
    #[arg(long, default_value = "8", hide = true)]
    pub connectivity: u8,

    /// Remove noise clusters smaller than this many pixels before dilation (0 = disabled)
    #[arg(long, default_value = "25", hide = true)]
    pub denoise: u32,

    /// Dilation radius in pixels (expands diff mask to merge nearby changes, 0 = disabled)
    #[arg(long, default_value = "0", hide = true)]
    pub dilate: u32,

    /// Max gap in pixels between regions to merge them (0 = disabled)
    #[arg(long, default_value = "50", hide = true)]
    pub merge_distance: u32,

    /// Include all diagnostic fields in JSON output (dimensions, pixel counts, deltas)
    #[arg(short = 'v', long, hide = true)]
    pub verbose: bool,

    /// Pretty-print JSON output (default is compact single-line)
    #[arg(long, hide = true)]
    pub pretty: bool,

    /// Suppress stdout output
    #[arg(short = 'q', long, hide = true)]
    pub quiet: bool,
}

#[derive(ValueEnum, Clone)]
pub enum OutputFormat {
    Json,
    Image,
    Summary,
}