Skip to content

Commit

Permalink
Fix failing build due to putting unit test for renderer in unit inste…
Browse files Browse the repository at this point in the history
…ad of functional

This was done by fixing a TODO comment ("move to separate class ImageTestUtils")
in the MapCSSRendererTest.

git-svn-id: https://josm.openstreetmap.de/svn/trunk@19221 0c6e7542-c601-0410-84e7-c038aed88b3b
  • Loading branch information
taylor.smock committed Sep 12, 2024
1 parent 3763d44 commit 0826abb
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.mappaint;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals;

import java.awt.Color;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -39,7 +34,6 @@
import org.openstreetmap.josm.io.OsmReader;
import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
import org.openstreetmap.josm.testutils.annotations.Projection;
import org.openstreetmap.josm.tools.ColorHelper;
import org.openstreetmap.josm.tools.Utils;

/**
Expand All @@ -58,9 +52,6 @@ public class MapCSSRendererTest {
private static final Bounds AREA_DEFAULT = new Bounds(0, 0, 1, 1);
private static final int IMAGE_SIZE = 256;

// development flag - set to true in order to update all reference images
private static final boolean UPDATE_ALL = false;

/**
* The different configurations of this test.
*
Expand Down Expand Up @@ -201,111 +192,6 @@ void testRender(TestConfig testConfig, String ignored) throws Exception {
});
}

/**
* Compares the reference image file with the actual images given as {@link BufferedImage}.
* @param testIdentifier a test identifier for error messages
* @param referenceImageFile the reference image file to be read using {@link ImageIO#read(File)}
* @param image the actual image
* @param thresholdPixels maximum number of differing pixels
* @param thresholdTotalColorDiff maximum sum of color value differences
* @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null
* @throws IOException in case of I/O error
*/
public static void assertImageEquals(
String testIdentifier, File referenceImageFile, BufferedImage image,
int thresholdPixels, int thresholdTotalColorDiff, Consumer<BufferedImage> diffImageConsumer) throws IOException {

// TODO move to separate class ImageTestUtils
if (UPDATE_ALL) {
ImageIO.write(image, "png", referenceImageFile);
return;
}
final BufferedImage reference = ImageIO.read(referenceImageFile);
assertImageEquals(testIdentifier, reference, image, thresholdPixels, thresholdTotalColorDiff, diffImageConsumer);
}

/**
* Compares the reference image file with the actual images given as {@link BufferedImage}.
* @param testIdentifier a test identifier for error messages
* @param reference the reference image
* @param image the actual image
* @param thresholdPixels maximum number of differing pixels
* @param thresholdTotalColorDiff maximum sum of color value differences
* @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null
*/
public static void assertImageEquals(String testIdentifier, BufferedImage reference, BufferedImage image,
int thresholdPixels, int thresholdTotalColorDiff, Consumer<BufferedImage> diffImageConsumer) {
assertEquals(reference.getWidth(), image.getWidth());
assertEquals(reference.getHeight(), image.getHeight());

StringBuilder differences = new StringBuilder();
ArrayList<Point> differencePoints = new ArrayList<>();
int colorDiffSum = 0;

for (int y = 0; y < reference.getHeight(); y++) {
for (int x = 0; x < reference.getWidth(); x++) {
int expected = reference.getRGB(x, y);
int result = image.getRGB(x, y);
int expectedAlpha = expected >> 24;
boolean colorsAreSame = expectedAlpha == 0 ? result >> 24 == 0 : expected == result;
if (!colorsAreSame) {
Color expectedColor = new Color(expected, true);
Color resultColor = new Color(result, true);
int colorDiff = Math.abs(expectedColor.getRed() - resultColor.getRed())
+ Math.abs(expectedColor.getGreen() - resultColor.getGreen())
+ Math.abs(expectedColor.getBlue() - resultColor.getBlue());
int alphaDiff = Math.abs(expectedColor.getAlpha() - resultColor.getAlpha());
// Ignore small alpha differences due to Java versions, rendering libraries and so on
if (alphaDiff <= 20) {
alphaDiff = 0;
}
// Ignore small color differences for the same reasons, but also completely for almost-transparent pixels
if (colorDiff <= 15 || resultColor.getAlpha() <= 20) {
colorDiff = 0;
}
if (colorDiff + alphaDiff > 0) {
differencePoints.add(new Point(x, y));
if (differences.length() < 2000) {
differences.append("\nDifference at ")
.append(x)
.append(",")
.append(y)
.append(": Expected ")
.append(ColorHelper.color2html(expectedColor))
.append(" but got ")
.append(ColorHelper.color2html(resultColor))
.append(" (color diff is ")
.append(colorDiff)
.append(", alpha diff is ")
.append(alphaDiff)
.append(")");
}
}
colorDiffSum += colorDiff + alphaDiff;
}
}
}

if (differencePoints.size() > thresholdPixels || colorDiffSum > thresholdTotalColorDiff) {
// Add a nice image that highlights the differences:
BufferedImage diffImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (Point p : differencePoints) {
diffImage.setRGB(p.x, p.y, 0xffff0000);
}
if (diffImageConsumer != null) {
diffImageConsumer.accept(diffImage);
}

if (differencePoints.size() > thresholdPixels) {
fail(MessageFormat.format("Images for test {0} differ at {1} points, threshold is {2}: {3}",
testIdentifier, differencePoints.size(), thresholdPixels, differences.toString()));
} else {
fail(MessageFormat.format("Images for test {0} differ too much in color, value is {1}, permitted threshold is {2}: {3}",
testIdentifier, colorDiffSum, thresholdTotalColorDiff, differences.toString()));
}
}
}

