Tuesday, June 14, 2016

Image Comparison in Selenium using Thumbnailator

What it does (simplified): Used to compare two images.

First the two images that should be compared are divided in squares with the width and height,that you defined in the constructor. For every square in each image an average RGB-Value is calculated. If the average RGB-Values of the corresponding squares differ more than the threshold that you defined in the constructor (0.05 = 5%), the function fuzzyEqual(…) will return false.If you passed a path to save an image with the found differences a copy will be save at this path with \ all the differences marked with red squares.

Note: The sensitivity of the fuzzy-equal-test will be influenced by the defined threshold as well as the size of the squares!

How to Use:
  
  String imgOriginal = "F://ImageComparision//logo.png";
  String imgToCompareWithOriginal = "F://ImageComparision//screenshot.png";
  String imgOutputDifferences = "F://ImageComparision//new_screenshot_with_changes.png";
  
  ImageComparison imageComparison = new ImageComparison(10,10,0.05);
  if(imageComparison.fuzzyEqual(imgOriginal,imgToCompareWithOriginal,imgOutputDifferences))
  System.out.println("Images are equal.");
  else
  System.out.println("Images are not equal.");

Jars files to add to buildpath : thumbnailator-0.4.8.jar - http://repo1.maven.org/maven2/net/coobird/thumbnailator/0.4.8/thumbnailator-0.4.8.jar

package common;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;

import javax.imageio.ImageIO;

import net.coobird.thumbnailator.Thumbnails;
import com.sun.image.codec.jpeg.*;

public class ImageComparison {
    private BufferedImage imgOut = null;
    private int pixelPerBlockX;
    private int pixelPerBlockY;
    private double threshold;

    public ImageComparison(int pixelPerBlockX, int pixelPerBlockY,
   double threshold) {
this.pixelPerBlockX = pixelPerBlockX;
this.pixelPerBlockY = pixelPerBlockY;
this.threshold = threshold;
    }

    public boolean fuzzyEqual(String path1, String path2, String pathOut)
   throws IOException {
return fuzzyEqual(ImageIO.read(new File(path1)),
ImageIO.read(new File(path2)), pathOut);
    }

    public boolean fuzzyEqual(File file1, File file2, String pathOut)
   throws IOException {
return fuzzyEqual(ImageIO.read(file1), ImageIO.read(file2), pathOut);
    }

    public boolean fuzzyEqual(Image img1, Image img2, String pathOut)
   throws IOException {
return fuzzyEqual(imageToBufferedImage(img1),
imageToBufferedImage(img2), pathOut);
    }

    public boolean fuzzyEqual(BufferedImage img1, BufferedImage img2,
   String pathOut) throws IOException {
boolean fuzzyEqual = true;
img2 = adaptImageSize(img1, img2);

imgOut = imageToBufferedImage(img2);
Graphics2D outImgGraphics = imgOut.createGraphics();
outImgGraphics.setColor(Color.RED);

int subImageHeight;
int subImageWidth;
// String debug;

int blocksx = (int) Math.ceil((float) img1.getWidth()
/ (float) pixelPerBlockX);
int blocksy = (int) Math.ceil((float) img1.getHeight()
/ (float) pixelPerBlockY);

for (int y = 0; y < blocksy; y++) {
   // debug = "";
   for (int x = 0; x < blocksx; x++) {
subImageWidth = calcPixSpan(pixelPerBlockX, x, img1.getWidth());
subImageHeight = calcPixSpan(pixelPerBlockY, y,
img1.getHeight());
/*
* System.out.println(" Real Width:" + img1.getWidth() +
* " Real Height:" + img1.getHeight() + " X-Start:" + x
* pixelPerBlockX + " Y-Start:" + y * pixelPerBlockY +
* " sub width:" + subImageWidth + " sub height:" +
* subImageHeight); System.out.println(" Real Width:" +
* img2.getWidth() + " Real Height:" + img2.getHeight() +
* " X-Start:" + x pixelPerBlockX + " Y-Start:" + y *
* pixelPerBlockY + " sub width:" + subImageWidth +
* " sub height:" + subImageHeight);
*/
HashMap<String, Integer> avgRgb1 = getAverageRgb(img1
.getSubimage(x * pixelPerBlockX, y * pixelPerBlockY,
subImageWidth, subImageHeight));
HashMap<String, Integer> avgRgb2 = getAverageRgb(img2
.getSubimage(x * pixelPerBlockX, y * pixelPerBlockY,
subImageWidth, subImageHeight));
// debug = debug
// + String.format(Locale.ENGLISH, "%1.2f",
// calculateRgbDiff(avgRgb1, avgRgb2)) + " | ";
if (calculateRgbDiff(avgRgb1, avgRgb2) > threshold) {
   outImgGraphics.drawRect(x * pixelPerBlockX, y
   * pixelPerBlockY, pixelPerBlockX - 1,
   pixelPerBlockY - 1);
   fuzzyEqual = false;
}
   }
   // System.out.println(debug);
}
if (pathOut != null && !pathOut.isEmpty())
   saveImage(imgOut, pathOut);
return fuzzyEqual;
    }

