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