image-compare
SkillFiles & storageCompare two image files on disk and report what changed, region by region. Trigger on "what changed between these two images", "diff these PNGs", "did this render drift", "compare screenshots/exports/renders".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the image-compare skill
What this skill tells your AI
The instructions your AI receives, as published by teimurjan/blazediff in skill/image-compare/SKILL.md and read by ahel’s review.
Two image files in, a verdict out. No baselines, no manifest, no browser.
CLI binary is blazediff-cli (npm i -g @blazediff/cli).
Not this skill: capturing a running app, managing baselines, or re-running a
visual regression suite → use the blazediff skill instead (it owns
.blazediff/, blazediff-agent, and the check/rewrite loop). If the working
directory has a .blazediff/manifest.json and the user is asking about routes
rather than files, you are in the wrong skill.
Be terse
- Pass
--jsonon every call; parse fields. Do not echo CLI output. - Never send whole images to a model when
interprethas located the regions — see Send crops, not pages below. This is the point of the skill. - One final summary line:
N regions | <severity> | <one-clause verdict>.
Pick the command by the question
| The user is asking | Command | Read from it |
|---|---|---|
| "what changed?" / "describe the differences" | interpret | regions[] — bbox, changeType, position |
| "are these identical?" | core-native | different pixels (0 = byte-identical after decode) |
| "how different, perceptually?" | ssim | SSIM score, 1.0 = identical |
| "did quality degrade?" (compression, resize, generative output) | msssim | multi-scale score, 1.0 = identical |
| "where are the structural differences?" | gmsd -o map.png | deviation, 0.0 = identical |
interpret is the default. The others answer how much; only interpret
answers what.
blazediff-cli interpret a.png b.png --json
Exit codes: 0 no regions, 1 regions found, 2 error. Safe to branch on.
Picking --source
pixel(default) — pixel-aligned renders: screenshots, PNG exports, chart renders, PDF rasterizations. Anything produced twice by the same renderer.ms-ssim/ssim— images with resampling or compression noise: JPEGs, camera captures, generative-model output, anything that went through a lossy round-trip. Per-pixel deltas are everywhere in these, sopixelreturns one giant region; the metric sources threshold a similarity map instead. Their boxes are coarser (map-grid resolution), but the pixel counts inside them are still exact.
Add -a when the pair is text-heavy and the only differences are antialiasing
fringes. Raise -t (default 0.1) when the renderer has known per-run jitter.
Reading the result
{
"summary": "Moderate visual change detected (1.87% of image, 4 regions).",
"severity": "medium", // low | medium | high
"diffCount": 30421, // changed pixels
"diffPercentage": 1.87,
"totalRegions": 4,
"width": 1328, "height": 1228,
"regions": [{ "bbox": {...}, "changeType": "...", "position": "...", "shape": "...",
"pixelCount": 0, "percentage": 0, "confidence": 0 /* + stats blocks */ }]
}
changeType — addition, deletion, content-change, color-change,
shift, rendering-noise.
position — top-left…bottom-right, top/bottom/left/right/center.
shape — solid-region, mixed-region, contour-frame, sparse-distributed,
edge-dominated.
Each region also carries chroma, colorDelta, gradient, shapeStats, and
signals blocks. Do not put these in your context or forward them to a
model — they are classifier inputs, not answers. Project each region down to
{changeType, position, bbox, percentage} before you reason about it. The full
JSON for a 4-region diff is ~7.5 KB; the projection is ~200 tokens.
Interpreting changeType:
rendering-noise— subpixel/antialiasing jitter. Report as "no meaningful change" unless the user is specifically hunting rendering differences.color-changewithsignals.lowColorDeltaandgradient.edgeCorrelationnear 1.0 — same content, recoloured. Theme change, not content change.shift— content moved, not edited. Say what moved and by roughly how much; don't describe it as added and removed.addition/deletion/content-change— genuine content differences. These are the ones worth cropping and reading.
Send crops, not pages
When the user wants each change described (not just located), crop the regions and send only those. Never send the two full images.
Two reasons, and the second matters more than the first:
- Cost. A pair of full-page screenshots is thousands of visual tokens, and nearly all of them show pixels that are identical in both images.
- Legibility. Claude downscales any image that exceeds the model's limits. A full-page screenshot gets crushed by ~6×, which destroys the exact detail you are trying to compare — and images over 8000 px on an edge are rejected outright.
Measured on this repo's fixtures (Claude Opus 5, high-resolution tier:
2576 px max edge, 4784 max visual tokens; cost is ⌈w/28⌉ × ⌈h/28⌉ per image):
A: fixtures/page — 3598×16384 page, 2 regions, 0.09% px | B: fixtures/blazediff/3 — 1328×1228, 4 regions, 1.87% px | |
|---|---|---|
| Both full images, as-is | rejected — 16384 px exceeds the 8000 px limit | 2112 ×2 = 4224 tokens |
| Both pre-resized to fit | 566×2576 → 1932 ×2 = 3864 tokens — but the 711×104 changed region is now 112×16 px, and the 212×42 one is 33×7 px. Unreadable. | n/a, already fits |
| Crops + projected region JSON | 408 + ~128 = 536 | 1178 + ~206 = 1384 |
| 7.2× cheaper, at native resolution | 3.1× cheaper (−67%) |
The saving scales with how little of the image changed, which for real diffs is almost always "very little". Case A changed 0.09% of its pixels; sending both pages would have spent ~99.9% of the budget on identical content.
The crop step
Pad each bbox by 32 px so the change has surrounding context, clamp to the image bounds, and crop the same box from both images — the pair only reads as a before/after if the framing is identical.
# one region, both sides. x/y/w/h come from regions[i].bbox, padded and clamped.
node -e '
const sharp = require("sharp"), [src, out, x, y, w, h] = process.argv.slice(1);
sharp(src).extract({left:+x, top:+y, width:+w, height:+h}).toFile(out);
' "$SRC" "$OUT" "$X" "$Y" "$W" "$H"
sharp ships with @blazediff/cli via @blazediff/codec-sharp. If it does not
resolve (global install, no local node_modules), fall back to sips -c on
macOS or magick crop where ImageMagick is present.
Then send, per region, in one message: the region's projected JSON, its before
crop, its after crop. Label them — Region 1 (content-change, bottom): before / after — so follow-up questions can name a region without resending anything.
When not to crop
Send the full (resized) pair instead when any of these hold:
totalRegionsis 0 — nothing to crop; answer from the metric score.- The regions cover most of the frame (
diffPercentageabove ~25%, or one region spanning most of the bbox area). A full-frame rewrite is best read whole. - Summed crop tokens exceed the full-pair cost. With many small scattered regions, per-crop 28-px padding overhead adds up — compute both, pick the smaller.
- The question is about layout or global composition rather than local content. Crops destroy spatial relationships between regions.
Answering
Lead with the verdict, then the regions, in the user's terms:
4 regions changed (medium).
1. content-change, bottom (590×479 at 356,651) — <what the crops show>
2. addition, right (95×154 at 992,614) — <what the crops show>
...
If every region is rendering-noise, say the images are visually equivalent and
give the SSIM score as backing. Don't manufacture a difference to report.
Failure modes
- Different dimensions —
interpretrequires matching sizes. Say so and ask whether to resize or compare a common crop; don't silently resize, since that changes what "different" means. pixelreturns one region covering everything — the pair has resampling or compression noise. Re-run with--source ms-ssim.- Huge
diffCountbut the images look the same — check for an alpha or colour-profile difference; compare withssim(structure-only) to confirm. gmsdoutput labels are inverted — the CLI prints(0=different, 1=identical)and asimilarity: N%line, but GMSD is a deviation: identical images score0.000000. Read the number as distance, and ignore the printed label.
Signals
- GitHub stars
- 338
- Forks
- 3
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
image-compare- Source
- github.com/teimurjan/blazediff