Building a Tiny Rust Screenshot Diff CLI
A practical look at RustScreenDiffAI, a small Rust CLI for screenshot diffs, JSON reports, thresholds, and visual review artifacts.
On this page
Visual regression testing does not always need a giant platform.
Sometimes I want a boring, local, predictable command that answers one question: did this screenshot change enough that I should look at it? That is the shape of RustScreenDiffAI. It is a small Rust CLI that compares two images, counts changed pixels, applies a threshold, and returns a pass or fail verdict. In v1.1.0 I added the missing piece that makes it more useful in real reviews: a generated diff image.
The goal is not to replace full design QA tools. The goal is to give a solo developer a fast checkpoint that can run locally, in scripts, or as part of a release checklist without dragging in a complicated service.
The Problem
Screenshots are useful because they capture the real rendered result. They are also noisy. A small antialiasing shift, a font rendering difference, or a one-pixel movement can create change even when the product is fine.
That means a screenshot comparison tool needs three things:
- A direct comparison between a baseline image and a new image.
- A threshold so tiny changes do not block useful work.
- A review artifact so a human can see what changed.
RustScreenDiffAI keeps those requirements deliberately narrow. It compares pixels, calculates the changed percentage, and tells you whether the change is inside your threshold.
screendiff compare examples/before.ppm examples/after.ppm --threshold 0.10That command says: compare these two images and pass if the changed pixel ratio stays under 10 percent.
The text report stays readable:
ScreenDiff Report
Before: examples/before.ppm
After: examples/after.ppm
Total Pixels: 16
Diff Pixels: 1
Diff: 6.25%
Threshold: 10.00%
Verdict: PASSFor many projects, that is already enough to catch accidental layout changes before shipping.
Adding Diff Images
Text output is good for automation, but it is not always good for human review. A percentage tells me something changed. A highlighted diff tells me where.
The v1.1.0 release adds
--diff-outputscreendiff compare before.png after.png --threshold 0.01 --diff-output diff.pngThe implementation is intentionally simple. If a pixel differs, the output image marks it with a strong highlight color. If it does not differ, it keeps a neutral representation from the comparison.
pub fn write_diff_image(before_path: &str, after_path: &str, output_path: &str) -> Result<()> {
let before = image::open(before_path)?;
let after = image::open(after_path)?;
let before_rgb = before.to_rgb8();
let after_rgb = after.to_rgb8();
if before_rgb.dimensions() != after_rgb.dimensions() {
return Err(ScreenDiffError::DimensionMismatch.into());
}
let mut diff = before_rgb.clone();
for (x, y, pixel) in diff.enumerate_pixels_mut() {
if before_rgb.get_pixel(x, y) != after_rgb.get_pixel(x, y) {
*pixel = image::Rgb([255, 0, 255]);
}
}
diff.save(output_path)?;
Ok(())
}That is the kind of code I like in small release tools: obvious behavior, clear failure paths, and no mystery in the result.
JSON For Scripts
A CLI becomes more useful when it can speak both human and machine. RustScreenDiffAI supports JSON output so another script can record the result, fail a release gate, or attach the summary somewhere else.
screendiff compare before.png after.png --threshold 0.01 --jsonExample output:
{
"before": "before.png",
"after": "after.png",
"total_pixels": 10000,
"diff_pixels": 42,
"diff_percent": 0.0042,
"threshold": 0.01,
"verdict": "Pass"
}This matters because the same tool can support two workflows. In a terminal, I can read the report. In automation, I can parse the JSON and make a decision without scraping text.
Thresholds That Match The Risk
The threshold is where screenshot diffing becomes practical. A zero threshold is useful when you expect exact output, but most UI work benefits from a little tolerance.
| Threshold | Good For | Tradeoff |
|---|---|---|
code | Exact image fixtures, generated assets | Very sensitive to tiny rendering changes |
code | Stable UI screenshots | Catches small layout drift |
code | Product screenshots with minor rendering noise | Good default for local checks |
code | Broad smoke tests | May miss subtle visual regressions |
For my own use, I start strict and loosen only when the project proves it needs it. The threshold should be a product decision, not a way to hide unstable output.
[!TIP] Keep one baseline folder and one current-output folder. That makes it easy to rerun the same comparison after a UI change and review only the files that moved.
Why Rust Fits This Tool
Rust is a good fit for this kind of CLI because the job is file-heavy, deterministic, and easy to make fast without adding much runtime complexity. The binary can stay small, the error handling can stay explicit, and the command can be dropped into a script without a long setup story.
The useful production behavior is mostly about being predictable:
- Return a clear non-zero exit when the diff fails the threshold.
- Report dimension mismatches instead of guessing.
- Support text output for humans and JSON output for automation.
- Produce a visual diff artifact when the review needs eyes.
Those pieces make the tool easier to trust. Trust is the real feature in a release helper.
Where It Fits In A Solo Release Flow
I do not want every small side project to require a big CI setup. For a solo developer, a practical release checklist can be more valuable than an overbuilt pipeline.
RustScreenDiffAI fits into a local release flow like this:
app-under-test export-screenshots ./screens/current
screendiff compare ./screens/baseline/home.png ./screens/current/home.png --threshold 0.01 --diff-output ./screens/diffs/home.png
screendiff compare ./screens/baseline/settings.png ./screens/current/settings.png --threshold 0.01 --jsonIf something fails, I can open the diff image, decide whether the change is expected, and either update the baseline or fix the UI. That keeps the tool in the right role: it catches surprises, but it does not replace judgment.
Key Takeaways
- A small screenshot diff CLI can be enough for real visual regression checks.
- Thresholds turn pixel comparison from noisy trivia into a useful release signal.
- JSON output makes the tool scriptable without making the human report worse.
- Diff images are the difference between "something changed" and "I can review this quickly."
- For solo projects, boring local tools can be better than a large workflow you do not actually maintain.
RustScreenDiffAI v1.1.0 is intentionally small. It compares images, explains the result, and now gives you a visual artifact when the change needs review. That is the whole point: one sharp tool for one release problem.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
