/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.image.test;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.geotools.image.ImageWorker;
import org.geotools.image.test.ImageComparator.Mode;
import org.geotools.util.logging.Logging;
/**
* Compares two images using perceptual criterias: the assertions will fail if the images would look
* different to a human being.
*
* @author Andrea Aime - GeoSolutions
*
*
* @source $URL$
*/
public class ImageAssert {
/**
* Makes the test interactive, showing a Swing dialog with before/after and a choice to
* overwrite the expected image
*/
static final boolean INTERACTIVE = Boolean.getBoolean("org.geotools.image.test.interactive");
/**
* Forces the image comparison tests to be skipped
*/
static final boolean SKIP = Boolean.getBoolean("org.geotools.image.test.skip");
static final Logger LOGGER = Logging.getLogger(Logger.class);
/**
* Checks the image in the reference file and the actual image are equals from a human
* perception p.o.v
*
* @param expectedFile
* @param actualImage
* @param threshold
* @throws IOException
*/
public static void assertEquals(File expectedFile, RenderedImage actualImage, int threshold)
throws IOException {
if (SKIP) {
return;
}
assertImagesResemble(expectedFile, actualImage, Mode.IgnoreAntialiasing, threshold, true);
}
/**
* Checks the expected image and the actual image are equals from a human perception p.o.v
*
* @param expectedFile
* @param actualImage
* @param threshold
*/
public static void assertEquals(RenderedImage expectedImage, RenderedImage actualImage,
int threshold) {
ImageComparator comparator = new ImageComparator(Mode.IgnoreAntialiasing, expectedImage, actualImage);
if (comparator.getMismatchCount() > threshold) {
throw new AssertionError("Images are visibly different, found "
+ comparator.getMismatchCount() + " different pixels, against a threshold of "
+ threshold);
}
}
/**
* Checks the expected image and the actual image are equals from a human perception p.o.v
*
* @param expectedFile
* @param actualImage
* @param threshold
* @throws IOException
*/
public static void assertEquals(File expectedImage, RenderedImage actualImage, int threshold,
Mode mode) throws IOException {
assertImagesResemble(expectedImage, actualImage, mode, threshold, true);
}
private static void assertImagesResemble(File expectedFile, RenderedImage actualImage,
Mode mode, int threshold, boolean actualReferenceFile) throws IOException {
// do we have the reference image at all?
if (!expectedFile.exists()) {
// see what the user thinks of the image
boolean useAsReference = actualReferenceFile && INTERACTIVE
&& ReferenceImageDialog.show(realignImage(actualImage));
if (useAsReference) {
try {
File parent = expectedFile.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
throw new AssertionError("Could not create directory that will contain :"
+ expectedFile.getParent());
}
new ImageWorker(actualImage).writePNG(expectedFile, "FILTERED", 0.9f, false,
false);
} catch (IOException e) {
throw (Error) new AssertionError("Failed to write the image to disk")
.initCause(e);
}
} else {
throw new AssertionError("Reference image is missing: " + expectedFile
+ ", add -Dorg.geotools.image.test.interactive=true to show a dialog comparing them (requires GUI support)");
}
} else {
RenderedImage expectedImage = ImageIO.read(expectedFile);
ImageComparator comparator = new ImageComparator(mode, expectedImage, actualImage);
if (comparator.getMismatchCount() > threshold) {
// check with the user
boolean overwrite = false;
if (INTERACTIVE) {
overwrite = CompareImageDialog.show(realignImage(expectedImage),
realignImage(actualImage), actualReferenceFile);
} else {
LOGGER.info("Images are different, add -Dorg.geotools.image.test.interactive=true to show a dialog comparing them (requires GUI support)");
}
if (overwrite) {
ImageIO.write(actualImage, "PNG", expectedFile);
} else {
throw new AssertionError("Images are visibly different, found "
+ comparator.getMismatchCount()
+ " different pixels, against a threshold of " + threshold
+ "\nYou can add -Dorg.geotools.image.test.interactive=true to show a dialog comparing them (requires GUI support)");
}
} else {
LOGGER.fine("Images are not visibly different, found "
+ comparator.getMismatchCount()
+ " different pixels, against a threshold of " + threshold);
}
}
}
static String getFormat(File expectedImage) {
final String loName = expectedImage.getName().toLowerCase();
if (loName.endsWith(".png")) {
return "PNG";
} else if (loName.endsWith(".tif") || loName.endsWith(".tiff")) {
return "TIFF";
} else {
throw new IllegalArgumentException("Expected image file should be a png or a tiff");
}
}
/**
* Makes sure the image starts at 0,0, all images coming from files do but the ones
* coming from a JAI chain might not
* @param image
* @return
*/
static final RenderedImage realignImage(RenderedImage image) {
if (image.getMinX() > 0 || image.getMinY() > 0) {
return new BufferedImage(image.getColorModel(),
((WritableRaster) image.getData()).createWritableTranslatedChild(0, 0), image
.getColorModel().isAlphaPremultiplied(), null);
} else {
return image;
}
}
}