imgdiff: add positional and frequency robustness parameters (#9822)

- Introduce `shiftRadius` to allow positional tolerances by searching
  a local neighborhood, absorbing sub-pixel shifts and MSAA quirks.
- Introduce `blurRadius` to apply local area averaging, ignoring
  high-frequency noise like hardware dithering.
- Enhance `ImageDiffResult` to include an `averageError` array and
  a 10-bin `errorHistogram` for actionable failure debugging.
- Update Android JNI bindings (`ImageDiff.java` and `ImageDiff.cpp`)
  to propagate the new error distribution statistics to Java callers.
- Update C++ unit tests to cover the new heuristic options.
- Document the new parameters and JSON result format in README.md.
- Add synthetic image generation tests in `tools/diffimg/tests/` to
  validate the CLI tool's handling of spatial shifts and dithering.
This commit is contained in:
Powei Feng
2026-03-24 17:31:18 -07:00
committed by GitHub
parent e22588c4e1
commit 541a4feae1
11 changed files with 457 additions and 75 deletions

View File

@@ -136,6 +136,52 @@ TEST_F(ImageDiffTest, GlobalFailureFraction) {
EXPECT_EQ(result.status, ImageDiffResult::Status::PASSED);
}
TEST_F(ImageDiffTest, ShiftRadius) {
LinearImage img1 = createImage(10, 10, 0.5f);
LinearImage img2 = createImage(10, 10, 0.5f);
// Shift the color by 1 pixel to the right in img2
setPixel(img1, 5, 5, 1.0f, 0.0f, 0.0f);
setPixel(img2, 6, 5, 1.0f, 0.0f, 0.0f);
ImageDiffConfig config;
config.maxAbsDiff = 0.05f;
// Strict comparison should fail
auto result = compare(img1, img2, config);
EXPECT_EQ(result.status, ImageDiffResult::Status::PIXEL_DIFFERENCE);
// With shiftRadius = 1, it should find the pixel nearby and pass
config.shiftRadius = 1;
result = compare(img1, img2, config);
EXPECT_EQ(result.status, ImageDiffResult::Status::PASSED);
}
TEST_F(ImageDiffTest, BlurRadius) {
LinearImage img1 = createImage(10, 10, 0.5f);
LinearImage img2 = createImage(10, 10, 0.5f);
// Dithering/Noise: add noise that averages out to 0
// img1 has 0.5 everywhere
// img2 has 0.6 and 0.4 alternating (average is 0.5)
setPixel(img2, 5, 5, 0.6f, 0.5f, 0.5f);
setPixel(img2, 6, 5, 0.4f, 0.5f, 0.5f);
setPixel(img2, 5, 6, 0.4f, 0.5f, 0.5f);
setPixel(img2, 6, 6, 0.6f, 0.5f, 0.5f);
ImageDiffConfig config;
config.maxAbsDiff = 0.05f;
// Strict comparison should fail because pixel diff is 0.1
auto result = compare(img1, img2, config);
EXPECT_EQ(result.status, ImageDiffResult::Status::PIXEL_DIFFERENCE);
// With blurRadius = 1, it compares the 3x3 average, which is much closer to 0.5
config.blurRadius = 1;
result = compare(img1, img2, config);
EXPECT_EQ(result.status, ImageDiffResult::Status::PASSED);
}
TEST_F(ImageDiffTest, JSONSerialization) {
ImageDiffConfig config;
config.mode = ImageDiffConfig::Mode::AND;