image-collage 1.0.0

Create beautiful 2x2 image collages with a blazing-fast Rust CLI and TUI
Documentation
use anyhow::{Context, Result};
use clap::Parser;
use image_collage::{
    collage::{create_collage, load_and_process_image},
    tui::{self, TuiConfig},
};
use std::path::PathBuf;

#[derive(Parser)]
#[command(
    name = "image-collage",
    version,
    author,
    about = "Create beautiful 2x2 image collages with ease",
    long_about = r#"
image-collage - Create beautiful 2x2 image collages with ease

This tool allows you to create a 2x2 grid collage from 4 images, with
customizable borders and sizing. Images are automatically center-cropped
to squares and resized for perfect alignment.

MODES:
  CLI Mode:  Provide 4 image paths as arguments
  TUI Mode:  Run without arguments for interactive file browser

VISUAL LAYOUT:
  The collage arranges your images in a 2x2 grid:

    ┌─────────┬─────────┐
    │ Image 1 │ Image 2 │
    ├─────────┼─────────┤
    │ Image 3 │ Image 4 │
    └─────────┴─────────┘

  With borders (--border 10):

    ┌─────────────────────────┐
    │ ┌───────┐   ┌───────┐   │
    │ │  Img1 │   │  Img2 │   │
    │ └───────┘   └───────┘   │
    │                         │
    │ ┌───────┐   ┌───────┐   │
    │ │  Img3 │   │  Img4 │   │
    │ └───────┘   └───────┘   │
    └─────────────────────────┘

EXAMPLES:
  # CLI mode with 4 images
  image-collage photo1.jpg photo2.png photo3.gif photo4.bmp

  # CLI mode with custom output and border
  image-collage img1.jpg img2.jpg img3.jpg img4.jpg -o my_collage.jpg -b 5

  # CLI mode with 800x800 images and thick border
  image-collage *.jpg --size 800 --border 20 --output large_collage.jpg

  # Interactive TUI mode (launches file browser)
  image-collage

  # TUI mode with custom settings
  image-collage --output ~/Desktop/collage.jpg --size 600 --quality 100

TUI CONTROLS:
  j/↓     Move down in file list
  k/↑     Move up in file list
  l/Enter Select/deselect image or enter directory
  h/←     Go to parent directory
  c       Create collage (when 4 images selected)
  q       Quit without creating

SUPPORTED FORMATS:
  Input:  JPEG, PNG, GIF, BMP, WebP, TIFF
  Output: Format determined by file extension (default: JPEG)

NOTES:
  • Images are automatically center-cropped to squares
  • Border color is always white (#FFFFFF)
  • Quality setting only affects JPEG output
  • In TUI mode, selected images are marked with ✓
"#
)]
struct Args {
    #[arg(help = "First image path (leave empty for interactive mode)")]
    image1: Option<PathBuf>,
    
    #[arg(help = "Second image path")]
    image2: Option<PathBuf>,
    
    #[arg(help = "Third image path")]
    image3: Option<PathBuf>,
    
    #[arg(help = "Fourth image path")]
    image4: Option<PathBuf>,
    
    #[arg(
        short,
        long,
        default_value = "collage.jpg",
        help = "Output file path",
        long_help = "Output file path. Format is determined by extension (e.g., .jpg, .png)"
    )]
    output: PathBuf,
    
    #[arg(
        short,
        long,
        default_value_t = 2,
        help = "Border width in pixels",
        long_help = "Width of the white border between images in pixels (0 for no border)"
    )]
    border: u32,
    
    #[arg(
        short,
        long,
        default_value_t = 400,
        help = "Size of each image in pixels",
        long_help = "Size (width and height) of each image in the grid. Final collage will be (size*2 + border) pixels"
    )]
    size: u32,
    
    #[arg(
        short,
        long,
        default_value_t = 95,
        value_parser = clap::value_parser!(u8).range(1..=100),
        help = "JPEG quality (1-100)",
        long_help = "JPEG compression quality from 1 (worst) to 100 (best). Only affects JPEG output"
    )]
    quality: u8,
}

fn main() -> Result<()> {
    let args = Args::parse();
    
    if args.image1.is_none() {
        let config = TuiConfig {
            output: args.output,
            border: args.border,
            size: args.size,
            quality: args.quality,
        };
        tui::run(config)?;
    } else if let (Some(img1), Some(img2), Some(img3), Some(img4)) = 
        (args.image1.as_ref(), args.image2.as_ref(), args.image3.as_ref(), args.image4.as_ref()) {
        
        println!("Loading images...");
        let images = vec![
            load_and_process_image(img1, args.size)?,
            load_and_process_image(img2, args.size)?,
            load_and_process_image(img3, args.size)?,
            load_and_process_image(img4, args.size)?,
        ];
        
        println!("Creating collage...");
        let collage = create_collage(images, args.size, args.border);
        
        println!("Saving to {}...", args.output.display());
        collage
            .save(&args.output)
            .with_context(|| format!("Failed to save output to {}", args.output.display()))?;
        
        println!("✓ Collage created successfully!");
    } else {
        anyhow::bail!("Please provide either no images (for interactive mode) or all 4 images");
    }
    
    Ok(())
}