package ru.yandex.qatools.ashot.comparison;
import ru.yandex.qatools.ashot.Screenshot;
import ru.yandex.qatools.ashot.coordinates.Coords;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.LinkedHashSet;
import java.util.Set;
import static ru.yandex.qatools.ashot.util.ImageBytesDiffer.areImagesEqual;
import static ru.yandex.qatools.ashot.util.ImageTool.rgbCompare;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class ImageDiffer {
private static final int DEFAULT_COLOR_DISTORTION = 15;
private int colorDistortion = DEFAULT_COLOR_DISTORTION;
private DiffMarkupPolicy diffMarkupPolicy = new PointsMarkupPolicy();
private Color ignoredColor = null;
public ImageDiffer withIgnoredColor(final Color ignoreColor) {
this.ignoredColor = ignoreColor;
return this;
}
public ImageDiffer withColorDistortion(int distortion) {
this.colorDistortion = distortion;
return this;
}
/**
* Sets the diff markup policy.
*
* @param diffMarkupPolicy diff markup policy instance
* @return self for fluent style
* @see ImageMarkupPolicy
* @see PointsMarkupPolicy
*/
public ImageDiffer withDiffMarkupPolicy(final DiffMarkupPolicy diffMarkupPolicy) {
this.diffMarkupPolicy = diffMarkupPolicy;
return this;
}
public ImageDiff makeDiff(Screenshot expected, Screenshot actual) {
ImageDiff diff = new ImageDiff(diffMarkupPolicy);
if (areImagesEqual(expected, actual)) {
diff.setDiffImage(actual.getImage());
} else {
markDiffPoints(expected, actual, diff);
}
return diff;
}
protected void markDiffPoints(Screenshot expected, Screenshot actual, ImageDiff diff) {
Coords expectedImageCoords = Coords.ofImage(expected.getImage());
Coords actualImageCoords = Coords.ofImage(actual.getImage());
CoordsSet compareCoordsSet = new CoordsSet(CoordsSet.union(actual.getCoordsToCompare(), expected.getCoordsToCompare()));
CoordsSet ignoreCoordsSet = new CoordsSet(CoordsSet.intersection(actual.getIgnoredAreas(), expected.getIgnoredAreas()));
int width = Math.max(expected.getImage().getWidth(), actual.getImage().getWidth());
int height = Math.max(expected.getImage().getHeight(), actual.getImage().getHeight());
diff.setDiffImage(createDiffImage(expected.getImage(), actual.getImage(), width, height));
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (ignoreCoordsSet.contains(i, j)) {
continue;
}
if (!isInsideBothImages(i, j, expectedImageCoords, actualImageCoords)
|| compareCoordsSet.contains(i, j) && hasDiffInChannel(expected, actual, i, j)) {
diff.addDiffPoint(i, j);
}
}
}
}
private boolean hasDiffInChannel(Screenshot expected, Screenshot actual, int i, int j) {
if(ignoredColor != null && rgbCompare(expected.getImage().getRGB(i, j), ignoredColor.getRGB(), 0)) {
return false;
}
return !rgbCompare(expected.getImage().getRGB(i, j), actual.getImage().getRGB(i, j), colorDistortion);
}
public ImageDiff makeDiff(BufferedImage expected, BufferedImage actual) {
return makeDiff(new Screenshot(expected), new Screenshot(actual));
}
private BufferedImage createDiffImage(BufferedImage expectedImage, BufferedImage actualImage, int width, int height) {
BufferedImage diffImage = new BufferedImage(width, height, actualImage.getType());
paintImage(actualImage, diffImage);
paintImage(expectedImage, diffImage);
return diffImage;
}
private void paintImage(BufferedImage image, BufferedImage diffImage) {
Graphics graphics = diffImage.getGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
}
private boolean isInsideBothImages(int i, int j, Coords expected, Coords actual) {
return expected.contains(i, j) && actual.contains(i, j);
}
private static class CoordsSet {
private final boolean isSingle;
private final Coords minRectangle;
private Set<Coords> coordsSet;
public CoordsSet(Set<Coords> coordsSet) {
isSingle = coordsSet.size() == 1;
this.coordsSet = coordsSet;
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = 0;
int maxY = 0;
for (Coords coords : coordsSet) {
minX = Math.min(minX, (int) coords.getMinX());
minY = Math.min(minY, (int) coords.getMinY());
maxX = Math.max(maxX, (int) coords.getMaxX());
maxY = Math.max(maxY, (int) coords.getMaxY());
}
minRectangle = new Coords(minX, minY, maxX - minX, maxY - minY);
}
private boolean contains(int i, int j) {
return inaccurateContains(i, j) && accurateContains(i, j);
}
private boolean inaccurateContains(int i, int j) {
return minRectangle.contains(i, j);
}
private boolean accurateContains(int i, int j) {
if (isSingle) {
return true;
}
for (Coords coords : coordsSet) {
if (coords.contains(i, j)) {
return true;
}
}
return false;
}
private static Set<Coords> intersection(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {
return Coords.intersection(coordsPool1, coordsPool2);
}
private static Set<Coords> union(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {
Set<Coords> coordsPool = new LinkedHashSet<>();
coordsPool.addAll(coordsPool1);
coordsPool.addAll(coordsPool2);
return coordsPool;
}
}
}