    private int calcPixSpan(int pixelPerBlock, int n, int overallSpan) {
if (pixelPerBlock * (n + 1) > overallSpan)
   return overallSpan % pixelPerBlock;
else
   return pixelPerBlock;
    }

    private BufferedImage adaptImageSize(BufferedImage img1, BufferedImage img2)
   throws IOException {
int scalePixelWidth;
int scalePixelHeight;
/*
* System.out.println("1 Real Width:" + img1.getWidth() +
* " Real Height:" + img1.getHeight());
* System.out.println("2 Real Width:" + img2.getWidth() +
* " Real Height:" + img2.getHeight());
*/

if (((float) img2.getWidth() / (float) img1.getWidth()) < ((float) img2
.getHeight() / (float) img1.getHeight())) {
   scalePixelWidth = img1.getWidth();
   scalePixelHeight = (int) (img2.getHeight() * Math.ceil((float) img1
   .getWidth() / (float) img2.getWidth()));
   System.out.println("If : Scale Width:" + scalePixelWidth
   + " Scale Height:" + scalePixelHeight);
} else {
   scalePixelHeight = img1.getHeight();
   scalePixelWidth = (int) (img2.getWidth() * Math.ceil((float) img1
   .getHeight() / (float) img2.getHeight()));
   System.out.println("Else: Scale Width:" + scalePixelWidth
   + " Scale Height:" + scalePixelHeight);
}
/*
* System.out.println("1 Real Width:" + img1.getWidth() +
* " Real Height:" + img1.getHeight());
* System.out.println("2 Real Width:" +
* Thumbnails.of(img2).size(scalePixelWidth, scalePixelHeight)
* .asBufferedImage().getWidth() + " Real Height:" +
* Thumbnails.of(img2).size(scalePixelWidth, scalePixelHeight)
* .asBufferedImage().getHeight());
*/
return Thumbnails.of(img2).size(scalePixelWidth, scalePixelHeight)
.asBufferedImage();
    }

    private double calculateRgbDiff(HashMap<String, Integer> avgRgb1,
   HashMap<String, Integer> avgRgb2) {
double maxDifference = 255 * 3;
double difference = Math.abs(avgRgb1.get("r") - avgRgb2.get("r"))
+ Math.abs(avgRgb1.get("g") - avgRgb2.get("g"))
+ Math.abs(avgRgb1.get("b") - avgRgb2.get("b"));

return difference / maxDifference;
    }

    private HashMap<String, Integer> getAverageRgb(BufferedImage img) {
Raster currentRaster = img.getData();
HashMap<String, Integer> averageRgb = new HashMap<String, Integer>();
averageRgb.put("r", 0);
averageRgb.put("g", 0);
averageRgb.put("b", 0);

for (int y = 0; y < img.getHeight(); y++) {
   for (int x = 0; x < img.getWidth(); x++) {
averageRgb.put("r",
averageRgb.get("r") + currentRaster.getSample(x, y, 0));
averageRgb.put("g",
averageRgb.get("g") + currentRaster.getSample(x, y, 1));
averageRgb.put("b",
averageRgb.get("b") + currentRaster.getSample(x, y, 2));

   }
}

averageRgb.put("r",
averageRgb.get("r") / (img.getHeight() * img.getWidth()));
averageRgb.put("g",
averageRgb.get("g") / (img.getHeight() * img.getWidth()));
averageRgb.put("b",
averageRgb.get("b") / (img.getHeight() * img.getWidth()));

return averageRgb;
    }

    private BufferedImage imageToBufferedImage(Image img) {
BufferedImage bi = new BufferedImage(img.getWidth(null),
img.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
g2.drawImage(img, null, null);
return bi;
    }

    private void saveImage(Image img, String filename) {
BufferedImage bi = imageToBufferedImage(img);
FileOutputStream out = null;
try {
   out = new FileOutputStream(filename);
} catch (java.io.FileNotFoundException io) {
   System.out.println("File Not Found");
}
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
param.setQuality(0.8f, false);
encoder.setJPEGEncodeParam(param);
try {
   encoder.encode(bi);
   out.close();
} catch (java.io.IOException io) {
   System.out.println("IOException");
}
    }

}


Courtesy:  http://www.frontendtest.org/blog/screenshot-comparison/