# agent-image-diff
Perceptual image diffing CLI that outputs structured JSON. Built for LLM agents that need to compare screenshots in iterative loops — compact output by default to minimise context window usage.
## Install
```bash
npm install -g agent-image-diff
```
Or with Cargo:
```bash
cargo install agent-image-diff
```
## Quick Start
Compare two images:
```bash
agent-image-diff baseline.png candidate.png
```
```json
{"match":false,"diff_percentage":1.0,"regions":[{"id":1,"bounding_box":{"x":20,"y":30,"width":10,"height":10},"label":"color-change"}]}
```
Images match:
```bash
agent-image-diff expected.png actual.png
```
```json
{"match":true,"diff_percentage":0.0,"regions":[]}
```
## Output Schema
### Default (compact)
Optimised for agents. Single line, only actionable fields.
| `match` | `bool` | `true` if images are identical (no regions detected) |
| `diff_percentage` | `float` | Percentage of pixels that differ, rounded to 1 decimal place |
| `regions` | `array` | List of changed regions, largest first |
| `regions[].id` | `int` | Region identifier (1-indexed) |
| `regions[].bounding_box` | `object` | `{x, y, width, height}` in pixels from top-left |
| `regions[].label` | `string` | One of: `added`, `removed`, `color-change`, `content-change` |
| `dimension_mismatch` | `object?` | Present only when images have different dimensions |
### Verbose (`-v`)
Adds diagnostic fields for debugging. Not recommended for agent loops.
```bash
agent-image-diff baseline.png candidate.png -v --pretty
```
```json
{
"dimensions": { "width": 100, "height": 100 },
"stats": {
"changed_pixels": 100,
"total_pixels": 10000,
"diff_percentage": 1.0,
"region_count": 1,
"antialiased_pixels": 0
},
"match": false,
"regions": [
{
"id": 1,
"bounding_box": { "x": 20, "y": 30, "width": 10, "height": 10 },
"pixel_count": 100,
"avg_delta": 0.628,
"max_delta": 0.628,
"label": "color-change"
}
]
}
```
Additional verbose fields: `dimensions`, `stats.*`, `regions[].pixel_count`, `regions[].avg_delta`, `regions[].max_delta`.
### Summary (`-f summary`)
Human-readable text output.
```
Images differ.
Dimensions: 100x100
Changed pixels: 100 (1.00%)
Regions: 1
Region #1 [color-change]: 10x10 at (20,30) — 100 pixels, avg delta 0.628, max delta 0.628
```
### Quiet (`-q`)
Suppresses all stdout. Still writes the diff image if `-o` is provided.
## Region Labels
| `added` | Content present in candidate but not baseline (new element appeared) |
| `removed` | Content present in baseline but not candidate (element disappeared) |
| `color-change` | Same structure, different colors (theme change, hover state) |
| `content-change` | Structure differs (text changed, layout shifted) |
## Visual Diff Image
Generate a diff image alongside any output format:
```bash
agent-image-diff baseline.png candidate.png -o diff.png
```
The diff image shows the candidate with semi-transparent colour overlays and borders on each changed region.
## Parameters
| `-t, --threshold` | `0.1` | Colour difference sensitivity (0.0 = exact, 1.0 = match everything) |
| `--denoise` | `25` | Remove noise clusters smaller than N pixels before analysis |
| `--dilate` | `0` | Expand diff mask by N pixels to bridge nearby changes |
| `--merge-distance` | `50` | Merge regions within N pixels of each other |
| `--min-region-size` | `25` | Filter out regions smaller than N pixels |
| `--connectivity` | `8` | Pixel connectivity for clustering: `4` (cross) or `8` (with diagonals) |
| `--detect-antialias` | `true` | Ignore anti-aliased edge pixels |
| `-f, --format` | `json` | Output format: `json`, `summary`, `image` |
| `-o, --output` | — | Write visual diff image to path |
| `-v, --verbose` | off | Include all diagnostic fields in JSON |
| `--pretty` | off | Pretty-print JSON |
| `-q, --quiet` | off | Suppress stdout |
## Recipes
**Strict pixel-exact comparison** (CI screenshot tests):
```bash
agent-image-diff baseline.png candidate.png -t 0.0 --denoise 0
```
**Web UI screenshots** (tolerant of font rendering noise):
```bash
agent-image-diff baseline.png candidate.png
```
**Detailed debugging** (all fields, formatted):
```bash
agent-image-diff baseline.png candidate.png -v --pretty
```
**Diff image only** (no JSON):
```bash
agent-image-diff baseline.png candidate.png -q -o diff.png
```
## Library Usage
```rust
use agent_image_diff::{diff_images, DiffOptions};
let baseline = image::open("baseline.png").unwrap().to_rgba8();
let candidate = image::open("candidate.png").unwrap().to_rgba8();
let result = diff_images(&baseline, &candidate, DiffOptions::default());
if !result.is_match {
for region in &result.regions {
println!("Region {} [{}]: {}x{} at ({},{})",
region.id, region.label,
region.bounding_box.width, region.bounding_box.height,
region.bounding_box.x, region.bounding_box.y,
);
}
}
```
## How It Works
1. **Compare** — Per-pixel YIQ perceptual colour distance with configurable threshold
2. **Anti-alias filter** — Detects and excludes anti-aliased edge pixels
3. **Denoise** — Removes small noise clusters (font rendering artifacts, compression noise)
4. **Dilate** — Optional morphological expansion to bridge nearby changed pixels
5. **Cluster** — Connected component labelling groups changed pixels into regions
6. **Merge** — Combines regions within `--merge-distance` pixels of each other
7. **Classify** — Labels each region as `added`, `removed`, `color-change`, or `content-change`