Perceptual image diffing CLI that outputs structured JSON. Built for AI agents that need to compare screenshots in iterative loops — compact output by default to minimise context window usage.
npm install -g agent-image-diffcargo install agent-image-diffagent-image-diff baseline.png candidate.png
# {"match":false,"diff_percentage":1.0,"regions":[{"id":1,"bounding_box":{"x":20,"y":30,"width":10,"height":10},"label":"color-change"}]}
agent-image-diff expected.png actual.png
# {"match":true,"diff_percentage":0.0,"regions":[]}
agent-image-diff baseline.png candidate.png -v --pretty # All diagnostic fields, formatted
agent-image-diff baseline.png candidate.png -o diff.png # Write visual diff image
agent-image-diff baseline.png candidate.png -q -o diff.png # Diff image only, no stdoutOptimised for agents. Single line, only actionable fields.
| Field | Type | Description |
|---|---|---|
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 |
Adds diagnostic fields for debugging. Not recommended for agent loops.
agent-image-diff baseline.png candidate.png -v --pretty{
"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.
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
Suppresses all stdout. Still writes the diff image if -o is provided.
| Label | Meaning |
|---|---|
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) |
Generate a diff image alongside any output format:
agent-image-diff baseline.png candidate.png -o diff.pngThe diff image shows the candidate with semi-transparent colour overlays and borders on each changed region.
| Flag | Default | Description |
|---|---|---|
-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 |
# Strict pixel-exact comparison (CI screenshot tests)
agent-image-diff baseline.png candidate.png -t 0.0 --denoise 0
# Web UI screenshots (tolerant of font rendering noise)
agent-image-diff baseline.png candidate.png
# Detailed debugging (all fields, formatted)
agent-image-diff baseline.png candidate.png -v --pretty
# Diff image only (no JSON)
agent-image-diff baseline.png candidate.png -q -o diff.pnguse 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,
);
}
}- Compare — Per-pixel YIQ perceptual colour distance with configurable threshold
- Anti-alias filter — Detects and excludes anti-aliased edge pixels
- Denoise — Removes small noise clusters (font rendering artifacts, compression noise)
- Dilate — Optional morphological expansion to bridge nearby changed pixels
- Cluster — Connected component labelling groups changed pixels into regions
- Merge — Combines regions within
--merge-distancepixels of each other - Classify — Labels each region as
added,removed,color-change, orcontent-change