1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.awt.Color; 25 import java.awt.Graphics2D; 26 import java.awt.Rectangle; 27 import java.awt.image.BufferedImage; 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 33 import javax.imageio.ImageIO; 34 import javax.imageio.ImageWriter; 35 import javax.imageio.event.IIOWriteProgressListener; 36 import javax.imageio.stream.ImageOutputStream; 37 38 import static java.awt.image.BufferedImage.TYPE_BYTE_BINARY; 39 import java.awt.image.ColorModel; 40 import java.awt.image.Raster; 41 import java.awt.image.RenderedImage; 42 import java.awt.image.SampleModel; 43 import java.awt.image.WritableRaster; 44 import java.util.Vector; 45 import javax.imageio.IIOImage; 46 import javax.imageio.ImageReader; 47 import javax.imageio.stream.ImageInputStream; 48 49 /** 50 * @test 51 * @bug 8144245 52 * @summary Ensure aborting write works properly for a TIFF sequence. 53 */ 54 public final class WriteToSequenceAfterAbort implements IIOWriteProgressListener { 55 56 private volatile boolean abortFlag = true; 57 private volatile boolean isAbortCalled; 58 private volatile boolean isCompleteCalled; 59 private volatile boolean isProgressCalled; 60 private volatile boolean isStartedCalled; 61 private static final int WIDTH = 100; 62 private static final int HEIGHT = 100; 63 private static final int NUM_TILES_XY = 3; 64 65 private class TiledImage implements RenderedImage { 66 private final BufferedImage tile; 67 private final BufferedImage image; 68 private final int numXTiles, numYTiles; 69 private boolean isImageInitialized = false; 70 TiledImage(BufferedImage tile, int numXTiles, int numYTiles)71 TiledImage(BufferedImage tile, int numXTiles, int numYTiles) { 72 this.tile = tile; 73 this.numXTiles = numXTiles; 74 this.numYTiles = numYTiles; 75 image = new BufferedImage(getWidth(), getHeight(), tile.getType()); 76 } 77 78 @Override getSources()79 public Vector<RenderedImage> getSources() { 80 return null; 81 } 82 83 @Override getProperty(String string)84 public Object getProperty(String string) { 85 return java.awt.Image.UndefinedProperty; 86 } 87 88 @Override getPropertyNames()89 public String[] getPropertyNames() { 90 return new String[0]; 91 } 92 93 @Override getColorModel()94 public ColorModel getColorModel() { 95 return tile.getColorModel(); 96 } 97 98 @Override getSampleModel()99 public SampleModel getSampleModel() { 100 return tile.getSampleModel(); 101 } 102 103 @Override getWidth()104 public int getWidth() { 105 return numXTiles*tile.getWidth(); 106 } 107 108 @Override getHeight()109 public int getHeight() { 110 return numYTiles*tile.getHeight(); 111 } 112 113 @Override getMinX()114 public int getMinX() { 115 return 0; 116 } 117 118 @Override getMinY()119 public int getMinY() { 120 return 0; 121 } 122 123 @Override getNumXTiles()124 public int getNumXTiles() { 125 return numXTiles; 126 } 127 128 @Override getNumYTiles()129 public int getNumYTiles() { 130 return numYTiles; 131 } 132 133 @Override getMinTileX()134 public int getMinTileX() { 135 return 0; 136 } 137 138 @Override getMinTileY()139 public int getMinTileY() { 140 return 0; 141 } 142 143 @Override getTileWidth()144 public int getTileWidth() { 145 return tile.getWidth(); 146 } 147 148 @Override getTileHeight()149 public int getTileHeight() { 150 return tile.getHeight(); 151 } 152 153 @Override getTileGridXOffset()154 public int getTileGridXOffset() { 155 return 0; 156 } 157 158 @Override getTileGridYOffset()159 public int getTileGridYOffset() { 160 return 0; 161 } 162 163 @Override getTile(int x, int y)164 public Raster getTile(int x, int y) { 165 WritableRaster r = tile.getRaster(); 166 return r.createWritableTranslatedChild(x*tile.getWidth(), 167 y*tile.getHeight()); 168 } 169 170 @Override getData()171 public Raster getData() { 172 return getAsBufferedImage().getData(); 173 } 174 175 @Override getData(Rectangle r)176 public Raster getData(Rectangle r) { 177 return getAsBufferedImage().getData(r); 178 } 179 180 @Override copyData(WritableRaster wr)181 public WritableRaster copyData(WritableRaster wr) { 182 return getAsBufferedImage().copyData(wr); 183 } 184 getAsBufferedImage()185 public BufferedImage getAsBufferedImage() { 186 synchronized (image) { 187 if (!isImageInitialized) { 188 int tx0 = getMinTileX(), ty0 = getMinTileY(); 189 int txN = tx0 + getNumXTiles(), tyN = ty0 + getNumYTiles(); 190 for (int j = ty0; j < tyN; j++) { 191 for (int i = tx0; i < txN; i++) { 192 image.setData(getTile(i, j)); 193 } 194 } 195 } 196 isImageInitialized = true; 197 } 198 return image; 199 } 200 } 201 test(final ImageWriter writer)202 private void test(final ImageWriter writer) throws IOException { 203 String suffix = writer.getOriginatingProvider().getFileSuffixes()[0]; 204 205 // Image initialization 206 BufferedImage imageUpperLeft = 207 new BufferedImage(WIDTH, HEIGHT, TYPE_BYTE_BINARY); 208 Graphics2D g = imageUpperLeft.createGraphics(); 209 g.setColor(Color.WHITE); 210 g.fillRect(0, 0, WIDTH/2, HEIGHT/2); 211 g.dispose(); 212 BufferedImage imageLowerRight = 213 new BufferedImage(WIDTH, HEIGHT, TYPE_BYTE_BINARY); 214 g = imageLowerRight.createGraphics(); 215 g.setColor(Color.WHITE); 216 g.fillRect(WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2); 217 g.dispose(); 218 TiledImage[] images = new TiledImage[] { 219 new TiledImage(imageUpperLeft, NUM_TILES_XY, NUM_TILES_XY), 220 new TiledImage(imageUpperLeft, NUM_TILES_XY, NUM_TILES_XY), 221 new TiledImage(imageLowerRight, NUM_TILES_XY, NUM_TILES_XY), 222 new TiledImage(imageLowerRight, NUM_TILES_XY, NUM_TILES_XY) 223 }; 224 225 // File initialization 226 File file = File.createTempFile("temp", "." + suffix); 227 file.deleteOnExit(); 228 FileOutputStream fos = new SkipWriteOnAbortOutputStream(file); 229 ImageOutputStream ios = ImageIO.createImageOutputStream(fos); 230 writer.setOutput(ios); 231 writer.addIIOWriteProgressListener(this); 232 233 writer.prepareWriteSequence(null); 234 boolean[] abortions = new boolean[] {true, false, true, false}; 235 for (int i = 0; i < 4; i++) { 236 abortFlag = abortions[i]; 237 isAbortCalled = false; 238 isCompleteCalled = false; 239 isProgressCalled = false; 240 isStartedCalled = false; 241 242 TiledImage image = images[i]; 243 if (abortFlag) { 244 // This write will be aborted, and file will not be touched 245 writer.writeToSequence(new IIOImage(image, null, null), null); 246 if (!isStartedCalled) { 247 throw new RuntimeException("Started should be called"); 248 } 249 if (!isProgressCalled) { 250 throw new RuntimeException("Progress should be called"); 251 } 252 if (!isAbortCalled) { 253 throw new RuntimeException("Abort should be called"); 254 } 255 if (isCompleteCalled) { 256 throw new RuntimeException("Complete should not be called"); 257 } 258 } else { 259 // This write should be completed successfully and the file should 260 // contain correct image data. 261 writer.writeToSequence(new IIOImage(image, null, null), null); 262 if (!isStartedCalled) { 263 throw new RuntimeException("Started should be called"); 264 } 265 if (!isProgressCalled) { 266 throw new RuntimeException("Progress should be called"); 267 } 268 if (isAbortCalled) { 269 throw new RuntimeException("Abort should not be called"); 270 } 271 if (!isCompleteCalled) { 272 throw new RuntimeException("Complete should be called"); 273 } 274 } 275 } 276 277 writer.endWriteSequence(); 278 writer.dispose(); 279 ios.close(); 280 281 // Validates content of the file. 282 ImageReader reader = ImageIO.getImageReader(writer); 283 ImageInputStream iis = ImageIO.createImageInputStream(file); 284 reader.setInput(iis); 285 for (int i = 0; i < 2; i++) { 286 System.out.println("Testing image " + i); 287 BufferedImage imageRead = reader.read(i); 288 BufferedImage imageWrite = images[2 * i].getAsBufferedImage(); 289 for (int x = 0; x < WIDTH; ++x) { 290 for (int y = 0; y < HEIGHT; ++y) { 291 if (imageRead.getRGB(x, y) != imageWrite.getRGB(x, y)) { 292 throw new RuntimeException("Test failed for image " + i); 293 } 294 } 295 } 296 } 297 } 298 main(final String[] args)299 public static void main(final String[] args) throws IOException { 300 WriteToSequenceAfterAbort writeAfterAbort = new WriteToSequenceAfterAbort(); 301 ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next(); 302 writeAfterAbort.test(writer); 303 System.out.println("Test passed."); 304 } 305 306 // Callbacks 307 308 @Override imageComplete(ImageWriter source)309 public void imageComplete(ImageWriter source) { 310 isCompleteCalled = true; 311 } 312 313 @Override imageProgress(ImageWriter source, float percentageDone)314 public void imageProgress(ImageWriter source, float percentageDone) { 315 isProgressCalled = true; 316 if (percentageDone > 50 && abortFlag) { 317 source.abort(); 318 } 319 } 320 321 @Override imageStarted(ImageWriter source, int imageIndex)322 public void imageStarted(ImageWriter source, int imageIndex) { 323 isStartedCalled = true; 324 } 325 326 @Override writeAborted(final ImageWriter source)327 public void writeAborted(final ImageWriter source) { 328 isAbortCalled = true; 329 } 330 331 @Override thumbnailComplete(ImageWriter source)332 public void thumbnailComplete(ImageWriter source) { 333 } 334 335 @Override thumbnailProgress(ImageWriter source, float percentageDone)336 public void thumbnailProgress(ImageWriter source, float percentageDone) { 337 } 338 339 @Override thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex)340 public void thumbnailStarted(ImageWriter source, int imageIndex, 341 int thumbnailIndex) { 342 } 343 344 /** 345 * We need to skip writes on abort, because content of the file after abort 346 * is undefined. 347 */ 348 private class SkipWriteOnAbortOutputStream extends FileOutputStream { 349 SkipWriteOnAbortOutputStream(File file)350 SkipWriteOnAbortOutputStream(File file) throws FileNotFoundException { 351 super(file); 352 } 353 354 @Override write(int b)355 public void write(int b) throws IOException { 356 if (!abortFlag) { 357 super.write(b); 358 } 359 } 360 361 @Override write(byte[] b)362 public void write(byte[] b) throws IOException { 363 if (!abortFlag) { 364 super.write(b); 365 } 366 } 367 368 @Override write(byte[] b, int off, int len)369 public void write(byte[] b, int off, int len) throws IOException { 370 if (!abortFlag) { 371 super.write(b, off, len); 372 } 373 } 374 } 375 } 376 377