mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-10 17:22:09 +10:00
mavenize
This commit is contained in:
+703
@@ -0,0 +1,703 @@
|
||||
package com.github.romankh3.image.comparison;
|
||||
|
||||
import com.github.romankh3.image.comparison.model.ExcludedAreas;
|
||||
import com.github.romankh3.image.comparison.model.ImageComparisonResult;
|
||||
import com.github.romankh3.image.comparison.model.ImageComparisonState;
|
||||
import com.github.romankh3.image.comparison.model.Rectangle;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.github.romankh3.image.comparison.ImageComparisonUtil.getDifferencePercent;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Main class for comparison images.
|
||||
*/
|
||||
public class ImageComparison {
|
||||
|
||||
/**
|
||||
* The threshold which means the max distance between non-equal pixels.
|
||||
* Could be changed according to the size and requirements of the image.
|
||||
*/
|
||||
private int threshold = 5;
|
||||
|
||||
/**
|
||||
* Expected image for comparison
|
||||
*/
|
||||
private final BufferedImage expected;
|
||||
|
||||
/**
|
||||
* Actual image for comparison
|
||||
*/
|
||||
private final BufferedImage actual;
|
||||
|
||||
/**
|
||||
* Width of the line that is drawn the rectangle
|
||||
*/
|
||||
private int rectangleLineWidth = 1;
|
||||
|
||||
/**
|
||||
* {@link File} of the result destination.
|
||||
*/
|
||||
private /* @Nullable */ File destination;
|
||||
|
||||
/**
|
||||
* The number which marks how many rectangles. Beginning from 2.
|
||||
*/
|
||||
private int counter = 2;
|
||||
|
||||
/**
|
||||
* The number of the marking specific rectangle.
|
||||
*/
|
||||
private int regionCount = counter;
|
||||
|
||||
/**
|
||||
* The number of the minimal rectangle size. Count as (width x height).
|
||||
*/
|
||||
private Integer minimalRectangleSize = 1;
|
||||
|
||||
/**
|
||||
* Maximal count of the {@link Rectangle}s.
|
||||
* It means that would get the first x biggest rectangles.
|
||||
* Default value is -1, that means that all the rectangles would be drawn.
|
||||
*/
|
||||
private Integer maximalRectangleCount = -1;
|
||||
|
||||
/**
|
||||
* Level of the pixel tolerance. By default, it's 0.1 -> 10% difference.
|
||||
* The value can be set from 0.0 to 0.99.
|
||||
*/
|
||||
private double pixelToleranceLevel = 0.1;
|
||||
|
||||
/**
|
||||
* Constant using for counting the level of the difference.
|
||||
*/
|
||||
private double differenceConstant;
|
||||
|
||||
/**
|
||||
* Matrix YxX => int[y][x].
|
||||
* E.g.:
|
||||
* | X - width ----
|
||||
* | .....................................
|
||||
* Y . (0, 0) .
|
||||
* | . .
|
||||
* | . .
|
||||
* h . .
|
||||
* e . .
|
||||
* i . .
|
||||
* g . .
|
||||
* h . .
|
||||
* t . (X, Y).
|
||||
* | .....................................
|
||||
*/
|
||||
private int[][] matrix;
|
||||
|
||||
/**
|
||||
* ExcludedAreas contains a List of {@link Rectangle}s to be ignored when comparing images
|
||||
*/
|
||||
private ExcludedAreas excludedAreas = new ExcludedAreas();
|
||||
|
||||
/**
|
||||
* Flag which says draw excluded rectangles or not.
|
||||
*/
|
||||
private boolean drawExcludedRectangles = false;
|
||||
|
||||
/**
|
||||
* Flag for filling comparison difference rectangles.
|
||||
*/
|
||||
private boolean fillDifferenceRectangles = false;
|
||||
|
||||
/**
|
||||
* Sets the opacity percentage of the fill of comparison difference rectangles. 0.0 means completely transparent and 100.0 means completely opaque.
|
||||
*/
|
||||
private double percentOpacityDifferenceRectangles = 20.0;
|
||||
|
||||
/**
|
||||
* Flag for filling excluded rectangles.
|
||||
*/
|
||||
private boolean fillExcludedRectangles = false;
|
||||
|
||||
/**
|
||||
* Sets the opacity percentage of the fill of excluded rectangles. 0.0 means completely transparent and 100.0 means completely opaque.
|
||||
*/
|
||||
private double percentOpacityExcludedRectangles = 20.0;
|
||||
|
||||
/**
|
||||
* The percent of the allowing pixels to be different to stay {@link ImageComparisonState#MATCH} for comparison.
|
||||
* E.g. percent of the pixels, which would ignore in comparison.
|
||||
*/
|
||||
private double allowingPercentOfDifferentPixels = 0.0;
|
||||
|
||||
/**
|
||||
* Sets rectangle color of image difference. By default, it's red.
|
||||
*/
|
||||
private Color differenceRectangleColor = Color.RED;
|
||||
|
||||
/**
|
||||
* Sets rectangle color of excluded part. By default, it's green.
|
||||
*/
|
||||
private Color excludedRectangleColor = Color.GREEN;
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link ImageComparison} that can compare the given images.
|
||||
*
|
||||
* @param expected expected image to be compared
|
||||
* @param actual actual image to be compared
|
||||
*/
|
||||
public ImageComparison(String expected, String actual) {
|
||||
this(ImageComparisonUtil.readImageFromResources(expected),
|
||||
ImageComparisonUtil.readImageFromResources(actual),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link ImageComparison} that can compare the given images.
|
||||
*
|
||||
* @param expected expected image to be compared
|
||||
* @param actual actual image to be compared
|
||||
* @param destination destination to save the result. If null, the result is shown in the UI.
|
||||
*/
|
||||
public ImageComparison(BufferedImage expected, BufferedImage actual, File destination) {
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
this.destination = destination;
|
||||
differenceConstant = calculateDifferenceConstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link ImageComparison} that can compare the given images.
|
||||
*
|
||||
* @param expected expected image to be compared
|
||||
* @param actual actual image to be compared
|
||||
*/
|
||||
public ImageComparison(BufferedImage expected, BufferedImage actual) {
|
||||
this(expected, actual, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw rectangles which cover the regions of the difference pixels.
|
||||
*
|
||||
* @return the result of the drawing.
|
||||
*/
|
||||
public ImageComparisonResult compareImages() {
|
||||
|
||||
// check that the images have the same size
|
||||
if (isImageSizesNotEqual(expected, actual)) {
|
||||
BufferedImage actualResized = ImageComparisonUtil.resize(actual, expected.getWidth(), expected.getHeight());
|
||||
return ImageComparisonResult.defaultSizeMisMatchResult(expected, actual, getDifferencePercent(actualResized, expected));
|
||||
}
|
||||
|
||||
List<Rectangle> rectangles = populateRectangles();
|
||||
|
||||
if (rectangles.isEmpty()) {
|
||||
ImageComparisonResult matchResult = ImageComparisonResult.defaultMatchResult(expected, actual);
|
||||
if (drawExcludedRectangles) {
|
||||
matchResult.setResult(drawRectangles(rectangles));
|
||||
saveImageForDestination(matchResult.getResult());
|
||||
}
|
||||
return matchResult;
|
||||
}
|
||||
|
||||
BufferedImage resultImage = drawRectangles(rectangles);
|
||||
saveImageForDestination(resultImage);
|
||||
return ImageComparisonResult.defaultMisMatchResult(expected, actual, getDifferencePercent(actual, expected))
|
||||
.setResult(resultImage)
|
||||
.setRectangles(rectangles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check images for equals their widths and heights.
|
||||
*
|
||||
* @param expected {@link BufferedImage} object of the expected image.
|
||||
* @param actual {@link BufferedImage} object of the actual image.
|
||||
* @return true if image size are not equal, false otherwise.
|
||||
*/
|
||||
private boolean isImageSizesNotEqual(BufferedImage expected, BufferedImage actual) {
|
||||
return expected.getHeight() != actual.getHeight() || expected.getWidth() != actual.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate binary matrix with "0" and "1". If the pixels are different set it as "1", otherwise "0".
|
||||
*
|
||||
* @return the count of different pixels
|
||||
*/
|
||||
private long populateTheMatrixOfTheDifferences() {
|
||||
long countOfDifferentPixels = 0;
|
||||
matrix = new int[expected.getHeight()][expected.getWidth()];
|
||||
for (int y = 0; y < expected.getHeight(); y++) {
|
||||
for (int x = 0; x < expected.getWidth(); x++) {
|
||||
if (!excludedAreas.contains(new Point(x, y))) {
|
||||
if (isDifferentPixels(expected.getRGB(x, y), actual.getRGB(x, y))) {
|
||||
matrix[y][x] = 1;
|
||||
countOfDifferentPixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return countOfDifferentPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Say if the two pixels equal or not. The rule is the difference between two pixels
|
||||
* need to be more than {@link #pixelToleranceLevel}.
|
||||
*
|
||||
* @param expectedRgb the RGB value of the Pixel of the Expected image.
|
||||
* @param actualRgb the RGB value of the Pixel of the Actual image.
|
||||
* @return {@code true} if they' are difference, {@code false} otherwise.
|
||||
*/
|
||||
private boolean isDifferentPixels(int expectedRgb, int actualRgb) {
|
||||
if (expectedRgb == actualRgb) {
|
||||
return false;
|
||||
} else if (pixelToleranceLevel == 0.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int red1 = (expectedRgb >> 16) & 0xff;
|
||||
int green1 = (expectedRgb >> 8) & 0xff;
|
||||
int blue1 = (expectedRgb) & 0xff;
|
||||
int red2 = (actualRgb >> 16) & 0xff;
|
||||
int green2 = (actualRgb >> 8) & 0xff;
|
||||
int blue2 = (actualRgb) & 0xff;
|
||||
|
||||
return (Math.pow(red2 - red1, 2) + Math.pow(green2 - green1, 2) + Math.pow(blue2 - blue1, 2))
|
||||
> differenceConstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate rectangles of the differences
|
||||
*
|
||||
* @return the collection of the populated {@link Rectangle} objects.
|
||||
*/
|
||||
private List<Rectangle> populateRectangles() {
|
||||
long countOfDifferentPixels = populateTheMatrixOfTheDifferences();
|
||||
|
||||
if (countOfDifferentPixels == 0) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
if (isAllowedPercentOfDifferentPixels(countOfDifferentPixels)) {
|
||||
return emptyList();
|
||||
}
|
||||
groupRegions();
|
||||
List<Rectangle> rectangles = new ArrayList<>();
|
||||
while (counter <= regionCount) {
|
||||
Rectangle rectangle = createRectangle();
|
||||
if (!rectangle.equals(Rectangle.createDefault()) && rectangle.size() >= minimalRectangleSize) {
|
||||
rectangles.add(rectangle);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
||||
return mergeRectangles(mergeRectangles(rectangles));
|
||||
}
|
||||
|
||||
/**
|
||||
* Say if provided {@param countOfDifferentPixels} is allowed for {@link ImageComparisonState#MATCH} state.
|
||||
*
|
||||
* @param countOfDifferentPixels the count of the different pixels in comparison.
|
||||
* @return true, if percent of different pixels lower or equal {@link ImageComparison#allowingPercentOfDifferentPixels},
|
||||
* false - otherwise.
|
||||
*/
|
||||
private boolean isAllowedPercentOfDifferentPixels(long countOfDifferentPixels) {
|
||||
long totalPixelCount = matrix.length * matrix[0].length;
|
||||
double actualPercentOfDifferentPixels = ((double) countOfDifferentPixels / (double) totalPixelCount) * 100;
|
||||
return actualPercentOfDifferentPixels <= allowingPercentOfDifferentPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Rectangle} object.
|
||||
*
|
||||
* @return the {@link Rectangle} object.
|
||||
*/
|
||||
private Rectangle createRectangle() {
|
||||
Rectangle rectangle = Rectangle.createDefault();
|
||||
for (int y = 0; y < matrix.length; y++) {
|
||||
for (int x = 0; x < matrix[0].length; x++) {
|
||||
if (matrix[y][x] == counter) {
|
||||
updateRectangleCreation(rectangle, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update {@link Point} of the rectangle based on x and y coordinates.
|
||||
*/
|
||||
private void updateRectangleCreation(Rectangle rectangle, int x, int y) {
|
||||
if (x < rectangle.getMinPoint().getX()) {
|
||||
rectangle.getMinPoint().x = x;
|
||||
}
|
||||
if (x > rectangle.getMaxPoint().getX()) {
|
||||
rectangle.getMaxPoint().x = x;
|
||||
}
|
||||
|
||||
if (y < rectangle.getMinPoint().getY()) {
|
||||
rectangle.getMinPoint().y = y;
|
||||
}
|
||||
if (y > rectangle.getMaxPoint().getY()) {
|
||||
rectangle.getMaxPoint().y = y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find overlapping rectangles and merge them.
|
||||
*/
|
||||
private List<Rectangle> mergeRectangles(List<Rectangle> rectangles) {
|
||||
int position = 0;
|
||||
while (position < rectangles.size()) {
|
||||
if (rectangles.get(position).equals(Rectangle.createZero())) {
|
||||
position++;
|
||||
}
|
||||
for (int i = 1 + position; i < rectangles.size(); i++) {
|
||||
Rectangle r1 = rectangles.get(position);
|
||||
Rectangle r2 = rectangles.get(i);
|
||||
if (r2.equals(Rectangle.createZero())) {
|
||||
continue;
|
||||
}
|
||||
if (r1.isOverlapping(r2)) {
|
||||
rectangles.set(position, r1.merge(r2));
|
||||
r2.makeZeroRectangle();
|
||||
if (position != 0) {
|
||||
position--;
|
||||
}
|
||||
}
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
return rectangles.stream().filter(it -> !it.equals(Rectangle.createZero())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the rectangles based on collection of the rectangles and result image.
|
||||
*
|
||||
* @param rectangles the collection of the {@link Rectangle} objects.
|
||||
* @return result {@link BufferedImage} with drawn rectangles.
|
||||
*/
|
||||
private BufferedImage drawRectangles(List<Rectangle> rectangles) {
|
||||
BufferedImage resultImage = ImageComparisonUtil.deepCopy(actual);
|
||||
Graphics2D graphics = preparedGraphics2D(resultImage);
|
||||
|
||||
drawExcludedRectangles(graphics);
|
||||
drawRectanglesOfDifferences(rectangles, graphics);
|
||||
|
||||
return resultImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw excluded rectangles.
|
||||
*
|
||||
* @param graphics prepared {@link Graphics2D}object.
|
||||
*/
|
||||
private void drawExcludedRectangles(Graphics2D graphics) {
|
||||
if (drawExcludedRectangles) {
|
||||
graphics.setColor(this.excludedRectangleColor);
|
||||
draw(graphics, excludedAreas.getExcluded());
|
||||
|
||||
if (fillExcludedRectangles) {
|
||||
fillRectangles(graphics, excludedAreas.getExcluded(), percentOpacityExcludedRectangles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw rectangles with the differences.
|
||||
*
|
||||
* @param rectangles the collection of the {@link Rectangle} of differences.
|
||||
* @param graphics prepared {@link Graphics2D}object.
|
||||
*/
|
||||
private void drawRectanglesOfDifferences(List<Rectangle> rectangles, Graphics2D graphics) {
|
||||
List<Rectangle> rectanglesForDraw;
|
||||
graphics.setColor(this.differenceRectangleColor);
|
||||
|
||||
if (maximalRectangleCount > 0 && maximalRectangleCount < rectangles.size()) {
|
||||
rectanglesForDraw = rectangles.stream()
|
||||
.sorted(Comparator.comparing(Rectangle::size))
|
||||
.skip(rectangles.size() - maximalRectangleCount)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
rectanglesForDraw = new ArrayList<>(rectangles);
|
||||
}
|
||||
|
||||
draw(graphics, rectanglesForDraw);
|
||||
|
||||
if (fillDifferenceRectangles) {
|
||||
fillRectangles(graphics, rectanglesForDraw, percentOpacityDifferenceRectangles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare {@link Graphics2D} based on resultImage and rectangleLineWidth
|
||||
*
|
||||
* @param image image based on created {@link Graphics2D}.
|
||||
* @return prepared {@link Graphics2D} object.
|
||||
*/
|
||||
private Graphics2D preparedGraphics2D(BufferedImage image) {
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setStroke(new BasicStroke(rectangleLineWidth));
|
||||
return graphics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save image to destination object if exists.
|
||||
*
|
||||
* @param image {@link BufferedImage} to be saved.
|
||||
*/
|
||||
private void saveImageForDestination(BufferedImage image) {
|
||||
if (Objects.nonNull(destination)) {
|
||||
ImageComparisonUtil.saveImage(destination, image);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw rectangles based on collection of the {@link Rectangle} and {@link Graphics2D}.
|
||||
* getWidth/getHeight return real width/height,
|
||||
* so need to draw rectangle on one px smaller because minpoint + width/height is point on excluded pixel.
|
||||
*
|
||||
* @param graphics the {@link Graphics2D} object for drawing.
|
||||
* @param rectangles the collection of the {@link Rectangle}.
|
||||
*/
|
||||
private void draw(Graphics2D graphics, List<Rectangle> rectangles) {
|
||||
rectangles.forEach(rectangle -> graphics.drawRect(
|
||||
rectangle.getMinPoint().x,
|
||||
rectangle.getMinPoint().y,
|
||||
rectangle.getWidth() - 1,
|
||||
rectangle.getHeight() - 1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill rectangles based on collection of the {@link Rectangle} and {@link Graphics2D}.
|
||||
* getWidth/getHeight return real width/height,
|
||||
* so need to draw rectangle fill two px smaller to fit inside rectangle borders.
|
||||
*
|
||||
* @param graphics the {@link Graphics2D} object for drawing.
|
||||
* @param rectangles rectangles the collection of the {@link Rectangle}.
|
||||
* @param percentOpacity the opacity of the fill.
|
||||
*/
|
||||
private void fillRectangles(Graphics2D graphics, List<Rectangle> rectangles, double percentOpacity) {
|
||||
|
||||
graphics.setColor(new Color(graphics.getColor().getRed(),
|
||||
graphics.getColor().getGreen(),
|
||||
graphics.getColor().getBlue(),
|
||||
(int) (percentOpacity / 100 * 255)
|
||||
));
|
||||
rectangles.forEach(rectangle -> graphics.fillRect(
|
||||
rectangle.getMinPoint().x - 1,
|
||||
rectangle.getMinPoint().y - 1,
|
||||
rectangle.getWidth() - 2,
|
||||
rectangle.getHeight() - 2)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Group rectangle regions in matrix.
|
||||
*/
|
||||
private void groupRegions() {
|
||||
for (int y = 0; y < matrix.length; y++) {
|
||||
for (int x = 0; x < matrix[y].length; x++) {
|
||||
if (matrix[y][x] == 1) {
|
||||
joinToRegion(x, y);
|
||||
regionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The recursive method which go to all directions and finds difference
|
||||
* in binary matrix using {@code threshold} for setting max distance between values which equal "1".
|
||||
* and set the {@code groupCount} to matrix.
|
||||
*
|
||||
* @param x the value of the X-coordinate.
|
||||
* @param y the value of the Y-coordinate.
|
||||
*/
|
||||
private void joinToRegion(int x, int y) {
|
||||
if (isJumpRejected(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
matrix[y][x] = regionCount;
|
||||
|
||||
for (int i = 0; i < threshold; i++) {
|
||||
joinToRegion(x + 1 + i, y);
|
||||
joinToRegion(x, y + 1 + i);
|
||||
|
||||
joinToRegion(x + 1 + i, y - 1 - i);
|
||||
joinToRegion(x - 1 - i, y + 1 + i);
|
||||
joinToRegion(x + 1 + i, y + 1 + i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of rectangles that would be drawn as a diff image.
|
||||
* If you submit two images that are the same barring the parts you want to excludedAreas you get a list of
|
||||
* rectangles that can be used as said excludedAreas
|
||||
*
|
||||
* @return List of {@link Rectangle}
|
||||
*/
|
||||
public List<Rectangle> createMask() {
|
||||
return populateRectangles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check next step valid or not.
|
||||
*
|
||||
* @param x X-coordinate of the image.
|
||||
* @param y Y-coordinate of the image
|
||||
* @return true if jump rejected, otherwise false.
|
||||
*/
|
||||
private boolean isJumpRejected(int x, int y) {
|
||||
return y < 0 || y >= matrix.length || x < 0 || x >= matrix[y].length || matrix[y][x] != 1;
|
||||
}
|
||||
|
||||
public double getPixelToleranceLevel() {
|
||||
return pixelToleranceLevel;
|
||||
}
|
||||
|
||||
public ImageComparison setPixelToleranceLevel(double pixelToleranceLevel) {
|
||||
if (0.0 <= pixelToleranceLevel && pixelToleranceLevel < 1) {
|
||||
this.pixelToleranceLevel = pixelToleranceLevel;
|
||||
differenceConstant = calculateDifferenceConstant();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private double calculateDifferenceConstant() {
|
||||
return Math.pow(pixelToleranceLevel * Math.sqrt(Math.pow(255, 2) * 3), 2);
|
||||
}
|
||||
|
||||
public boolean isDrawExcludedRectangles() {
|
||||
return drawExcludedRectangles;
|
||||
}
|
||||
|
||||
public ImageComparison setDrawExcludedRectangles(boolean drawExcludedRectangles) {
|
||||
this.drawExcludedRectangles = drawExcludedRectangles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public ImageComparison setThreshold(int threshold) {
|
||||
this.threshold = threshold;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Optional<File> getDestination() {
|
||||
return Optional.ofNullable(destination);
|
||||
}
|
||||
|
||||
public ImageComparison setDestination(File destination) {
|
||||
this.destination = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BufferedImage getExpected() {
|
||||
return expected;
|
||||
}
|
||||
|
||||
public BufferedImage getActual() {
|
||||
return actual;
|
||||
}
|
||||
|
||||
public int getRectangleLineWidth() {
|
||||
return rectangleLineWidth;
|
||||
}
|
||||
|
||||
public ImageComparison setRectangleLineWidth(int rectangleLineWidth) {
|
||||
this.rectangleLineWidth = rectangleLineWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMinimalRectangleSize() {
|
||||
return minimalRectangleSize;
|
||||
}
|
||||
|
||||
public ImageComparison setMinimalRectangleSize(Integer minimalRectangleSize) {
|
||||
this.minimalRectangleSize = minimalRectangleSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMaximalRectangleCount() {
|
||||
return maximalRectangleCount;
|
||||
}
|
||||
|
||||
public ImageComparison setMaximalRectangleCount(Integer maximalRectangleCount) {
|
||||
this.maximalRectangleCount = maximalRectangleCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImageComparison setExcludedAreas(List<Rectangle> excludedAreas) {
|
||||
this.excludedAreas = new ExcludedAreas(excludedAreas);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isFillDifferenceRectangles() {
|
||||
return this.fillDifferenceRectangles;
|
||||
}
|
||||
|
||||
public double getPercentOpacityDifferenceRectangles() {
|
||||
return this.percentOpacityDifferenceRectangles;
|
||||
}
|
||||
|
||||
public ImageComparison setDifferenceRectangleFilling(boolean fillRectangles, double percentOpacity) {
|
||||
this.fillDifferenceRectangles = fillRectangles;
|
||||
this.percentOpacityDifferenceRectangles = percentOpacity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isFillExcludedRectangles() {
|
||||
return this.fillExcludedRectangles;
|
||||
}
|
||||
|
||||
public double getPercentOpacityExcludedRectangles() {
|
||||
return this.percentOpacityExcludedRectangles;
|
||||
}
|
||||
|
||||
public ImageComparison setExcludedRectangleFilling(boolean fillRectangles, double percentOpacity) {
|
||||
this.fillExcludedRectangles = fillRectangles;
|
||||
this.percentOpacityExcludedRectangles = percentOpacity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getAllowingPercentOfDifferentPixels() {
|
||||
return allowingPercentOfDifferentPixels;
|
||||
}
|
||||
|
||||
public ImageComparison setAllowingPercentOfDifferentPixels(double allowingPercentOfDifferentPixels) {
|
||||
if (0.0 <= allowingPercentOfDifferentPixels && allowingPercentOfDifferentPixels <= 100) {
|
||||
this.allowingPercentOfDifferentPixels = allowingPercentOfDifferentPixels;
|
||||
} else {
|
||||
//todo add warning here
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Color getDifferenceRectangleColor() {
|
||||
return this.differenceRectangleColor;
|
||||
}
|
||||
|
||||
public ImageComparison setDifferenceRectangleColor(Color differenceRectangleColor) {
|
||||
this.differenceRectangleColor = differenceRectangleColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Color getExcludedRectangleColor() {
|
||||
return this.excludedRectangleColor;
|
||||
}
|
||||
|
||||
public ImageComparison setExcludedRectangleColor(Color excludedRectangleColor) {
|
||||
this.excludedRectangleColor = excludedRectangleColor;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.github.romankh3.image.comparison;
|
||||
|
||||
import com.github.romankh3.image.comparison.exception.ImageComparisonException;
|
||||
import com.github.romankh3.image.comparison.exception.ImageNotFoundException;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ConvolveOp;
|
||||
import java.awt.image.Kernel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Tools for the {@link ImageComparison} object.
|
||||
*/
|
||||
public final class ImageComparisonUtil {
|
||||
|
||||
/**
|
||||
* Make a copy of the {@link BufferedImage} object.
|
||||
*
|
||||
* @param image the provided image.
|
||||
* @return copy of the provided image.
|
||||
*/
|
||||
static BufferedImage deepCopy(BufferedImage image) {
|
||||
ColorModel cm = image.getColorModel();
|
||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
||||
WritableRaster raster = image.copyData(image.getRaster().createCompatibleWritableRaster());
|
||||
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read image from the provided path.
|
||||
*
|
||||
* @param path the path where contains image.
|
||||
* @return the {@link BufferedImage} object of this specific image.
|
||||
* @throws ImageComparisonException due to read the image from resources.
|
||||
*/
|
||||
public static BufferedImage readImageFromResources(String path) throws ImageComparisonException {
|
||||
File imageFile = new File(path);
|
||||
if (imageFile.exists()) {
|
||||
try {
|
||||
return ImageIO.read(imageFile);
|
||||
} catch (IOException e) {
|
||||
throw new ImageComparisonException(String.format("Cannot read image from the file, path=%s", path), e);
|
||||
}
|
||||
} else {
|
||||
InputStream inputStream = ImageComparisonUtil.class.getClassLoader().getResourceAsStream(path);
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
return ImageIO.read(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new ImageComparisonException(String.format("Cannot read image from the file, path=%s", path), e);
|
||||
}
|
||||
} else {
|
||||
throw new ImageNotFoundException(String.format("Image with path = %s not found", path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save image to the provided path.
|
||||
*
|
||||
* @param pathFile the path to the saving image.
|
||||
* @param image the {@link BufferedImage} object of this specific image.
|
||||
* @throws ImageComparisonException due to save image.
|
||||
*/
|
||||
public static void saveImage(File pathFile, BufferedImage image) throws ImageComparisonException {
|
||||
File dir = pathFile.getParentFile();
|
||||
// make dir if it's not using from Gradle.
|
||||
boolean dirExists = dir == null || dir.isDirectory() || dir.mkdirs();
|
||||
if (!dirExists) {
|
||||
throw new ImageComparisonException("Unable to create directory " + dir);
|
||||
}
|
||||
try {
|
||||
ImageIO.write(image, "png", pathFile);
|
||||
} catch (IOException e) {
|
||||
throw new ImageComparisonException(
|
||||
String.format("Cannot save image to path=%s", pathFile.getAbsolutePath()), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize image to new dimensions and return new image.
|
||||
*
|
||||
* @param img the object of the image to be resized.
|
||||
* @param newW the new width.
|
||||
* @param newH the new height.
|
||||
* @return resized {@link BufferedImage} object.
|
||||
*/
|
||||
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
|
||||
return toBufferedImage(img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image to buffered image.
|
||||
*
|
||||
* @param img the object of the image to be converted to buffered image.
|
||||
* @return the converted buffered image.
|
||||
*/
|
||||
public static BufferedImage toBufferedImage(Image img) {
|
||||
if (img instanceof BufferedImage) {
|
||||
return (BufferedImage) img;
|
||||
}
|
||||
|
||||
float softenFactor = 0.05f;
|
||||
final Image temp = new ImageIcon(img).getImage();
|
||||
final BufferedImage bufferedImage = new BufferedImage(
|
||||
temp.getWidth(null),
|
||||
temp.getHeight(null),
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics g = bufferedImage.createGraphics();
|
||||
g.setColor(Color.white);
|
||||
g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null));
|
||||
g.drawImage(temp, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
final float[] softenArray = {0, softenFactor, 0, softenFactor, 1 - (softenFactor * 4), softenFactor, 0,
|
||||
softenFactor, 0};
|
||||
final Kernel kernel = new Kernel(3, 3, softenArray);
|
||||
final ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
|
||||
|
||||
return cOp.filter(bufferedImage, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the difference in percent between two buffered images.
|
||||
*
|
||||
* @param img1 the first image.
|
||||
* @param img2 the second image.
|
||||
* @return difference percent.
|
||||
*/
|
||||
public static float getDifferencePercent(BufferedImage img1, BufferedImage img2) {
|
||||
int width = img1.getWidth();
|
||||
int height = img1.getHeight();
|
||||
|
||||
long diff = 0;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
long maxDiff = 3L * 255 * width * height;
|
||||
|
||||
return (float) (100.0 * diff / maxDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two pixels
|
||||
*
|
||||
* @param rgb1 the first rgb
|
||||
* @param rgb2 the second rgn
|
||||
* @return the difference.
|
||||
*/
|
||||
public static int pixelDiff(int rgb1, int rgb2) {
|
||||
int r1 = (rgb1 >> 16) & 0xff;
|
||||
int g1 = (rgb1 >> 8) & 0xff;
|
||||
int b1 = rgb1 & 0xff;
|
||||
int r2 = (rgb2 >> 16) & 0xff;
|
||||
int g2 = (rgb2 >> 8) & 0xff;
|
||||
int b2 = rgb2 & 0xff;
|
||||
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.github.romankh3.image.comparison.exception;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} that is thrown in case of an image comparison failures.
|
||||
*/
|
||||
public final class ImageComparisonException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ImageComparisonException} with the specified detail message.
|
||||
* The cause is not initialized, and may subsequently be initialized by a call to
|
||||
* Throwable.initCause(java.lang.Throwable).
|
||||
*
|
||||
* @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage()
|
||||
* method.
|
||||
*/
|
||||
public ImageComparisonException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ImageComparisonException} with the specified detail message and cause.
|
||||
* Note that the detail message associated with cause is not automatically incorporated in this {@link
|
||||
* ImageComparisonException}'s detail message.
|
||||
*
|
||||
* @param message the detail message (which is saved for later retrieval by the Throwable.getMessage() method).
|
||||
* @param throwable the cause (which is saved for later retrieval by the Throwable.getCause() method).
|
||||
* (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
|
||||
*/
|
||||
public ImageComparisonException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.github.romankh3.image.comparison.exception;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} that is thrown in case of an image getting failures.
|
||||
*/
|
||||
public class ImageNotFoundException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ImageNotFoundException} with the specified detail message.
|
||||
* The cause is not initialized, and may subsequently be initialized by a call to
|
||||
* Throwable.initCause(java.lang.Throwable).
|
||||
*
|
||||
* @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage()
|
||||
* method.
|
||||
*/
|
||||
public ImageNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.github.romankh3.image.comparison.model;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The area that will be excluded, masked, in the image.
|
||||
*/
|
||||
public class ExcludedAreas {
|
||||
|
||||
/**
|
||||
* The collection of the areas which would be excluded from the comparison.
|
||||
*/
|
||||
private final List<Rectangle> excluded;
|
||||
|
||||
/**
|
||||
* Create empty instance of the {@link ExcludedAreas}.
|
||||
*/
|
||||
public ExcludedAreas() {
|
||||
excluded = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of the {@link ExcludedAreas} with provided {@link Rectangle} areas.
|
||||
*
|
||||
* @param excluded provided collection of the {@link Rectangle} objects.
|
||||
*/
|
||||
public ExcludedAreas(List<Rectangle> excluded) {
|
||||
this.excluded = excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this {@link Point} contains in the {@link ExcludedAreas#excluded}
|
||||
* collection of the {@link Rectangle}.
|
||||
*
|
||||
* @param point the {@link Point} object to be checked.
|
||||
*
|
||||
* @return {@code true} if this {@link Point} contains in areas from {@link ExcludedAreas#excluded}.
|
||||
*/
|
||||
public boolean contains(Point point) {
|
||||
return excluded.stream().anyMatch(rectangle -> rectangle.containsPoint(point));
|
||||
}
|
||||
|
||||
public List<Rectangle> getExcluded() {
|
||||
return excluded;
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
package com.github.romankh3.image.comparison.model;
|
||||
|
||||
import com.github.romankh3.image.comparison.ImageComparisonUtil;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Data transfer objects which contains all the needed data for result of the comparison.
|
||||
*/
|
||||
public class ImageComparisonResult {
|
||||
|
||||
/**
|
||||
* {@link BufferedImage} object of the expected.
|
||||
*/
|
||||
private BufferedImage expected;
|
||||
|
||||
/**
|
||||
* {@link BufferedImage} object of the actual.
|
||||
*/
|
||||
private BufferedImage actual;
|
||||
|
||||
/**
|
||||
* {@link BufferedImage} object of the comparison result.
|
||||
*/
|
||||
private BufferedImage result;
|
||||
|
||||
/**
|
||||
* State of the comparison.
|
||||
*/
|
||||
private ImageComparisonState imageComparisonState;
|
||||
|
||||
/**
|
||||
* The difference percentage between two images.
|
||||
*/
|
||||
private float differencePercent;
|
||||
|
||||
/**
|
||||
* Rectangles of the differences
|
||||
*/
|
||||
private List<Rectangle> rectangles;
|
||||
|
||||
/**
|
||||
* Create default instance of the {@link ImageComparisonResult} with {@link ImageComparisonState#SIZE_MISMATCH}.
|
||||
*
|
||||
* @param expected expected {@link BufferedImage} object.
|
||||
* @param actual actual {@link BufferedImage} object.
|
||||
* @param differencePercent the percent of the differences between images.
|
||||
* @return instance of the {@link ImageComparisonResult} object.
|
||||
*/
|
||||
public static ImageComparisonResult defaultSizeMisMatchResult(BufferedImage expected, BufferedImage actual,
|
||||
float differencePercent) {
|
||||
return new ImageComparisonResult()
|
||||
.setImageComparisonState(ImageComparisonState.SIZE_MISMATCH)
|
||||
.setDifferencePercent(differencePercent)
|
||||
.setExpected(expected)
|
||||
.setActual(actual)
|
||||
.setResult(actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default instance of the {@link ImageComparisonResult} with {@link ImageComparisonState#MISMATCH}.
|
||||
*
|
||||
* @param expected expected {@link BufferedImage} object.
|
||||
* @param actual actual {@link BufferedImage} object.
|
||||
* @param differencePercent the persent of the differences between images.
|
||||
* @return instance of the {@link ImageComparisonResult} object.
|
||||
*/
|
||||
public static ImageComparisonResult defaultMisMatchResult(BufferedImage expected, BufferedImage actual, float differencePercent) {
|
||||
return new ImageComparisonResult()
|
||||
.setImageComparisonState(ImageComparisonState.MISMATCH)
|
||||
.setDifferencePercent(differencePercent)
|
||||
.setExpected(expected)
|
||||
.setActual(actual)
|
||||
.setResult(actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default instance of the {@link ImageComparisonResult} with {@link ImageComparisonState#MATCH}.
|
||||
*
|
||||
* @param expected expected {@link BufferedImage} object.
|
||||
* @param actual actual {@link BufferedImage} object.
|
||||
* @return instance of the {@link ImageComparisonResult} object.
|
||||
*/
|
||||
public static ImageComparisonResult defaultMatchResult(BufferedImage expected, BufferedImage actual) {
|
||||
return new ImageComparisonResult()
|
||||
.setImageComparisonState(ImageComparisonState.MATCH)
|
||||
.setExpected(expected)
|
||||
.setActual(actual)
|
||||
.setResult(actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the image to the provided {@link File} object.
|
||||
*
|
||||
* @param file the provided {@link File} object.
|
||||
* @return this {@link ImageComparisonResult} object.
|
||||
*/
|
||||
public ImageComparisonResult writeResultTo(File file) {
|
||||
ImageComparisonUtil.saveImage(file, result);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BufferedImage getExpected() {
|
||||
return expected;
|
||||
}
|
||||
|
||||
public ImageComparisonResult setExpected(BufferedImage expected) {
|
||||
this.expected = expected;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BufferedImage getActual() {
|
||||
return actual;
|
||||
}
|
||||
|
||||
public ImageComparisonResult setActual(BufferedImage actual) {
|
||||
this.actual = actual;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BufferedImage getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public ImageComparisonResult setResult(BufferedImage result) {
|
||||
this.result = result;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImageComparisonState getImageComparisonState() {
|
||||
return imageComparisonState;
|
||||
}
|
||||
|
||||
public ImageComparisonResult setImageComparisonState(ImageComparisonState imageComparisonState) {
|
||||
this.imageComparisonState = imageComparisonState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getDifferencePercent() {
|
||||
return differencePercent;
|
||||
}
|
||||
|
||||
ImageComparisonResult setDifferencePercent(float differencePercent) {
|
||||
this.differencePercent = differencePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Rectangle> getRectangles() {
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
public ImageComparisonResult setRectangles(List<Rectangle> rectangles) {
|
||||
this.rectangles = rectangles;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.github.romankh3.image.comparison.model;
|
||||
|
||||
/**
|
||||
* Enum for telling the result of the Comparison.
|
||||
* For example {@link ImageComparisonState#MATCH} - means that the images are equal.
|
||||
*/
|
||||
public enum ImageComparisonState {
|
||||
|
||||
/**
|
||||
* Result state of the comparison, where mismatch of the image sizes.
|
||||
*/
|
||||
SIZE_MISMATCH,
|
||||
|
||||
/**
|
||||
* Result state of the comparison, where mismatch of the images.
|
||||
*/
|
||||
MISMATCH,
|
||||
|
||||
/**
|
||||
* Result state of the images, where images are equal, e.g. match.
|
||||
*/
|
||||
MATCH
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package com.github.romankh3.image.comparison.model;
|
||||
|
||||
import static java.lang.Integer.max;
|
||||
import static java.lang.Integer.min;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Object contained data for a rectangle.
|
||||
*/
|
||||
public class Rectangle {
|
||||
|
||||
/**
|
||||
* Left Top {@link Point} on the {@link Rectangle}.
|
||||
*/
|
||||
private Point minPoint;
|
||||
|
||||
/**
|
||||
* Right bottom {@link Point} on the {@link Rectangle}.
|
||||
*/
|
||||
private Point maxPoint;
|
||||
|
||||
/**
|
||||
* Create empty instance of the {@link Rectangle}.
|
||||
*/
|
||||
private Rectangle() {
|
||||
minPoint = new Point();
|
||||
maxPoint = new Point();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create clone object based on the provided {@link Rectangle}.
|
||||
*
|
||||
* @param rectangle provided {@link Rectangle} object.
|
||||
*/
|
||||
public Rectangle(Rectangle rectangle) {
|
||||
this.minPoint = new Point(rectangle.getMinPoint().x, rectangle.getMinPoint().y);
|
||||
this.maxPoint = new Point(rectangle.getMaxPoint().x, rectangle.getMaxPoint().y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of the {@link Rectangle} based on the provided coordinates.
|
||||
*
|
||||
* @param minX minimal X-coordinate.
|
||||
* @param minY minimal Y-coordinate.
|
||||
* @param maxX maximal X-coordinate.
|
||||
* @param maxY maximal Y-coordinate.
|
||||
*/
|
||||
public Rectangle(int minX, int minY, int maxX, int maxY) {
|
||||
this.minPoint = new Point(minX, minY);
|
||||
this.maxPoint = new Point(maxX, maxY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default {@link Rectangle} object.
|
||||
*
|
||||
* @return default rectangle {@link Rectangle}.
|
||||
*/
|
||||
public static Rectangle createDefault() {
|
||||
Rectangle defaultRectangle = new Rectangle();
|
||||
|
||||
defaultRectangle.setDefaultValues();
|
||||
|
||||
return defaultRectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance with zero points.
|
||||
*
|
||||
* @return created {@link Rectangle} instance.
|
||||
*/
|
||||
public static Rectangle createZero() {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.makeZeroRectangle();
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Rectangle} via merging this and that.
|
||||
*
|
||||
* @param that {@link Rectangle} for merging with this.
|
||||
* @return new merged {@link Rectangle}.
|
||||
*/
|
||||
public Rectangle merge(Rectangle that) {
|
||||
return new Rectangle(min(this.getMinPoint().x, that.getMinPoint().x),
|
||||
min(this.getMinPoint().y, that.getMinPoint().y),
|
||||
max(this.getMaxPoint().x, that.getMaxPoint().x),
|
||||
max(this.getMaxPoint().y, that.getMaxPoint().y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is that rectangle overlapp this.
|
||||
*
|
||||
* @param that {@link Rectangle} which checks with this.
|
||||
* @return true if this over lapp that, false otherwise.
|
||||
*/
|
||||
public boolean isOverlapping(Rectangle that) {
|
||||
if (this.getMaxPoint().getY() < that.getMinPoint().getY() ||
|
||||
that.getMaxPoint().getY() < this.getMinPoint().getY()) {
|
||||
return false;
|
||||
}
|
||||
return this.getMaxPoint().getX() >= that.getMinPoint().getX() &&
|
||||
that.getMaxPoint().getX() >= this.getMinPoint().getX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for rectangle.
|
||||
*/
|
||||
public void setDefaultValues() {
|
||||
this.maxPoint = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
this.minPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make zero rectagle.
|
||||
*/
|
||||
public void makeZeroRectangle() {
|
||||
this.minPoint = new Point();
|
||||
this.maxPoint = new Point();
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of the {@link Rectangle}, counted as width x height.
|
||||
*
|
||||
* @return the size of the {@link Rectangle}.
|
||||
*/
|
||||
public Integer size() {
|
||||
return getWidth() * getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the width of the {@link Rectangle}.
|
||||
* Min and max point are included, so real width is +1px
|
||||
*
|
||||
* @return rectangle width.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return maxPoint.x - minPoint.x + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the height of the {@link Rectangle}.
|
||||
* Min and max point are included, so real width is +1px.
|
||||
*
|
||||
* @return rectangle height.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return maxPoint.y - minPoint.y + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the provided {@link Point} contains in the {@link Rectangle}.
|
||||
*
|
||||
* @param point provided {@link Point}.
|
||||
* @return {@code true} if provided {@link Point} contains, {@code false} - otherwise.
|
||||
*/
|
||||
boolean containsPoint(Point point) {
|
||||
return point.x >= minPoint.x && point.x<= maxPoint.x && point.y >= minPoint.y && point.y <= maxPoint.y;
|
||||
}
|
||||
|
||||
public Point getMinPoint() {
|
||||
return minPoint;
|
||||
}
|
||||
|
||||
public void setMinPoint(Point minPoint) {
|
||||
this.minPoint = minPoint;
|
||||
}
|
||||
|
||||
public Point getMaxPoint() {
|
||||
return maxPoint;
|
||||
}
|
||||
|
||||
public void setMaxPoint(Point maxPoint) {
|
||||
this.maxPoint = maxPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Rectangle rectangle = (Rectangle) o;
|
||||
return minPoint.equals(rectangle.minPoint) &&
|
||||
maxPoint.equals(rectangle.maxPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(minPoint, maxPoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.diylc;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.common.PropertyWrapper;
|
||||
import org.diylc.core.IView;
|
||||
|
||||
public class MockView implements IView {
|
||||
|
||||
private Map<Integer, List<String>> messages = new HashMap<Integer, List<String>>();
|
||||
|
||||
@Override
|
||||
public void showMessage(String message, String title, int messageType) {
|
||||
messages.putIfAbsent(messageType, new ArrayList<String>());
|
||||
messages.get(messageType).add(message);
|
||||
}
|
||||
|
||||
public Map<Integer, List<String>> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int showConfirmDialog(String message, String title, int optionType, int messageType) {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean editProperties(List<PropertyWrapper> properties,
|
||||
Set<PropertyWrapper> defaultedProperties, String title) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File promptFileSave() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showInputDialog(String message, String title) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.diylc;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.core.Template;
|
||||
import org.junit.Test;
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class PresenterTests extends TestBase {
|
||||
|
||||
@Test
|
||||
public void testComponentLoad() {
|
||||
Assert.assertTrue(presenter.getComponentTypes() != null && presenter.getComponentTypes().size() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoClickAdd() {
|
||||
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
|
||||
Assert.assertEquals(1, presenter.getCurrentProject().getComponents().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneClickAdd() {
|
||||
instantiateOneClick("Connectivity", "Solder Pad", new Point(1000, 1000));
|
||||
Assert.assertEquals(1, presenter.getCurrentProject().getComponents().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadVariants() {
|
||||
try {
|
||||
ComponentType resistorType = presenter.getComponentTypes().get("Passive").stream().filter(x -> x.getName().equals("Resistor")).findAny().get();
|
||||
List<Template> variantsFor = presenter.getVariantsFor(resistorType);
|
||||
Assert.assertEquals(0, variantsFor.size());
|
||||
presenter.importVariants("./test/resources/variants_test.xml");
|
||||
variantsFor = presenter.getVariantsFor(resistorType);
|
||||
Assert.assertTrue(!variantsFor.isEmpty());
|
||||
} catch (IOException e) {
|
||||
Assert.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBlocks() {
|
||||
try {
|
||||
int blocks = presenter.importBlocks("./test/resources/blocks_test.xml");
|
||||
Assert.assertTrue(blocks > 0);
|
||||
} catch (IOException e) {
|
||||
Assert.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
package org.diylc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
|
||||
import org.diylc.appframework.update.VersionNumber;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.netlist.Netlist;
|
||||
import org.diylc.netlist.NetlistException;
|
||||
import org.diylc.presenter.Presenter;
|
||||
import org.diylc.swing.plugins.file.ProjectDrawingProvider;
|
||||
import org.diylc.swingframework.export.DrawingExporter;
|
||||
import com.github.romankh3.image.comparison.ImageComparison;
|
||||
import com.github.romankh3.image.comparison.ImageComparisonUtil;
|
||||
import com.github.romankh3.image.comparison.model.ImageComparisonResult;
|
||||
import com.github.romankh3.image.comparison.model.ImageComparisonState;
|
||||
|
||||
public class RegressionTestRunner {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("No path and command provided");
|
||||
return;
|
||||
}
|
||||
//
|
||||
// URL url = DIYLCStarter.class.getResource("log4j.properties");
|
||||
// Properties properties = new Properties();
|
||||
// try {
|
||||
// properties.load(url.openStream());
|
||||
// PropertyConfigurator.configure(properties);
|
||||
// } catch (Exception e) {
|
||||
// System.err.println("Could not initialize log4j configuration: " + e.getMessage());
|
||||
// }
|
||||
|
||||
ConfigurationManager.getInstance().initialize("diylc-test");
|
||||
|
||||
String basePath = args[0];
|
||||
String command = args[1];
|
||||
String filter = null;
|
||||
if (args.length > 2) {
|
||||
filter = args[2];
|
||||
}
|
||||
|
||||
System.out.println("Running command " + command + " in: " + basePath);
|
||||
File inputsDir = new File(basePath + File.separator + "input");
|
||||
List<RegressionTestResult> collect =
|
||||
visit(inputsDir, basePath, command, filter).collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
if ("TEST".equalsIgnoreCase(command)) {
|
||||
createReport(collect, basePath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
|
||||
System.out.println("Finished!");
|
||||
}
|
||||
|
||||
private static void createReport(List<RegressionTestResult> results, String basePath)
|
||||
throws IOException {
|
||||
IView view = new MockView();
|
||||
Presenter presenter = new Presenter(view, InMemoryConfigurationManager.getInstance());
|
||||
VersionNumber currentVersionNumber = presenter.getCurrentVersionNumber();
|
||||
|
||||
String fileName = basePath + File.separator + "reports" + File.separator
|
||||
+ LocalDateTime.now().toString().replace(":", "_") + "-" + "V"
|
||||
+ currentVersionNumber.toString().replace('.', '_') + ".csv";
|
||||
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
|
||||
|
||||
long okCount = results.stream().filter(x -> x.ok).count();
|
||||
long failCount = results.size() - okCount;
|
||||
long totalTime = results.stream().map(x -> x.duration).reduce(0l, (a, b) -> a + b);
|
||||
long avgTime = totalTime / results.size();
|
||||
|
||||
writer.write("OK,Failed,Total Time,Avg Time");
|
||||
writer.newLine();
|
||||
writer.write(Long.toString(okCount));
|
||||
writer.write(",");
|
||||
writer.write(Long.toString(failCount));
|
||||
writer.write(",");
|
||||
writer.write(Long.toString(totalTime));
|
||||
writer.write(",");
|
||||
writer.write(Long.toString(avgTime));
|
||||
writer.newLine();
|
||||
writer.newLine();
|
||||
writer.newLine();
|
||||
|
||||
writer.write("File,Status,Message,Time");
|
||||
writer.newLine();
|
||||
results.stream().sorted(Comparator.comparing(x -> x.fileName)).forEach(item -> {
|
||||
try {
|
||||
writer.write(item.fileName);
|
||||
writer.write(",");
|
||||
writer.write(item.ok ? "OK" : "FAILED");
|
||||
writer.write(",");
|
||||
writer.write(item.message == null ? "" : item.message);
|
||||
writer.write(",");
|
||||
writer.write(Long.toString(item.duration));
|
||||
writer.newLine();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
writer.close();
|
||||
|
||||
System.out.println("OK files: " + okCount + "; Failed files: " + failCount + "; totalTime: "
|
||||
+ (totalTime / 1000) + "s" + "; avgTime: " + avgTime + "ms");
|
||||
|
||||
System.out.println("Saved test report to: " + fileName);
|
||||
}
|
||||
|
||||
private static Stream<RegressionTestResult> visit(File inputsDir, String basePath, String command,
|
||||
String filter) {
|
||||
File[] files = inputsDir.listFiles();
|
||||
return Arrays.stream(files).parallel().flatMap(file -> {
|
||||
if (file.isDirectory()) {
|
||||
return visit(file, basePath, command, filter);
|
||||
} else {
|
||||
if (!file.getName().endsWith(".diy")) {
|
||||
return Stream.empty();
|
||||
}
|
||||
if (filter != null) {
|
||||
Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(file.getAbsolutePath().substring(basePath.length()));
|
||||
boolean matchFound = matcher.find();
|
||||
if (!matchFound) {
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
RegressionTestResult testResult = processFile(file, command);
|
||||
System.out.println(
|
||||
testResult.fileName + ": " + (testResult.message == null ? "OK" : testResult.message));
|
||||
return Stream.of(testResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static RegressionTestResult processFile(File file, String command) {
|
||||
String prefix = "File: " + file.getName() + " status: ";
|
||||
try {
|
||||
MockView view = new MockView();
|
||||
Presenter presenter = new Presenter(view, InMemoryConfigurationManager.getInstance());
|
||||
ProjectDrawingProvider drawingProvider =
|
||||
new ProjectDrawingProvider(presenter, false, true, false);
|
||||
presenter.loadProjectFromFile(file.getAbsolutePath());
|
||||
if ("PREPARE".equalsIgnoreCase(command)) {
|
||||
// generate PNG input image for comparison
|
||||
return prepareInputs(file, drawingProvider, presenter);
|
||||
} else if ("TEST".equalsIgnoreCase(command)) {
|
||||
return testOutputs(file, prefix, presenter, drawingProvider, view);
|
||||
}
|
||||
return new RegressionTestResult(file.getName()).failed("Command not recognized");
|
||||
} catch (Exception e) {
|
||||
System.out.println(prefix + "ERROR: " + e.getMessage());
|
||||
return new RegressionTestResult(file.getName()).failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static RegressionTestResult testOutputs(File file, String prefix, Presenter presenter,
|
||||
ProjectDrawingProvider drawingProvider, MockView view) throws NetlistException {
|
||||
RegressionTestResult res = new RegressionTestResult(file.getName());
|
||||
File baseDir = file.getParentFile().getParentFile().getParentFile().getParentFile();
|
||||
File outputDir = new File(baseDir.getAbsolutePath() + File.separator + "output" + File.separator
|
||||
+ file.getParentFile().getParentFile().getName());
|
||||
File inputDir = file.getParentFile().getParentFile();
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
// test save
|
||||
File outputFile = new File(
|
||||
outputDir.getAbsolutePath() + File.separator + "diy" + File.separator + file.getName());
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (outputFile.exists()) {
|
||||
outputFile.delete();
|
||||
}
|
||||
presenter.saveProjectToFile(outputFile.getAbsolutePath(), false);
|
||||
|
||||
// test export png
|
||||
File pngOutputFile = new File(outputDir.getAbsolutePath() + File.separator + "png"
|
||||
+ File.separator + file.getName().replace(".diy", ".png"));
|
||||
pngOutputFile.getParentFile().mkdirs();
|
||||
if (pngOutputFile.exists()) {
|
||||
pngOutputFile.delete();
|
||||
}
|
||||
DrawingExporter.getInstance().exportPNG(drawingProvider, pngOutputFile);
|
||||
File pngInputFile = new File(inputDir.getAbsolutePath() + File.separator + "png"
|
||||
+ File.separator + (file.getName().replace(".diy", ".png")));
|
||||
if (!pngInputFile.exists()) {
|
||||
res.failed("Input image does not exist!");
|
||||
}
|
||||
|
||||
BufferedImage expectedImage =
|
||||
ImageComparisonUtil.readImageFromResources(pngInputFile.getAbsolutePath());
|
||||
BufferedImage actualImage =
|
||||
ImageComparisonUtil.readImageFromResources(pngOutputFile.getAbsolutePath());
|
||||
|
||||
File pngDiffFile = new File(outputDir.getAbsolutePath() + File.separator + "diff"
|
||||
+ File.separator + file.getName().replace(".diy", ".png"));
|
||||
pngDiffFile.getParentFile().mkdirs();
|
||||
|
||||
ImageComparisonResult imageComparisonResult =
|
||||
new ImageComparison(expectedImage, actualImage, pngDiffFile).compareImages();
|
||||
|
||||
if (ImageComparisonState.MATCH != imageComparisonResult.getImageComparisonState()) {
|
||||
res.failed("Images do not match!");
|
||||
}
|
||||
|
||||
testNetlist(file, presenter, res, outputDir, inputDir, true);
|
||||
// if (netlistResultWithSwitches != null)
|
||||
// res.merge(netlistResultWithSwitches);
|
||||
|
||||
testNetlist(file, presenter, res, outputDir, inputDir, false);
|
||||
// if (netlistResultWithoutSwitches != null)
|
||||
// res.merge(netlistResultWithoutSwitches);
|
||||
|
||||
// List<INetlistAnalyzer> summarizers = presenter.getNetlistAnalyzers();
|
||||
// if (summarizers != null) {
|
||||
// List<Netlist> netlists = presenter.extractNetlists(true);
|
||||
//
|
||||
// for (INetlistAnalyzer summarizer : summarizers) {
|
||||
// File summaryOutputDir =
|
||||
// new File(outputDir.getAbsolutePath() + File.separator + summarizer.getShortName());
|
||||
// File summaryOutputFile = new File(summaryOutputDir.getAbsolutePath() + File.separator
|
||||
// + file.getName().replace(".diy", ".txt"));
|
||||
// summaryOutputDir.mkdirs();
|
||||
// if (summaryOutputFile.exists()) {
|
||||
// summaryOutputFile.delete();
|
||||
// }
|
||||
//
|
||||
// File summaryInputDir = new File(file.getParentFile().getParentFile().getAbsolutePath()
|
||||
// + File.separator + summarizer.getShortName());
|
||||
// File summaryInputFile = new File(summaryInputDir.getAbsolutePath() + File.separator
|
||||
// + file.getName().replace(".diy", ".txt"));
|
||||
//
|
||||
// if (!summaryInputFile.exists()) {
|
||||
// return res.failed("Summary input file not found for: " + summarizer.getShortName());
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// List<String> outputLines = new ArrayList<String>();
|
||||
// try {
|
||||
// writeSummariesToFile(netlists, summarizer, summaryOutputFile);
|
||||
// outputLines = Files.readAllLines(Paths.get(summaryOutputFile.getAbsolutePath()));
|
||||
// } catch (Exception e) {
|
||||
// try (BufferedWriter writer = new BufferedWriter(
|
||||
// new OutputStreamWriter(new FileOutputStream(summaryOutputFile, true), "UTF-8"))) {
|
||||
// String line = e.getClass().getCanonicalName() + ": " + e.getMessage();
|
||||
// writer.write(line);
|
||||
// outputLines.add(line);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// List<String> inputLines =
|
||||
// Files.readAllLines(Paths.get(summaryInputFile.getAbsolutePath()));
|
||||
//
|
||||
// if (inputLines.size() != outputLines.size()) {
|
||||
// return res.failed(
|
||||
// "Summary input and output files do not match for: " + summarizer.getShortName());
|
||||
// }
|
||||
// for (int i = 0; i < inputLines.size(); i++) {
|
||||
// if (!inputLines.get(i).equals(outputLines.get(i))) {
|
||||
// return res.failed(
|
||||
// "Summary input and output files do not match for: " + summarizer.getShortName());
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// return res.failed(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
String message = null;
|
||||
if (!view.getMessages().isEmpty()) {
|
||||
message = view.getMessages().entrySet().stream().flatMap(x -> x.getValue().stream())
|
||||
.reduce("", String::concat);
|
||||
}
|
||||
|
||||
if (!res.ok)
|
||||
return res;
|
||||
|
||||
return res.succedded(message);
|
||||
}
|
||||
|
||||
private static RegressionTestResult testNetlist(File file, Presenter presenter, RegressionTestResult res,
|
||||
File outputDir, File inputDir, boolean includeSwitches) throws NetlistException {
|
||||
File netlistOutputDir = new File(outputDir.getAbsolutePath() + File.separator + "netlist");
|
||||
File netlistOutputFile = new File(netlistOutputDir.getAbsolutePath() + File.separator
|
||||
+ file.getName().replace(".diy", includeSwitches ? "_incl_switches.txt" : "_excl_switches.txt"));
|
||||
File netlistInputFile = new File(inputDir.getAbsolutePath() + File.separator + "netlist"
|
||||
+ File.separator + (file.getName().replace(".diy", includeSwitches ? "_incl_switches.txt" : "_excl_switches.txt")));
|
||||
|
||||
netlistOutputDir.mkdirs();
|
||||
try {
|
||||
if (netlistOutputFile.exists()) {
|
||||
netlistOutputFile.delete();
|
||||
}
|
||||
try {
|
||||
List<Netlist> netlists = presenter.extractNetlists(includeSwitches);
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(netlistOutputFile, true), "UTF-8"))) {
|
||||
for (Netlist netlist : netlists) {
|
||||
writer.write(netlist.toString());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(netlistOutputFile, true), "UTF-8"))) {
|
||||
writer.write(e.getClass().getCanonicalName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
List<String> inputLines = Files.readAllLines(Paths.get(netlistInputFile.getAbsolutePath()));
|
||||
List<String> outputLines = Files.readAllLines(Paths.get(netlistOutputFile.getAbsolutePath()));
|
||||
|
||||
if (inputLines.size() != outputLines.size()) {
|
||||
return res.failed("Netlists do not match " + (includeSwitches ? "with switches" : "without switches"));
|
||||
}
|
||||
for (int i = 0; i < inputLines.size(); i++) {
|
||||
if (!inputLines.get(i).equals(outputLines.get(i))) {
|
||||
return res.failed("Netlists do not match " + (includeSwitches ? "with switches" : "without switches"));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return res.failed(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RegressionTestResult prepareInputs(File file,
|
||||
ProjectDrawingProvider drawingProvider, IPlugInPort plugInPort) {
|
||||
RegressionTestResult res = new RegressionTestResult(file.getName());
|
||||
try {
|
||||
File pngDir =
|
||||
new File(file.getParentFile().getParentFile().getAbsolutePath() + File.separator + "png");
|
||||
File pngFile = new File(
|
||||
pngDir.getAbsolutePath() + File.separator + file.getName().replace(".diy", ".png"));
|
||||
pngFile.getParentFile().mkdirs();
|
||||
|
||||
// always export image to ensure that rendering is done and continuity areas are populated
|
||||
DrawingExporter.getInstance().exportPNG(drawingProvider, pngFile);
|
||||
|
||||
prepareNetlist(file, plugInPort, true);
|
||||
prepareNetlist(file, plugInPort, false);
|
||||
|
||||
// List<INetlistAnalyzer> summarizers = plugInPort.getNetlistAnalyzers();
|
||||
// if (summarizers != null) {
|
||||
//
|
||||
//
|
||||
// for (INetlistAnalyzer summarizer : summarizers) {
|
||||
// summaryDir = new File(file.getParentFile().getParentFile().getAbsolutePath()
|
||||
// + File.separator + summarizer.getShortName());
|
||||
// summaryFile = new File(summaryDir.getAbsolutePath() + File.separator
|
||||
// + file.getName().replace(".diy", ".txt"));
|
||||
// summaryDir.mkdirs();
|
||||
// if (!summaryFile.exists()) {
|
||||
// try {
|
||||
// writeSummariesToFile(netlists, summarizer, summaryFile);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// try (BufferedWriter writer = new BufferedWriter(
|
||||
// new OutputStreamWriter(new FileOutputStream(summaryFile, true), "UTF-8"))) {
|
||||
// writer.write(e.getClass().getCanonicalName() + ": " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
return res.failed(e.getMessage());
|
||||
}
|
||||
return res.succedded();
|
||||
}
|
||||
|
||||
private static void prepareNetlist(File file, IPlugInPort plugInPort, boolean includeSwitches)
|
||||
throws IOException, UnsupportedEncodingException, FileNotFoundException, NetlistException {
|
||||
|
||||
File netlistDir = new File(
|
||||
file.getParentFile().getParentFile().getAbsolutePath() + File.separator + "netlist");
|
||||
File netlistFile = new File(
|
||||
netlistDir.getAbsolutePath() + File.separator + file.getName().replace(".diy", includeSwitches ? "_incl_switches.txt" : "_excl_switches.txt"));
|
||||
|
||||
netlistDir.mkdirs();
|
||||
if (!netlistFile.exists()) {
|
||||
try {
|
||||
List<Netlist> netlists = plugInPort.extractNetlists(includeSwitches);
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(netlistFile, true), "UTF-8"))) {
|
||||
for (Netlist netlist : netlists) {
|
||||
writer.write(netlist.toString());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(netlistFile, true), "UTF-8"))) {
|
||||
writer.write(e.getClass().getCanonicalName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private static void writeSummariesToFile(List<Netlist> netlists, INetlistAnalyzer summarizer,
|
||||
// File summaryFile)
|
||||
// throws TreeException, IOException, UnsupportedEncodingException, FileNotFoundException {
|
||||
// List<Summary> summaries = summarizer.summarize(netlists, null);
|
||||
//
|
||||
// try (BufferedWriter writer = new BufferedWriter(
|
||||
// new OutputStreamWriter(new FileOutputStream(summaryFile, true), "UTF-8"))) {
|
||||
// for (Summary summary : summaries) {
|
||||
// writer.write("<b>Switch configuration: ");
|
||||
// writer.write(summary.getNetlist().getSwitchSetup().toString());
|
||||
// writer.write("</b><br><br>");
|
||||
// writer.write(summary.getSummary());
|
||||
// writer.newLine();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
static class RegressionTestResult {
|
||||
boolean ok;
|
||||
String message;
|
||||
long startTime;
|
||||
long duration;
|
||||
String fileName;
|
||||
|
||||
public RegressionTestResult(String fileName) {
|
||||
this.fileName = fileName;
|
||||
startTime = System.currentTimeMillis();
|
||||
ok = true;
|
||||
}
|
||||
|
||||
public RegressionTestResult succedded() {
|
||||
return succedded(null);
|
||||
}
|
||||
|
||||
public RegressionTestResult succedded(String message) {
|
||||
this.ok = true;
|
||||
this.message = message;
|
||||
this.duration = System.currentTimeMillis() - this.startTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegressionTestResult failed(String message) {
|
||||
this.ok = false;
|
||||
if (this.message == null)
|
||||
this.message = message;
|
||||
else if (message != null)
|
||||
this.message = this.message + ";" + message;
|
||||
this.duration = System.currentTimeMillis() - this.startTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
// public void merge(RegressionTestResult other) {
|
||||
// this.ok = this.ok && other.ok;
|
||||
// this.duration = System.currentTimeMillis() - this.startTime;
|
||||
// if (this.message == null)
|
||||
// this.message = other.message;
|
||||
// else if (other.message != null)
|
||||
// this.message = this.message + ";" + other.message;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.diylc;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.presenter.Presenter;
|
||||
|
||||
public class TestBase {
|
||||
|
||||
protected Presenter presenter;
|
||||
protected IView view;
|
||||
|
||||
public TestBase() {
|
||||
URL url = DIYLCStarter.class.getResource("log4j.properties");
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(url.openStream());
|
||||
PropertyConfigurator.configure(properties);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not initialize log4j configuration: " + e.getMessage());
|
||||
}
|
||||
|
||||
ConfigurationManager.getInstance().initialize("diylc-test");
|
||||
view = new MockView();
|
||||
presenter = new Presenter(view, InMemoryConfigurationManager.getInstance());
|
||||
}
|
||||
|
||||
protected void instantiateOneClick(String category, String name, Point point1) {
|
||||
Map<String, List<ComponentType>> componentTypes = presenter.getComponentTypes();
|
||||
ComponentType resistorType = componentTypes.get(category).stream().filter(x -> x.getName().equals(name)).findFirst().get();
|
||||
presenter.setNewComponentTypeSlot(resistorType, null, null, false);
|
||||
presenter.mouseClicked(point1, MouseEvent.BUTTON1, false, false, false, 1);
|
||||
}
|
||||
|
||||
protected void instantiateTwoClick(String category, String name, Point point1, Point point2) {
|
||||
Map<String, List<ComponentType>> componentTypes = presenter.getComponentTypes();
|
||||
ComponentType resistorType = componentTypes.get(category).stream().filter(x -> x.getName().equals(name)).findFirst().get();
|
||||
presenter.setNewComponentTypeSlot(resistorType, null, null, false);
|
||||
presenter.mouseClicked(point1, MouseEvent.BUTTON1, false, false, false, 1);
|
||||
presenter.mouseMoved(point2, false, false, false);
|
||||
presenter.mouseClicked(point2, MouseEvent.BUTTON1, false, false, false, 1);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user