View Javadoc
1   //
2   // $Id$
3   //
4   // jupload - A file upload applet.
5   //
6   // Copyright 2008 The JUpload Team
7   //
8   // Created: 12 feb. 08
9   // Creator: etienne_sf
10  // Last modified: $Date$
11  //
12  // This program is free software; you can redistribute it and/or modify
13  // it under the terms of the GNU General Public License as published by
14  // the Free Software Foundation; either version 2 of the License, or
15  // (at your option) any later version.
16  //
17  // This program is distributed in the hope that it will be useful,
18  // but WITHOUT ANY WARRANTY; without even the implied warranty of
19  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  // GNU General Public License for more details.
21  //
22  // You should have received a copy of the GNU General Public License
23  // along with this program; if not, write to the Free Software
24  // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25  
26  package wjhk.jupload2.filedata.helper;
27  
28  import java.awt.image.BufferedImage;
29  import java.io.File;
30  import java.io.IOException;
31  import java.util.Iterator;
32  
33  import javax.imageio.IIOImage;
34  import javax.imageio.ImageIO;
35  import javax.imageio.ImageReader;
36  import javax.imageio.ImageWriteParam;
37  import javax.imageio.ImageWriter;
38  import javax.imageio.metadata.IIOMetadata;
39  import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
40  import javax.imageio.stream.FileImageInputStream;
41  import javax.imageio.stream.FileImageOutputStream;
42  
43  import wjhk.jupload2.exception.JUploadIOException;
44  import wjhk.jupload2.filedata.DefaultFileData;
45  import wjhk.jupload2.filedata.PictureFileData;
46  import wjhk.jupload2.policies.PictureUploadPolicy;
47  
48  /**
49   * This package provides low level methods, for picture management. It is used by {@link PictureFileData} to simplify
50   * its processing.
51   * 
52   * @author etienne_sf
53   */
54  public class ImageReaderWriterHelper {
55  
56      /**
57       * File input, from which the original picture should be read.
58       */
59      FileImageInputStream fileImageInputStream = null;
60  
61      /**
62       * File output stream for the current transformation.
63       */
64      FileImageOutputStream fileImageOutputStream;
65  
66      /**
67       * The {@link PictureFileData} that this helper will have to help.
68       */
69      PictureFileData pictureFileData;
70  
71      /**
72       * Current ImageReader. Initialized by {@link #initImageReader()}
73       */
74      ImageReader imageReader = null;
75  
76      /**
77       * Current ImageWriter. Initialized by {@link #initImageWriter()}
78       */
79      ImageWriter imageWriter = null;
80  
81      /**
82       * Current ImageWriter. Initialized by {@link #initImageWriter()}
83       */
84      ImageWriteParam imageWriterParam = null;
85  
86      /**
87       * Contains the target picture format (in lowercase): gif, jpg... It is used to find an ImageWriter, and to define
88       * the target filename.
89       */
90      String targetPictureFormat;
91  
92      /**
93       * The current upload policy must be a {@link PictureUploadPolicy}
94       */
95      PictureUploadPolicy uploadPolicy;
96  
97      // ////////////////////////////////////////////////////////////////////
98      // //////////////////// CONSTRUCTOR
99      // ////////////////////////////////////////////////////////////////////
100 
101     /**
102      * Standard constructor.
103      * 
104      * @param uploadPolicy The current upload policy.
105      * @param pictureFileData The file data to be 'helped'.
106      */
107     public ImageReaderWriterHelper(PictureUploadPolicy uploadPolicy, PictureFileData pictureFileData) {
108         this.uploadPolicy = uploadPolicy;
109         this.pictureFileData = pictureFileData;
110 
111         this.targetPictureFormat = uploadPolicy.getImageFileConversionInfo().getTargetFormat(
112                 pictureFileData.getFileExtension());
113     }
114 
115     // ////////////////////////////////////////////////////////////////////
116     // //////////////////// PUBLIC METHODS
117     // ////////////////////////////////////////////////////////////////////
118 
119     /**
120      * returns the target picture format (lowercase, may be the same as the file extension)
121      * 
122      * @return the target picture format (lowercase, may be the same as the file extension)
123      */
124     public String getTargetPictureFormat() {
125         return this.targetPictureFormat;
126     }
127 
128     /**
129      * Creates a FileImageOutputStream, and assign it as the output to the imageWriter.
130      * 
131      * @param file The file where the output stream must write.
132      * @throws JUploadIOException Any error...
133      */
134     public void setOutput(File file) throws JUploadIOException {
135         // We first initialize internal variable.
136         initImageWriter();
137 
138         try {
139             this.fileImageOutputStream = new FileImageOutputStream(file);
140         } catch (IOException e) {
141             throw new JUploadIOException("ImageReaderWriterHelper.setOutput()", e);
142         }
143         this.imageWriter.setOutput(this.fileImageOutputStream);
144     }
145 
146     /**
147      * Free all reserved resource by this helper. Includes closing of any input or output stream.
148      * 
149      * @throws JUploadIOException Any IO Exception
150      */
151     public void dispose() throws JUploadIOException {
152         // First: let's free any resource used by ImageWriter.
153         if (this.imageWriter != null) {
154             // An imageWriter was initialized. Let's free it.
155             this.imageWriter.dispose();
156             if (this.fileImageOutputStream != null) {
157                 try {
158                     this.fileImageOutputStream.close();
159                 } catch (IOException e) {
160                     throw new JUploadIOException("ImageReaderWriterHelper.dispose() [fileImageOutputStream]", e);
161                 }
162             }
163             this.imageWriter = null;
164             this.fileImageOutputStream = null;
165         }
166 
167         // Then, all about ImageReader
168         if (this.imageReader != null) {
169             // An imageReader was initialized. Let's free it.
170             this.imageReader.dispose();
171             try {
172                 this.fileImageInputStream.close();
173             } catch (IOException e) {
174                 throw new JUploadIOException("ImageReaderWriterHelper.dispose() [fileImageInputStream]", e);
175             }
176             this.imageReader = null;
177             this.fileImageInputStream = null;
178         }
179     }
180 
181     /**
182      * Call to imageReader.getNumImages(boolean). Caution: may be long, for big files.
183      * 
184      * @param allowSearch
185      * @return The number of image into this file.
186      * @throws JUploadIOException
187      */
188     public int getNumImages(boolean allowSearch) throws JUploadIOException {
189         initImageReader();
190         try {
191             return this.imageReader.getNumImages(allowSearch);
192         } catch (IOException e) {
193             throw new JUploadIOException("ImageReaderWriterHelper.getNumImages() [fileImageInputStream]", e);
194         }
195     }
196 
197     /**
198      * Call to ImageIO.read(fileImageInputStream).
199      * 
200      * @return The BufferedImage read
201      * @throws JUploadIOException public BufferedImage imageIORead() throws JUploadIOException { try { return
202      *             ImageIO.read(this.pictureFileData.getWorkingSourceFile()); } catch (IOException e) { throw new
203      *             JUploadIOException( "ImageReaderWriterHelper.ImageIORead()", e); } }
204      */
205 
206     /**
207      * Read an image, from the pictureFileData.
208      * 
209      * @param imageIndex The index number of the picture, in the file. 0 for the first picture (only valid value for
210      *            picture containing one picture)
211      * @return The image corresponding to this index, in the picture file.
212      * @throws JUploadIOException
213      * @throws IndexOutOfBoundsException Occurs when the imageIndex is wrong.
214      */
215     public BufferedImage readImage(int imageIndex) throws JUploadIOException, IndexOutOfBoundsException {
216         initImageReader();
217         try {
218             this.uploadPolicy.displayDebug("ImageReaderWriterHelper: reading picture number " + imageIndex
219                     + " of file " + this.pictureFileData.getFileName(), 30);
220             return this.imageReader.read(imageIndex);
221         } catch (IndexOutOfBoundsException e) {
222             // The IndexOutOfBoundsException is transmitted to the caller. It
223             // indicates that the index is out of bound. It's the good way for
224             // the caller to stop the loop through available pictures.
225             throw e;
226         } catch (IOException e) {
227             throw new JUploadIOException("ImageReaderWriterHelper.readImage(" + imageIndex + ")", e);
228         }
229     }
230 
231     /**
232      * Read an image, from the pictureFileData.
233      * 
234      * @param imageIndex The index number of the picture, in the file. 0 for the first picture (only valid value for
235      *            picture containing one picture)
236      * @return The full image data for this index
237      * @throws JUploadIOException
238      * @throws IndexOutOfBoundsException Occurs when the imageIndex is wrong.
239      */
240     public IIOImage readAll(int imageIndex) throws JUploadIOException, IndexOutOfBoundsException {
241         initImageReader();
242         try {
243             this.uploadPolicy.displayDebug("ImageReaderWriterHelper: reading picture number " + imageIndex
244                     + " of file " + this.pictureFileData.getFileName(), 30);
245             return this.imageReader.readAll(imageIndex, this.imageReader.getDefaultReadParam());
246         } catch (IndexOutOfBoundsException e) {
247             // The IndexOutOfBoundsException is transmitted to the caller. It
248             // indicates that the index is out of bound. It's the good way for
249             // the caller to stop the loop through available pictures.
250             throw e;
251         } catch (IOException e) {
252             throw new JUploadIOException("ImageReaderWriterHelper.readAll(" + imageIndex + ")", e);
253         }
254     }
255 
256     /**
257      * Load the metadata associated with one picture in the picture file.
258      * 
259      * @param imageIndex
260      * @return The metadata loaded
261      * @throws JUploadIOException Any IOException is encapsulated in this exception
262      */
263     public IIOMetadata getImageMetadata(int imageIndex) throws JUploadIOException {
264         // We must have the reader initialized
265         initImageReader();
266 
267         // Ok, let's go
268         try {
269             this.uploadPolicy.displayDebug("ImageReaderWriterHelper: reading metadata for picture number " + imageIndex
270                     + " of file " + this.pictureFileData.getFileName(), 30);
271             return this.imageReader.getImageMetadata(imageIndex);
272         } catch (IOException e) {
273             throw new JUploadIOException("ImageReaderWriterHelper.getImageMetadata()", e);
274         }
275     }
276 
277     /**
278      * Write a picture in the output picture file. Called just before an upload.
279      * 
280      * @param numIndex The index of the image in the transformed picture file.
281      * @param iioImage The image to write.
282      * @param iwp The parameter to use to write this image.
283      * @throws JUploadIOException
284      */
285     public void writeInsert(int numIndex, IIOImage iioImage, ImageWriteParam iwp) throws JUploadIOException {
286         initImageWriter();
287         try {
288             this.imageWriter.writeInsert(numIndex, iioImage, iwp);
289         } catch (IOException e) {
290             throw new JUploadIOException("ImageReaderWriterHelper.writeInsert()", e);
291         }
292     }
293 
294     /**
295      * Write a picture in the output picture file. Called just before an upload.
296      * 
297      * @param iioImage The image to write.
298      * @throws JUploadIOException
299      */
300     public void write(IIOImage iioImage) throws JUploadIOException {
301         initImageWriter();
302         try {
303             this.imageWriter.write(null, iioImage, this.imageWriterParam);
304         } catch (IOException e) {
305             throw new JUploadIOException("ImageReaderWriterHelper.write()", e);
306         }
307     }
308 
309     // ////////////////////////////////////////////////////////////////////
310     // //////////////////// PRIVATE METHODS
311     // ////////////////////////////////////////////////////////////////////
312 
313     /**
314      * Initialize the ImageWriter and the ImageWriteParam for the current picture helper.
315      * 
316      * @throws JUploadIOException
317      */
318     private void initImageWriter() throws JUploadIOException {
319         if (this.imageWriter == null) {
320             // Get the writer (to choose the compression quality)
321             // In the windows world, file extension may be in uppercase, which
322             // is not compatible with the core Java API.
323             Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(this.targetPictureFormat);
324             if (!iter.hasNext()) {
325                 // Too bad: no writer for the selected picture format
326 
327                 // A particular case: no gif support in JRE 1.5. A JRE upgrade
328                 // must be done.
329                 if (this.targetPictureFormat.equals("gif") && System.getProperty("java.version").startsWith("1.5")) {
330                     throw new JUploadIOException(
331                             "gif pictures are not supported in Java 1.5. Please switch to JRE 1.6.");
332                 }
333 
334                 throw new JUploadIOException("No writer for the '" + this.targetPictureFormat + "' picture format.");
335             } else {
336                 this.imageWriter = iter.next();
337                 this.imageWriterParam = this.imageWriter.getDefaultWriteParam();
338 
339                 // For jpeg pictures, we force the compression level.
340                 if (this.targetPictureFormat.equalsIgnoreCase("jpg")
341                         || this.targetPictureFormat.equalsIgnoreCase("jpeg")) {
342                     this.imageWriterParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
343                     // Let's select a good compromise between picture size
344                     // and quality.
345                     this.imageWriterParam.setCompressionQuality(this.uploadPolicy.getPictureCompressionQuality());
346                     // In some case, we need to force the Huffman tables:
347                     ((JPEGImageWriteParam) this.imageWriterParam).setOptimizeHuffmanTables(true);
348                 }
349 
350                 //
351                 try {
352                     this.uploadPolicy.displayDebug(
353                             "ImageWriter1 (used), CompressionQuality=" + this.imageWriterParam.getCompressionQuality(),
354                             50);
355                 } catch (Exception e2) {
356                     // If we come here, compression is not supported for
357                     // this picture format, or parameters are not explicit
358                     // mode, or ... (etc). May trigger several different
359                     // errors. We just ignore them: this par of code is only
360                     // to write some debug info.
361                     this.uploadPolicy.displayWarn(e2.getClass().getName() + " in ImageReaderWriterHelper.java");
362                 }
363             }
364         }
365     }// initImageWriter
366 
367     /**
368      * Initialize the ImageReader for the current helper.
369      * 
370      * @throws JUploadIOException
371      */
372     private void initImageReader() throws JUploadIOException {
373         // First: we open a ImageInputStream
374         try {
375             this.fileImageInputStream = new FileImageInputStream(this.pictureFileData.getWorkingSourceFile());
376         } catch (IOException e) {
377             throw new JUploadIOException("ImageReaderWriterHelper.initImageReader()", e);
378         }
379 
380         // Then: we create an ImageReader, and assign the ImageInputStream to
381         // it.
382         if (this.imageReader == null) {
383             String ext = DefaultFileData.getExtension(this.pictureFileData.getFileName());
384             Iterator<ImageReader> iterator = ImageIO.getImageReadersBySuffix(ext);
385             if (iterator.hasNext()) {
386                 this.imageReader = iterator.next();
387                 this.imageReader.setInput(this.fileImageInputStream);
388                 this.uploadPolicy.displayDebug("Foud one reader for " + ext + " extension", 50);
389             }// while
390 
391             // Did we find our reader ?
392             if (this.imageReader == null) {
393                 this.uploadPolicy.displayErr("Found no reader for " + ext + " extension");
394             } else if (this.uploadPolicy.getDebugLevel() >= 50) {
395                 // This call may be long, so we do it only if useful.
396                 try {
397                     this.uploadPolicy.displayDebug("Nb images in " + this.pictureFileData.getFileName() + ": "
398                             + this.imageReader.getNumImages(true), 50);
399                 } catch (IOException e) {
400                     // We mask the error, was just for debug...
401                 }
402             }
403         }
404     }
405 }