001 /* 002 003 ============================================================================ 004 The Apache Software License, Version 1.1 005 ============================================================================ 006 007 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modifica- 010 tion, are permitted provided that the following conditions are met: 011 012 1. Redistributions of source code must retain the above copyright notice, 013 this list of conditions and the following disclaimer. 014 015 2. Redistributions in binary form must reproduce the above copyright notice, 016 this list of conditions and the following disclaimer in the documentation 017 and/or other materials provided with the distribution. 018 019 3. The end-user documentation included with the redistribution, if any, must 020 include the following acknowledgment: "This product includes software 021 developed by the Apache Software Foundation (http://www.apache.org/)." 022 Alternately, this acknowledgment may appear in the software itself, if 023 and wherever such third-party acknowledgments normally appear. 024 025 4. The names "Batik" and "Apache Software Foundation" must not be 026 used to endorse or promote products derived from this software without 027 prior written permission. For written permission, please contact 028 apache@apache.org. 029 030 5. Products derived from this software may not be called "Apache", nor may 031 "Apache" appear in their name, without prior written permission of the 032 Apache Software Foundation. 033 034 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 035 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 036 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 037 APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 038 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- 039 DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 040 OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 041 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 042 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 043 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 This software consists of voluntary contributions made by many individuals 046 on behalf of the Apache Software Foundation. For more information on the 047 Apache Software Foundation, please see <http://www.apache.org/>. 048 049 */ 050 051 package org.apache.batik.test.util; 052 053 import java.awt.Color; 054 import java.awt.Graphics2D; 055 import java.awt.image.BufferedImage; 056 import java.awt.image.ColorModel; 057 import java.awt.image.RenderedImage; 058 import java.awt.image.WritableRaster; 059 import java.io.BufferedInputStream; 060 import java.io.File; 061 import java.io.FileOutputStream; 062 import java.io.IOException; 063 import java.io.InputStream; 064 import java.net.MalformedURLException; 065 import java.net.URL; 066 067 import org.apache.batik.ext.awt.image.GraphicsUtil; 068 import org.apache.batik.ext.awt.image.codec.PNGEncodeParam; 069 import org.apache.batik.ext.awt.image.codec.PNGImageEncoder; 070 import org.apache.batik.ext.awt.image.renderable.Filter; 071 import org.apache.batik.ext.awt.image.spi.ImageTagRegistry; 072 import org.apache.batik.test.AbstractTest; 073 import org.apache.batik.test.TestReport; 074 import org.apache.batik.util.ParsedURL; 075 076 /** 077 * This test does a pixel comparison of two images and passes if the 078 * two images are identical. It fails otherwise, producing a report 079 * describing why the two images are different. 080 * 081 * @author <a href="vhardy@apache.org">Vincent Hardy</a> 082 * @version $Id: ImageCompareTest.java,v 1.5 2003/08/09 16:58:45 deweese Exp $ 083 */ 084 public class ImageCompareTest extends AbstractTest { 085 public static final String ERROR_COULD_NOT_OPEN_IMAGE 086 = "ImageCompareTest.error.could.not.open.image"; 087 088 public static final String ERROR_COULD_NOT_LOAD_IMAGE 089 = "ImageCompareTest.error.could.not.load.image"; 090 091 public static final String ERROR_DIFFERENCES 092 = "ImageCompareTest.error.differences"; 093 094 public static final String ERROR_WHILE_COMPARING_FILES 095 = "ImageCompareTest.error.while.comparing.files"; 096 097 public static final String ENTRY_KEY_FIRST_IMAGE 098 = "ImageCompareTest.entry.key.first.image"; 099 100 public static final String ENTRY_KEY_SECOND_IMAGE 101 = "ImageCompareTest.entry.key.second.image"; 102 103 public static final String ENTRY_KEY_COMPARISON 104 = "ImageCompareTest.entry.key.comparison"; 105 106 public static final String ENTRY_KEY_DIFFERENCE 107 = "ImageCompareTest.entry.key.difference"; 108 109 public static final String ENTRY_KEY_IMAGE_URL 110 = "ImageCompareTest.entry.key.image.url"; 111 112 public static final String IMAGE_TYPE_DIFFERENCE 113 = "_diff"; 114 115 public static final String IMAGE_TYPE_COMPARISON 116 = "_cmp"; 117 118 /** 119 * Prefix for the temporary files created by Tests 120 * of this class 121 */ 122 public static final String TEMP_FILE_PREFIX 123 = "ImageCompareTest"; 124 125 /** 126 * Suffix for the temporary files created by 127 * Tests of this class 128 */ 129 public static final String TEMP_FILE_SUFFIX 130 = ""; 131 132 /** 133 * URL for the first image to be compared. 134 */ 135 protected String urlAStr; 136 protected URL urlA; 137 138 /** 139 * URL for the second image to be compared 140 */ 141 protected String urlBStr; 142 protected URL urlB; 143 144 /** 145 * Resolves the input string as follows. 146 * + First, the string is interpreted as a file description. 147 * If the file exists, then the file name is turned into 148 * a URL. 149 * + Otherwise, the string is supposed to be a URL. If it 150 * is an invalid URL, an IllegalArgumentException is thrown. 151 */ 152 protected URL resolveURL(String url){ 153 // Is url a file? 154 File f = (new File(url)).getAbsoluteFile(); 155 if(f.exists()){ 156 try{ 157 return f.toURL(); 158 }catch(MalformedURLException e){ 159 throw new IllegalArgumentException(); 160 } 161 } 162 163 // url is not a file. It must be a regular URL... 164 try{ 165 return new URL(url); 166 }catch(MalformedURLException e){ 167 throw new IllegalArgumentException(url); 168 } 169 } 170 171 /** 172 * This test makes a binary comparison of the two images 173 * (and not a pixel comparison). If the images are different, 174 * the test generates a report containing the two images and 175 * a delta images to help the user visualize the difference. 176 * 177 * @param urlA first image 178 * @param urlB second image 179 */ 180 public ImageCompareTest(String urlA, 181 String urlB){ 182 urlAStr = urlA; 183 urlBStr = urlB; 184 } 185 186 protected void initURLs(){ 187 if(urlA == null){ 188 throw new IllegalArgumentException(); 189 } 190 191 if(urlB == null){ 192 throw new IllegalArgumentException(); 193 } 194 195 this.urlA = resolveURL(urlAStr); 196 this.urlB = resolveURL(urlBStr); 197 } 198 199 public TestReport rumImpl() throws Exception { 200 initURLs(); 201 202 InputStream streamA = null; 203 204 try{ 205 streamA = new BufferedInputStream(urlA.openStream()); 206 }catch(IOException e){ 207 return reportException(ERROR_COULD_NOT_OPEN_IMAGE, e); 208 } 209 210 InputStream streamB = null; 211 212 try{ 213 streamB = new BufferedInputStream(urlB.openStream()); 214 }catch(IOException e){ 215 return reportException(ERROR_COULD_NOT_OPEN_IMAGE, e); 216 } 217 218 boolean accurate = false; 219 220 try{ 221 accurate = compare(streamA, streamB); 222 }catch(IOException e){ 223 TestReport report = reportException(ERROR_WHILE_COMPARING_FILES, e); 224 report.addDescriptionEntry(ENTRY_KEY_FIRST_IMAGE, 225 urlA.toString()); 226 report.addDescriptionEntry(ENTRY_KEY_SECOND_IMAGE, 227 urlB.toString()); 228 return report; 229 } 230 231 if(accurate){ 232 return reportSuccess(); 233 } 234 235 // We are in error (images are different: produce an image 236 // with the two images side by side as well as a diff image) 237 BufferedImage imageA = getImage(urlA); 238 if(imageA == null){ 239 TestReport report = reportError(ERROR_COULD_NOT_LOAD_IMAGE); 240 report.addDescriptionEntry(ENTRY_KEY_IMAGE_URL, 241 urlA.toString()); 242 return report; 243 } 244 245 BufferedImage imageB = getImage(urlB); 246 if(imageB == null){ 247 TestReport report = reportError(ERROR_COULD_NOT_LOAD_IMAGE); 248 report.addDescriptionEntry(ENTRY_KEY_IMAGE_URL, 249 urlB.toString()); 250 return report; 251 } 252 253 BufferedImage diff = buildDiffImage(imageA, imageB); 254 BufferedImage cmp = buildCompareImage(imageA, imageB); 255 256 File tmpDiff = imageToFile(diff, IMAGE_TYPE_DIFFERENCE); 257 File tmpCmp = imageToFile(cmp, IMAGE_TYPE_COMPARISON); 258 259 TestReport report = reportError(ERROR_DIFFERENCES); 260 report.addDescriptionEntry(ENTRY_KEY_COMPARISON, tmpCmp); 261 report.addDescriptionEntry(ENTRY_KEY_DIFFERENCE, tmpDiff); 262 263 return report; 264 } 265 266 protected BufferedImage buildCompareImage(BufferedImage ref, 267 BufferedImage gen){ 268 BufferedImage cmp = new BufferedImage(ref.getWidth()*2, 269 ref.getHeight(), 270 BufferedImage.TYPE_INT_ARGB); 271 272 Graphics2D g = cmp.createGraphics(); 273 g.setPaint(Color.white); 274 g.fillRect(0, 0, cmp.getWidth(), cmp.getHeight()); 275 g.drawImage(ref, 0, 0, null); 276 g.translate(ref.getWidth(), 0); 277 g.drawImage(gen, 0, 0, null); 278 g.dispose(); 279 280 return cmp; 281 } 282 283 /** 284 * Creates a temporary File into which the input image is 285 * saved. 286 */ 287 protected File imageToFile(BufferedImage img, 288 String imageType) 289 throws IOException { 290 291 File imageFile = makeRandomFileName(imageType); 292 imageFile.deleteOnExit(); 293 294 PNGImageEncoder encoder 295 = new PNGImageEncoder(new FileOutputStream(imageFile), 296 PNGEncodeParam.getDefaultEncodeParam(img)); 297 298 encoder.encode(img); 299 300 return imageFile; 301 302 } 303 304 /** 305 * Creates a temporary File into which the input image is 306 * saved. 307 */ 308 protected File makeRandomFileName(String imageType) 309 throws IOException { 310 311 return File.createTempFile(TEMP_FILE_PREFIX, 312 TEMP_FILE_SUFFIX + imageType, 313 null); 314 } 315 316 /** 317 * Builds a new BufferedImage that is the difference between the two input images 318 */ 319 public static BufferedImage buildDiffImage(BufferedImage ref, 320 BufferedImage gen) { 321 BufferedImage diff = new BufferedImage(ref.getWidth(), 322 ref.getHeight(), 323 BufferedImage.TYPE_INT_ARGB); 324 WritableRaster refWR = ref.getRaster(); 325 WritableRaster genWR = gen.getRaster(); 326 WritableRaster dstWR = diff.getRaster(); 327 328 boolean refPre = ref.isAlphaPremultiplied(); 329 if (!refPre) { 330 ColorModel cm = ref.getColorModel(); 331 cm = GraphicsUtil.coerceData(refWR, cm, true); 332 ref = new BufferedImage(cm, refWR, true, null); 333 } 334 boolean genPre = gen.isAlphaPremultiplied(); 335 if (!genPre) { 336 ColorModel cm = gen.getColorModel(); 337 cm = GraphicsUtil.coerceData(genWR, cm, true); 338 gen = new BufferedImage(cm, genWR, true, null); 339 } 340 341 342 int w=ref.getWidth(); 343 int h=ref.getHeight(); 344 345 int y, i,val; 346 int [] refPix = null; 347 int [] genPix = null; 348 for (y=0; y<h; y++) { 349 refPix = refWR.getPixels (0, y, w, 1, refPix); 350 genPix = genWR.getPixels (0, y, w, 1, genPix); 351 for (i=0; i<refPix.length; i++) { 352 // val = ((genPix[i]-refPix[i])*5)+128; 353 val = ((refPix[i]-genPix[i])*10)+128; 354 if ((val & 0xFFFFFF00) != 0) 355 if ((val & 0x80000000) != 0) val = 0; 356 else val = 255; 357 genPix[i] = val; 358 } 359 dstWR.setPixels(0, y, w, 1, genPix); 360 } 361 362 if (!genPre) { 363 ColorModel cm = gen.getColorModel(); 364 cm = GraphicsUtil.coerceData(genWR, cm, false); 365 } 366 367 if (!refPre) { 368 ColorModel cm = ref.getColorModel(); 369 cm = GraphicsUtil.coerceData(refWR, cm, false); 370 } 371 372 return diff; 373 } 374 375 376 /** 377 * Compare the two input streams 378 */ 379 public static boolean compare(InputStream refStream, 380 InputStream newStream) 381 throws IOException{ 382 int b, nb; 383 do { 384 b = refStream.read(); 385 nb = newStream.read(); 386 } while (b != -1 && nb != -1 && b == nb); Rate387 refStream.close(); Rate388 newStream.close(); 389 return (b == nb); 390 } 391 392 /** 393 * Loads an image from a URL 394 */ 395 protected BufferedImage getImage(URL url) { 396 ImageTagRegistry reg = ImageTagRegistry.getRegistry(); 397 Filter filt = reg.readURL(new ParsedURL(url)); 398 if(filt == null){ 399 return null; 400 } 401 402 RenderedImage red = filt.createDefaultRendering(); 403 if(red == null){ 404 return null; 405 } 406 407 BufferedImage img = new BufferedImage(red.getWidth(), 408 red.getHeight(), 409 BufferedImage.TYPE_INT_ARGB); 410 red.copyData(img.getRaster()); 411 412 return img; 413 } 414 415 416 }