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-25 00:31:18 +00:00
committed by GitHub
parent e22588c4e1
commit 541a4feae1
11 changed files with 457 additions and 75 deletions
@@ -72,6 +72,8 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
jfieldID statusField = env->GetFieldID(resultClass, "status", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
jfieldID failingCountField = env->GetFieldID(resultClass, "failingPixelCount", "J");
jfieldID maxDiffField = env->GetFieldID(resultClass, "maxDiffFound", "[F");
jfieldID avgErrorField = env->GetFieldID(resultClass, "averageError", "[F");
jfieldID errHistField = env->GetFieldID(resultClass, "errorHistogram", "[I");
jfieldID diffImageField = env->GetFieldID(resultClass, "diffImage", "Landroid/graphics/Bitmap;");
// Map Status enum
@@ -98,6 +100,24 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
env->SetFloatArrayRegion(maxDiffArray, 0, 4, result.maxDiffFound);
env->SetObjectField(resultObj, maxDiffField, maxDiffArray);
float avgErr[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
if (result.failingPixelCount > 0) {
for (int i = 0; i < 4; ++i) {
avgErr[i] = result.errorSum[i] / (float) result.failingPixelCount;
}
}
jfloatArray avgErrorArray = env->NewFloatArray(4);
env->SetFloatArrayRegion(avgErrorArray, 0, 4, avgErr);
env->SetObjectField(resultObj, avgErrorField, avgErrorArray);
jintArray errHistArray = env->NewIntArray(10);
jint hist[10];
for (int i = 0; i < 10; ++i) {
hist[i] = (jint) result.errorHistogram[i];
}
env->SetIntArrayRegion(errHistArray, 0, 10, hist);
env->SetObjectField(resultObj, errHistField, errHistArray);
if (generateDiff && result.diffImage.getWidth() > 0) {
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",