blob: 10aba5dd0b28d29c78f4f1aa60a140f4b2e4b10b [file] [log] [blame]
/*
============================================================================
The Apache Software License, Version 1.1
============================================================================
Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "Batik" and "Apache Software Foundation" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
apache@apache.org.
5. Products derived from this software may not be called "Apache", nor may
"Apache" appear in their name, without prior written permission of the
Apache Software Foundation.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals
on behalf of the Apache Software Foundation. For more information on the
Apache Software Foundation, please see <http://www.apache.org/>.
*/
package org.apache.batik.test.util;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.codec.PNGEncodeParam;
import org.apache.batik.ext.awt.image.codec.PNGImageEncoder;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.spi.ImageTagRegistry;
import org.apache.batik.test.AbstractTest;
import org.apache.batik.test.TestReport;
import org.apache.batik.util.ParsedURL;
/**
* This test does a pixel comparison of two images and passes if the
* two images are identical. It fails otherwise, producing a report
* describing why the two images are different.
*
* @author <a href="vhardy@apache.org">Vincent Hardy</a>
* @version $Id$
*/
public class ImageCompareTest extends AbstractTest {
public static final String ERROR_COULD_NOT_OPEN_IMAGE
= "ImageCompareTest.error.could.not.open.image";
public static final String ERROR_COULD_NOT_LOAD_IMAGE
= "ImageCompareTest.error.could.not.load.image";
public static final String ERROR_DIFFERENCES
= "ImageCompareTest.error.differences";
public static final String ERROR_WHILE_COMPARING_FILES
= "ImageCompareTest.error.while.comparing.files";
public static final String ENTRY_KEY_FIRST_IMAGE
= "ImageCompareTest.entry.key.first.image";
public static final String ENTRY_KEY_SECOND_IMAGE
= "ImageCompareTest.entry.key.second.image";
public static final String ENTRY_KEY_COMPARISON
= "ImageCompareTest.entry.key.comparison";
public static final String ENTRY_KEY_DIFFERENCE
= "ImageCompareTest.entry.key.difference";
public static final String ENTRY_KEY_IMAGE_URL
= "ImageCompareTest.entry.key.image.url";
public static final String IMAGE_TYPE_DIFFERENCE
= "_diff";
public static final String IMAGE_TYPE_COMPARISON
= "_cmp";
/**
* Prefix for the temporary files created by Tests
* of this class
*/
public static final String TEMP_FILE_PREFIX
= "ImageCompareTest";
/**
* Suffix for the temporary files created by
* Tests of this class
*/
public static final String TEMP_FILE_SUFFIX
= "";
/**
* URL for the first image to be compared.
*/
protected String urlAStr;
protected URL urlA;
/**
* URL for the second image to be compared
*/
protected String urlBStr;
protected URL urlB;
/**
* Resolves the input string as follows.
* + First, the string is interpreted as a file description.
* If the file exists, then the file name is turned into
* a URL.
* + Otherwise, the string is supposed to be a URL. If it
* is an invalid URL, an IllegalArgumentException is thrown.
*/
protected URL resolveURL(String url){
// Is url a file?
File f = (new File(url)).getAbsoluteFile();
if(f.exists()){
try{
return f.toURL();
}catch(MalformedURLException e){
throw new IllegalArgumentException();
}
}
// url is not a file. It must be a regular URL...
try{
return new URL(url);
}catch(MalformedURLException e){
throw new IllegalArgumentException(url);
}
}
/**
* This test makes a binary comparison of the two images
* (and not a pixel comparison). If the images are different,
* the test generates a report containing the two images and
* a delta images to help the user visualize the difference.
*
* @param urlA first image
* @param urlB second image
*/
public ImageCompareTest(String urlA,
String urlB){
urlAStr = urlA;
urlBStr = urlB;
}
protected void initURLs(){
if(urlA == null){
throw new IllegalArgumentException();
}
if(urlB == null){
throw new IllegalArgumentException();
}
this.urlA = resolveURL(urlAStr);
this.urlB = resolveURL(urlBStr);
}
public TestReport rumImpl() throws Exception {
initURLs();
InputStream streamA = null;
try{
streamA = new BufferedInputStream(urlA.openStream());
}catch(IOException e){
return reportException(ERROR_COULD_NOT_OPEN_IMAGE, e);
}
InputStream streamB = null;
try{
streamB = new BufferedInputStream(urlB.openStream());
}catch(IOException e){
return reportException(ERROR_COULD_NOT_OPEN_IMAGE, e);
}
boolean accurate = false;
try{
accurate = compare(streamA, streamB);
}catch(IOException e){
TestReport report = reportException(ERROR_WHILE_COMPARING_FILES, e);
report.addDescriptionEntry(ENTRY_KEY_FIRST_IMAGE,
urlA.toString());
report.addDescriptionEntry(ENTRY_KEY_SECOND_IMAGE,
urlB.toString());
return report;
}
if(accurate){
return reportSuccess();
}
// We are in error (images are different: produce an image
// with the two images side by side as well as a diff image)
BufferedImage imageA = getImage(urlA);
if(imageA == null){
TestReport report = reportError(ERROR_COULD_NOT_LOAD_IMAGE);
report.addDescriptionEntry(ENTRY_KEY_IMAGE_URL,
urlA.toString());
return report;
}
BufferedImage imageB = getImage(urlB);
if(imageB == null){
TestReport report = reportError(ERROR_COULD_NOT_LOAD_IMAGE);
report.addDescriptionEntry(ENTRY_KEY_IMAGE_URL,
urlB.toString());
return report;
}
BufferedImage diff = buildDiffImage(imageA, imageB);
BufferedImage cmp = buildCompareImage(imageA, imageB);
File tmpDiff = imageToFile(diff, IMAGE_TYPE_DIFFERENCE);
File tmpCmp = imageToFile(cmp, IMAGE_TYPE_COMPARISON);
TestReport report = reportError(ERROR_DIFFERENCES);
report.addDescriptionEntry(ENTRY_KEY_COMPARISON, tmpCmp);
report.addDescriptionEntry(ENTRY_KEY_DIFFERENCE, tmpDiff);
return report;
}
protected BufferedImage buildCompareImage(BufferedImage ref,
BufferedImage gen){
BufferedImage cmp = new BufferedImage(ref.getWidth()*2,
ref.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = cmp.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, cmp.getWidth(), cmp.getHeight());
g.drawImage(ref, 0, 0, null);
g.translate(ref.getWidth(), 0);
g.drawImage(gen, 0, 0, null);
g.dispose();
return cmp;
}
/**
* Creates a temporary File into which the input image is
* saved.
*/
protected File imageToFile(BufferedImage img,
String imageType)
throws IOException {
File imageFile = makeRandomFileName(imageType);
imageFile.deleteOnExit();
PNGImageEncoder encoder
= new PNGImageEncoder(new FileOutputStream(imageFile),
PNGEncodeParam.getDefaultEncodeParam(img));
encoder.encode(img);
return imageFile;
}
/**
* Creates a temporary File into which the input image is
* saved.
*/
protected File makeRandomFileName(String imageType)
throws IOException {
return File.createTempFile(TEMP_FILE_PREFIX,
TEMP_FILE_SUFFIX + imageType,
null);
}
/**
* Builds a new BufferedImage that is the difference between the two input images
*/
public static BufferedImage buildDiffImage(BufferedImage ref,
BufferedImage gen) {
BufferedImage diff = new BufferedImage(ref.getWidth(),
ref.getHeight(),
BufferedImage.TYPE_INT_ARGB);
WritableRaster refWR = ref.getRaster();
WritableRaster genWR = gen.getRaster();
WritableRaster dstWR = diff.getRaster();
boolean refPre = ref.isAlphaPremultiplied();
if (!refPre) {
ColorModel cm = ref.getColorModel();
cm = GraphicsUtil.coerceData(refWR, cm, true);
ref = new BufferedImage(cm, refWR, true, null);
}
boolean genPre = gen.isAlphaPremultiplied();
if (!genPre) {
ColorModel cm = gen.getColorModel();
cm = GraphicsUtil.coerceData(genWR, cm, true);
gen = new BufferedImage(cm, genWR, true, null);
}
int w=ref.getWidth();
int h=ref.getHeight();
int y, i,val;
int [] refPix = null;
int [] genPix = null;
for (y=0; y<h; y++) {
refPix = refWR.getPixels (0, y, w, 1, refPix);
genPix = genWR.getPixels (0, y, w, 1, genPix);
for (i=0; i<refPix.length; i++) {
// val = ((genPix[i]-refPix[i])*5)+128;
val = ((refPix[i]-genPix[i])*10)+128;
if ((val & 0xFFFFFF00) != 0)
if ((val & 0x80000000) != 0) val = 0;
else val = 255;
genPix[i] = val;
}
dstWR.setPixels(0, y, w, 1, genPix);
}
if (!genPre) {
ColorModel cm = gen.getColorModel();
cm = GraphicsUtil.coerceData(genWR, cm, false);
}
if (!refPre) {
ColorModel cm = ref.getColorModel();
cm = GraphicsUtil.coerceData(refWR, cm, false);
}
return diff;
}
/**
* Compare the two input streams
*/
public static boolean compare(InputStream refStream,
InputStream newStream)
throws IOException{
int b, nb;
do {
b = refStream.read();
nb = newStream.read();
} while (b != -1 && nb != -1 && b == nb);
refStream.close();
newStream.close();
return (b == nb);
}
/**
* Loads an image from a URL
*/
protected BufferedImage getImage(URL url) {
ImageTagRegistry reg = ImageTagRegistry.getRegistry();
Filter filt = reg.readURL(new ParsedURL(url));
if(filt == null){
return null;
}
RenderedImage red = filt.createDefaultRendering();
if(red == null){
return null;
}
BufferedImage img = new BufferedImage(red.getWidth(),
red.getHeight(),
BufferedImage.TYPE_INT_ARGB);
red.copyData(img.getRaster());
return img;
}
}