diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml
index 0209c3c0..aee882c5 100644
--- a/diylc/diylc-core/src/org/diylc/presenter/update.xml
+++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml
@@ -3731,7 +3731,7 @@
22
0
- 2022-02-27 00:00:00.000 CET
+ 2022-03-02 00:00:00.000 CET
@@ -3744,12 +3744,16 @@
IMPROVEMENT
- Separate WIRING layer (containing Hookup Wire and Twisted Wire at the moment) from TRACE layer and render it above the COMPONENT layer by default
+ Separate WIRING layer (containing 'Hookup Wire' and 'Twisted Wire' at the moment) from TRACE layer and render it above the COMPONENT layer by default
IMPROVEMENT
Upgrade OSX application package built-in Java to JRE 1.8.0_321-b07
+
+ NEW_FEATURE
+ Added regression test suite that validates each release candidate version against a barrage of different DIY files
+
https://github.com/bancika/diy-layout-creator/releases
diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar
index 574299bd..9332174d 100644
Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ
diff --git a/diylc/diylc-swing/RegressionTestRunner.launch b/diylc/diylc-swing/RegressionTestRunner.launch
new file mode 100644
index 00000000..49fd3296
--- /dev/null
+++ b/diylc/diylc-swing/RegressionTestRunner.launch
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar
index 574299bd..9332174d 100644
Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ
diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar
index f0dc52b8..22f5f014 100644
Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparison.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparison.java
new file mode 100644
index 00000000..728c6ef6
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparison.java
@@ -0,0 +1,708 @@
+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;
+
+ /**
+ * The difference in percent between two images.
+ */
+ private float differencePercent;
+
+ /**
+ * 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 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 populateRectangles() {
+ long countOfDifferentPixels = populateTheMatrixOfTheDifferences();
+
+ if (countOfDifferentPixels == 0) {
+ return emptyList();
+ }
+
+ if (isAllowedPercentOfDifferentPixels(countOfDifferentPixels)) {
+ return emptyList();
+ }
+ groupRegions();
+ List 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 mergeRectangles(List 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 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 rectangles, Graphics2D graphics) {
+ List 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 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 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 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 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 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;
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparisonUtil.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparisonUtil.java
new file mode 100644
index 00000000..4d1f5f15
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/ImageComparisonUtil.java
@@ -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);
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageComparisonException.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageComparisonException.java
new file mode 100644
index 00000000..b53006a7
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageComparisonException.java
@@ -0,0 +1,32 @@
+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 {
+
+ /**
+ * 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);
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageNotFoundException.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageNotFoundException.java
new file mode 100644
index 00000000..9c418a74
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/exception/ImageNotFoundException.java
@@ -0,0 +1,19 @@
+package com.github.romankh3.image.comparison.exception;
+
+/**
+ * {@link RuntimeException} that is thrown in case of an image getting failures.
+ */
+public class ImageNotFoundException extends RuntimeException {
+
+ /**
+ * 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);
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ExcludedAreas.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ExcludedAreas.java
new file mode 100644
index 00000000..b71a95a6
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ExcludedAreas.java
@@ -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 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 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 getExcluded() {
+ return excluded;
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonResult.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonResult.java
new file mode 100644
index 00000000..00136408
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonResult.java
@@ -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 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 getRectangles() {
+ return rectangles;
+ }
+
+ public ImageComparisonResult setRectangles(List rectangles) {
+ this.rectangles = rectangles;
+ return this;
+ }
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonState.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonState.java
new file mode 100644
index 00000000..968c5bb7
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/ImageComparisonState.java
@@ -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
+}
diff --git a/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/Rectangle.java b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/Rectangle.java
new file mode 100644
index 00000000..d6673627
--- /dev/null
+++ b/diylc/diylc-swing/test/com/github/romankh3/image/comparison/model/Rectangle.java
@@ -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);
+ }
+}
diff --git a/diylc/diylc-swing/test/org/diylc/RegressionTestRunner.java b/diylc/diylc-swing/test/org/diylc/RegressionTestRunner.java
new file mode 100644
index 00000000..796974df
--- /dev/null
+++ b/diylc/diylc-swing/test/org/diylc/RegressionTestRunner.java
@@ -0,0 +1,100 @@
+package org.diylc;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import org.diylc.appframework.miscutils.ConfigurationManager;
+import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
+import org.diylc.core.IView;
+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;
+ }
+
+ ConfigurationManager.getInstance().initialize("diylc-test");
+
+ String basePath = args[0];
+ String command = args[1];
+ System.out.println("Running command " + command + " in: " + basePath);
+ File inputsDir = new File(basePath + File.separator + "input" + File.separator + "diy");
+ if (!inputsDir.exists()) {
+ System.out.println("Could not find the input directory: " + inputsDir.getAbsolutePath());
+ return;
+ }
+ File[] files = inputsDir.listFiles();
+ int okCount = 0;
+ int failCount = 0;
+ for (File file : files) {
+ boolean fileOk = processFile(file, basePath, command);
+ if (fileOk) {
+ okCount++;
+ } else {
+ failCount++;
+ }
+ }
+ System.out.println("Finished! OK files: " + okCount + "; Failed files: " + failCount);
+ }
+
+ private static boolean processFile(File file, String basePath, String command) {
+ System.out.print("Processing file: " + file.getName() + "...");
+ try {
+ IView 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
+ File pngFile = new File(basePath + File.separator + "input" + File.separator + "png" + File.separator + (file.getName().replace(".diy", ".png")));
+ if (!pngFile.exists()) {
+ DrawingExporter.getInstance().exportPNG(drawingProvider, pngFile);
+ }
+ } else if ("TEST".equalsIgnoreCase(command)) {
+ // test save
+ File outputFile = new File(basePath + File.separator + "output" + File.separator + "diy" + File.separator + file.getName());
+ if (outputFile.exists()) {
+ outputFile.delete();
+ }
+ presenter.saveProjectToFile(outputFile.getAbsolutePath(), false);
+
+ // test export png
+ File pngOutputFile = new File(basePath + File.separator + "output" + File.separator + "png" + File.separator + (file.getName().replace(".diy", ".png")));
+ if (pngOutputFile.exists()) {
+ pngOutputFile.delete();
+ }
+ DrawingExporter.getInstance().exportPNG(drawingProvider, pngOutputFile);
+ File pngInputFile = new File(basePath + File.separator + "input" + File.separator + "png" + File.separator + (file.getName().replace(".diy", ".png")));
+ if (!pngInputFile.exists()) {
+ System.out.println("Input image does not exist!");
+ return false;
+ }
+
+ BufferedImage expectedImage = ImageComparisonUtil.readImageFromResources(pngInputFile.getAbsolutePath());
+ BufferedImage actualImage = ImageComparisonUtil.readImageFromResources(pngOutputFile.getAbsolutePath());
+
+ //Create ImageComparison object and compare the images.
+ ImageComparisonResult imageComparisonResult = new ImageComparison(expectedImage, actualImage).compareImages();
+
+ //Check the result
+ if (ImageComparisonState.MATCH != imageComparisonResult.getImageComparisonState()) {
+ System.out.println("Images do not match!");
+ return false;
+ }
+ }
+ System.out.println("OK");
+ return true;
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ return false;
+ }
+ }
+}
diff --git a/diylc/diylc-swing/test/org/diylc/cloud-inputs.zip b/diylc/diylc-swing/test/org/diylc/cloud-inputs.zip
new file mode 100644
index 00000000..a33ce434
Binary files /dev/null and b/diylc/diylc-swing/test/org/diylc/cloud-inputs.zip differ