blob: 8a57256e03a9c2443ca722420ba73685fbcea0d8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.spi.ImageTagRegistry;
import org.apache.batik.ext.awt.image.spi.ImageWriter;
import org.apache.batik.ext.awt.image.spi.ImageWriterRegistry;
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="mailto: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();
ImageWriter writer = ImageWriterRegistry.getInstance()
.getWriterFor("image/png");
OutputStream out = new FileOutputStream(imageFile);
try {
writer.writeImage(img, out);
} finally {
out.close();
}
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;
}
}