private void loadPrimitiveStyle(OsmPrimitive n) {
n.setHighlighted(n.isKeyTrue("highlight"));
if (n.isKeyTrue("disabled")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals;
import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals;

import java.awt.Dimension;
import java.awt.Image;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm.visitor.paint;

import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals;
import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals;
import static org.openstreetmap.josm.testutils.ImageTestUtils.writeDebugImages;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
Expand All @@ -20,8 +17,6 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import org.apache.commons.jcs3.access.CacheAccess;
import org.awaitility.Awaitility;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down Expand Up @@ -72,7 +67,7 @@ static Stream<Arguments> testRender() {
.flatMap(generateTests);
}

@ParameterizedTest(name = "{0} - {2}")
@ParameterizedTest(name = "{2} - {0}")
@MethodSource
void testRender(String testIdentifier, final Supplier<DataSet> dataSetSupplier, final TileZXY tile)
throws InterruptedException, ExecutionException {
Expand Down Expand Up @@ -117,20 +112,10 @@ public int getHeight() {
null, 0, image.getWidth())).anyMatch(i -> i != 0);
}).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().image()));
try {
assertImageEquals(testIdentifier, oldRenderStyle, newRenderStyle, 0, 0, diff -> {
try {
if (!Files.isDirectory(Paths.get(TestUtils.getTestDataRoot(), "output"))) {
Files.createDirectories(Paths.get(TestUtils.getTestDataRoot(), "output"));
}
final String basename = TestUtils.getTestDataRoot() + "output/" +
testIdentifier + ' ' + tile.zoom() + '-' + tile.x() + '-' + tile.y();
ImageIO.write(diff, "png", new File(basename + "-diff.png"));
ImageIO.write(newRenderStyle, "png", new File(basename + "-new.png"));
ImageIO.write(oldRenderStyle, "png", new File(basename + "-old.png"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
assertImageEquals(testIdentifier, oldRenderStyle, newRenderStyle, 0, 0, diff ->
writeDebugImages(Paths.get(TestUtils.getTestDataRoot(), "output"),
testIdentifier + ' ' + tile.zoom() + '-' + tile.x() + '-' + tile.y(), diff, oldRenderStyle, newRenderStyle)
);
} finally {
cache.clear();
}
Expand Down
165 changes: 165 additions & 0 deletions test/unit/org/openstreetmap/josm/testutils/ImageTestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.testutils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.function.Consumer;

import javax.imageio.ImageIO;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.openstreetmap.josm.tools.ColorHelper;

/**
* Test helper class for images
*/
public final class ImageTestUtils {
// development flag - set to true in order to update all reference images
private static final boolean UPDATE_ALL = false;

private ImageTestUtils() {
/* Hide constructor */
}

/**
* Compares the reference image file with the actual images given as {@link BufferedImage}.
* @param testIdentifier a test identifier for error messages
* @param referenceImageFile the reference image file to be read using {@link ImageIO#read(File)}
* @param image the actual image
* @param thresholdPixels maximum number of differing pixels
* @param thresholdTotalColorDiff maximum sum of color value differences
* @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null
* @throws IOException in case of I/O error
*/
public static void assertImageEquals(
String testIdentifier, File referenceImageFile, BufferedImage image,
int thresholdPixels, int thresholdTotalColorDiff, Consumer<BufferedImage> diffImageConsumer) throws IOException {

if (UPDATE_ALL) {
ImageIO.write(image, "png", referenceImageFile);
return;
}
final BufferedImage reference = ImageIO.read(referenceImageFile);
assertImageEquals(testIdentifier, reference, image, thresholdPixels, thresholdTotalColorDiff, diffImageConsumer);
}

/**
* Compares the reference image file with the actual images given as {@link BufferedImage}.
* @param testIdentifier a test identifier for error messages
* @param reference the reference image
* @param image the actual image
* @param thresholdPixels maximum number of differing pixels
* @param thresholdTotalColorDiff maximum sum of color value differences
* @param diffImageConsumer a consumer for a rendered image highlighting the differing pixels, may be null
*/
public static void assertImageEquals(String testIdentifier, BufferedImage reference, BufferedImage image,
int thresholdPixels, int thresholdTotalColorDiff, Consumer<BufferedImage> diffImageConsumer) {
assertEquals(reference.getWidth(), image.getWidth());
assertEquals(reference.getHeight(), image.getHeight());

StringBuilder differences = new StringBuilder();
ArrayList<Point> differencePoints = new ArrayList<>();
int colorDiffSum = 0;

for (int y = 0; y < reference.getHeight(); y++) {
for (int x = 0; x < reference.getWidth(); x++) {
int expected = reference.getRGB(x, y);
int result = image.getRGB(x, y);
int expectedAlpha = expected >> 24;
boolean colorsAreSame = expectedAlpha == 0 ? result >> 24 == 0 : expected == result;
if (!colorsAreSame) {
Color expectedColor = new Color(expected, true);
Color resultColor = new Color(result, true);
int colorDiff = Math.abs(expectedColor.getRed() - resultColor.getRed())
+ Math.abs(expectedColor.getGreen() - resultColor.getGreen())
+ Math.abs(expectedColor.getBlue() - resultColor.getBlue());
int alphaDiff = Math.abs(expectedColor.getAlpha() - resultColor.getAlpha());
// Ignore small alpha differences due to Java versions, rendering libraries and so on
if (alphaDiff <= 20) {
alphaDiff = 0;
}
// Ignore small color differences for the same reasons, but also completely for almost-transparent pixels
if (colorDiff <= 15 || resultColor.getAlpha() <= 20) {
colorDiff = 0;
}
if (colorDiff + alphaDiff > 0) {
differencePoints.add(new Point(x, y));
if (differences.length() < 2000) {
differences.append("\nDifference at ")
.append(x)
.append(",")
.append(y)
.append(": Expected ")
.append(ColorHelper.color2html(expectedColor))
.append(" but got ")
.append(ColorHelper.color2html(resultColor))
.append(" (color diff is ")
.append(colorDiff)
.append(", alpha diff is ")
.append(alphaDiff)
.append(")");
}
}
colorDiffSum += colorDiff + alphaDiff;
}
}
}

if (differencePoints.size() > thresholdPixels || colorDiffSum > thresholdTotalColorDiff) {
// Add a nice image that highlights the differences:
BufferedImage diffImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (Point p : differencePoints) {
diffImage.setRGB(p.x, p.y, 0xffff0000);
}
if (diffImageConsumer != null) {
diffImageConsumer.accept(diffImage);
}

if (differencePoints.size() > thresholdPixels) {
fail(MessageFormat.format("Images for test {0} differ at {1} points, threshold is {2}: {3}",
testIdentifier, differencePoints.size(), thresholdPixels, differences.toString()));
} else {
fail(MessageFormat.format("Images for test {0} differ too much in color, value is {1}, permitted threshold is {2}: {3}",
testIdentifier, colorDiffSum, thresholdTotalColorDiff, differences.toString()));
}
}
}

/**
* Write debug images to a directory
* @param directory The directory to put the debug images in
* @param filePrefix The file prefix for the images (e.g. test name)
* @param diff The difference between the expected image and the actual image
* @param oldImage The expected image
* @param newImage The actual image
*/
public static void writeDebugImages(@Nonnull Path directory, @Nonnull String filePrefix, @Nonnull BufferedImage diff,
@Nullable BufferedImage oldImage, @Nullable BufferedImage newImage) {
if (!UPDATE_ALL) {
return;
}
try {
if (!Files.isDirectory(directory)) {
Files.createDirectories(directory);
}
final String basename = directory.resolve(filePrefix).toString();
ImageIO.write(diff, "png", new File(basename + "-diff.png"));
if (newImage != null) ImageIO.write(newImage, "png", new File(basename + "-new.png"));
if (oldImage != null) ImageIO.write(oldImage, "png", new File(basename + "-old.png"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Loading

0 comments on commit 0826abb

Please sign in to comment.