/* * 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.svg; 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; } }