@@ -589,15 +589,11 @@ public static boolean screenshotTest(String screenshotName) {
589589 Display .getInstance ().getCurrent ().paintComponent (mute .getGraphics (), true );
590590 screenshotName = screenshotName + ".png" ;
591591 if (Storage .getInstance ().exists (screenshotName )) {
592- int [] rgba = mute .getRGBCached ();
593592 Image orig = Image .createImage (Storage .getInstance ().createInputStream (screenshotName ));
594- int [] origRgba = orig .getRGBCached ();
595- for (int iter = 0 ; iter < rgba .length ; iter ++) {
596- if (rgba [iter ] != origRgba [iter ]) {
597- log ("screenshots do not match at offset " + iter + " saving additional image under " + screenshotName + ".fail" );
598- io .save (mute , Storage .getInstance ().createOutputStream (screenshotName + ".fail" ), ImageIO .FORMAT_PNG , 1 );
599- return false ;
600- }
593+ if (!imagesWithinTolerance (mute , orig )) {
594+ log ("screenshots do not match within tolerance; saving additional image under " + screenshotName + ".fail" );
595+ io .save (mute , Storage .getInstance ().createOutputStream (screenshotName + ".fail" ), ImageIO .FORMAT_PNG , 1 );
596+ return false ;
601597 }
602598 } else {
603599 io .save (mute , Storage .getInstance ().createOutputStream (screenshotName ), ImageIO .FORMAT_PNG , 1 );
@@ -609,6 +605,66 @@ public static boolean screenshotTest(String screenshotName) {
609605 }
610606 }
611607
608+ private static boolean imagesWithinTolerance (Image candidate , Image baseline ) {
609+ int [] candidateRgba = candidate .getRGBCached ();
610+ int [] baselineRgba = baseline .getRGBCached ();
611+ if (candidateRgba .length != baselineRgba .length ) {
612+ log ("Screenshot sizes differ: candidate has " + candidateRgba .length + " pixels while baseline has " + baselineRgba .length );
613+ return false ;
614+ }
615+
616+ // Allow small, widespread differences and a tiny proportion of slightly larger
617+ // differences to accommodate minor rendering artifacts. These values are tuned
618+ // to keep comparisons strict while ignoring harmless noise we see across
619+ // devices/runs.
620+ final int toleratedChannelDelta = 3 ; // Difference that triggers "pixel differs" counting
621+ final double maxDifferentPixelsRatio = 0.01 ; // Up to 1% of pixels may exceed toleratedChannelDelta
622+ final double maxAverageChannelDelta = 2.5 ; // Average absolute delta per channel across whole image
623+ final double maxRmsChannelDelta = 5.0 ; // RMS delta per channel across whole image
624+
625+ int differingPixels = 0 ;
626+ long totalChannelDelta = 0 ;
627+ long totalChannelDeltaSquared = 0 ;
628+
629+ for (int iter = 0 ; iter < candidateRgba .length ; iter ++) {
630+ int candidatePixel = candidateRgba [iter ];
631+ int baselinePixel = baselineRgba [iter ];
632+
633+ int deltaR = Math .abs (((candidatePixel >> 16 ) & 0xff ) - ((baselinePixel >> 16 ) & 0xff ));
634+ int deltaG = Math .abs (((candidatePixel >> 8 ) & 0xff ) - ((baselinePixel >> 8 ) & 0xff ));
635+ int deltaB = Math .abs ((candidatePixel & 0xff ) - (baselinePixel & 0xff ));
636+ int deltaA = Math .abs (((candidatePixel >> 24 ) & 0xff ) - ((baselinePixel >> 24 ) & 0xff ));
637+
638+ int maxChannelDelta = Math .max (Math .max (deltaR , deltaG ), Math .max (deltaB , deltaA ));
639+ if (maxChannelDelta > toleratedChannelDelta ) {
640+ differingPixels ++;
641+ }
642+
643+ totalChannelDelta += deltaR + deltaG + deltaB + deltaA ;
644+ totalChannelDeltaSquared += deltaR * (long ) deltaR
645+ + deltaG * (long ) deltaG
646+ + deltaB * (long ) deltaB
647+ + deltaA * (long ) deltaA ;
648+ }
649+
650+ double differentPixelRatio = differingPixels / (double ) candidateRgba .length ;
651+ double averageChannelDelta = totalChannelDelta / (double ) (candidateRgba .length * 4 );
652+ double rmsChannelDelta = Math .sqrt (totalChannelDeltaSquared / (double ) (candidateRgba .length * 4 ));
653+
654+ boolean withinThresholds = differentPixelRatio <= maxDifferentPixelsRatio
655+ && averageChannelDelta <= maxAverageChannelDelta
656+ && rmsChannelDelta <= maxRmsChannelDelta ;
657+
658+ if (!withinThresholds ) {
659+ log ("Screenshot differs: "
660+ + (differentPixelRatio * 100.0 ) + "% pixels exceed delta " + toleratedChannelDelta
661+ + ", avg channel delta=" + averageChannelDelta
662+ + ", rms channel delta=" + rmsChannelDelta );
663+ }
664+
665+ return withinThresholds ;
666+ }
667+
612668 /**
613669 * Log to the test log
614670 *
0 commit comments