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 }