1 //
2 // $Id: PictureFileData.java 287 2007-06-17 09:07:04 +0000 (dim., 17 juin 2007)
3 // felfert $
4 //
5 // jupload - A file upload applet.
6 // Copyright 2007 The JUpload Team
7 //
8 // Created: 2006-05-09
9 // Creator: etienne_sf
10 // Last modified: $Date: 2015-03-14 15:13:43 +0100 (sam., 14 mars 2015) $
11 //
12 // This program is free software; you can redistribute it and/or modify it under
13 // the terms of the GNU General Public License as published by the Free Software
14 // Foundation; either version 2 of the License, or (at your option) any later
15 // version. This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 // details. You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software Foundation, Inc.,
20 // 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 package wjhk.jupload2.filedata;
23
24 import java.awt.Canvas;
25 import java.awt.Image;
26 import java.awt.image.BufferedImage;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.util.Iterator;
34
35 import javax.imageio.IIOImage;
36 import javax.imageio.ImageIO;
37 import javax.imageio.ImageReader;
38 import javax.imageio.metadata.IIOMetadata;
39 import javax.imageio.stream.FileImageInputStream;
40 import javax.swing.ImageIcon;
41 import javax.swing.JOptionPane;
42
43 import wjhk.jupload2.exception.JUploadException;
44 import wjhk.jupload2.exception.JUploadIOException;
45 import wjhk.jupload2.filedata.helper.ImageHelper;
46 import wjhk.jupload2.filedata.helper.ImageReaderWriterHelper;
47 import wjhk.jupload2.policies.PictureUploadPolicy;
48 import wjhk.jupload2.policies.UploadPolicy;
49
50 /**
51 * This class contains all data about files to upload as a picture. It adds the following elements to the
52 * {@link wjhk.jupload2.filedata.FileData} class :<BR>
53 * <UL>
54 * <LI>Ability to define a target format (to convert pictures to JPG before upload, for instance)
55 * <LI>Optional definition of a maximal width and/or height.
56 * <LI>Ability to rotate a picture, with {@link #addRotation(int)}
57 * <LI>Ability to store a picture into a BufferedImage. This is actualy a bad idea within an applet (should run within a
58 * java application) : the applet runs very quickly out of memory. With pictures from my Canon EOS20D (3,5M), I can only
59 * display two pictures. The third one generates an out of memory error, despite the System.finalize and System.gc I've
60 * put everywhere in the code!
61 * </UL>
62 *
63 * @author etienne_sf
64 * @version $Revision: 1718 $
65 */
66 public class PictureFileData extends DefaultFileData {
67
68 /**
69 * Indicates if this file is a picture or not. This is bases on the return of ImageIO.getImageReadersByFormatName().
70 */
71 boolean isPicture = false;
72
73 /**
74 * This picture is precalculated, and stored to avoid to calculate it each time the user select this picture again,
75 * or each time the use switch from an application to another.
76 */
77 Image offscreenImage = null;
78
79 /**
80 * quarterRotation contains the current rotation that will be applied to the picture. Its value should be one of 0,
81 * 1, 2, 3. It is controled by the {@link #addRotation(int)} method.
82 * <UL>
83 * <LI>0 means no rotation.
84 * <LI>1 means a rotation of 90� clockwise (word = Ok ??).
85 * <LI>2 means a rotation of 180�.
86 * <LI>3 means a rotation of 900 counterclockwise (word = Ok ??).
87 * </UL>
88 */
89 int quarterRotation = 0;
90
91 /**
92 * Width of the original picture. The width is taken from the first image of the file. We expect that all pictures
93 * in a file are of the same size (for instance, for animated gif). Calculated in the
94 * {@link #PictureFileData(File, File, PictureUploadPolicy)} constructor.
95 */
96 int originalWidth = -1;
97
98 /**
99 * Same as {@link #originalWidth}, for the height of the first image in the picture file.
100 */
101 int originalHeight = -1;
102
103 /**
104 * transformedPictureFile contains the reference to the temporary file that stored the transformed picture, during
105 * upload. It is created by {@link #getInputStream()} and freed by {@link #afterUpload()}.
106 */
107 File transformedPictureFile = null;
108
109 /**
110 * uploadLength contains the uploadLength, which is : <BR>
111 * - The size of the original file, if no transformation is needed. <BR>
112 * - The size of the transformed file, if a transformation were made. <BR>
113 * <BR>
114 * It is set to -1 whenever its calculation is to be done again (for instance, when the user ask for a rotation,
115 * which is currently the only action that need to recalculate the picture).
116 */
117 long uploadLength = -1;
118
119 /**
120 * Contains the reference to a copy of the original picture files. Originally created because a SUN bug would
121 * prevent picture to be correctly resized if the original picture filename contains accents (or any non-ASCII
122 * characters).
123 */
124
125 File workingCopyTempFile = null;
126
127 /**
128 * will be set if in {@link #createTranformedPictureFile(ImageHelper)}, if an image-transformation has occured
129 */
130 String targetPictureFormat;
131
132 /**
133 * Standard constructor: needs a PictureFileDataPolicy.
134 *
135 * @param file The files which data are to be handled by this instance.
136 * @param root The root directory, to calculate the relative dir (see {@link #getRelativeDir()}.
137 * @param uploadPolicy The current upload policy
138 * @throws JUploadIOException Encapsulation of the IOException, if any would occurs.
139 */
140 public PictureFileData(File file, PictureUploadPolicy uploadPolicy) throws JUploadIOException {
141 super(file, uploadPolicy);
142
143 String fileExtension = getFileExtension();
144
145 // Is it a picture?
146 uploadPolicy.displayDebug("Looking for iterator of extension '" + file + "'", 80);
147 this.isPicture = isFileAPicture(file);
148
149 // Let's log the test result
150 uploadPolicy.displayDebug("isPicture=" + this.isPicture + " (" + file.getName() + "), extension="
151 + fileExtension, 50);
152
153 // If it's a picture, we override the default mime type:
154 if (this.isPicture) {
155 setMimeTypeByExtension(fileExtension);
156 }
157 }
158
159 /**
160 * Free any available memory. This method is called very often here, to be sure that we don't use too much memory.
161 * But we still run out of memory in some case.
162 *
163 * @param caller Indicate the method or treatment from which this method is called.
164 * @param uploadPolicy The current upload policy is not available, to this static method...
165 */
166 public static void freeMemory(String caller, UploadPolicy uploadPolicy) {
167 Runtime rt = Runtime.getRuntime();
168
169 rt.runFinalization();
170 rt.gc();
171
172 if (uploadPolicy.getDebugLevel() >= 50) {
173 uploadPolicy.displayDebug(
174 "freeMemory (after " + caller + ") : " + rt.freeMemory() + " (maxMemory: " + rt.maxMemory()
175 + ", totalMemory: " + rt.totalMemory() + ")", 50);
176 }
177 }
178
179 /**
180 * If this pictures needs transformation, a temporary file is created. This can occurs if the original picture is
181 * bigger than the maxWidth or maxHeight, of if it has to be rotated. This temporary file contains the transformed
182 * picture. <BR>
183 * The call to this method is optional, if the caller calls {@link #getUploadLength()}. This method calls
184 * beforeUpload() if the uploadLength is unknown.
185 */
186 @Override
187 public void beforeUpload(String uploadFileRoot) throws JUploadException {
188 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.beforeUpload()", 95);
189
190 if (!this.preparedForUpload) {
191 if (this.uploadLength < 0) {
192 try {
193 // Picture management is a big work. Let's try to free some
194 // memory.
195 freeMemory("Picture manabeforeUpload(): before initTransformedPictureFile", uploadPolicy);
196
197 // Get the transformed picture file, if needed.
198 initTransformedPictureFile();
199
200 // More debug output, to understand where the applet
201 // freezes.
202 uploadPolicy.displayDebug(this.getClass().getName()
203 + ".beforeUpload(): after call to initTransformedPictureFile()", 100);
204
205 } catch (OutOfMemoryError e) {
206 // Oups ! My EOS 20D has too big pictures to handle more
207 // than
208 // two pictures in a navigator applet !!!!!
209 // :-(
210 //
211 // We don't transform it. We clean the file, if it has been
212 // created.
213 // More debug output, to understand where the applet
214 // freezes.
215 uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): OutOfMemoryError", 30);
216 deleteTransformedPictureFile();
217 deleteWorkingCopyPictureFile();
218
219 // Let's try to free some memory.
220 freeMemory("beforeUpload(): in OutOfMemoryError", uploadPolicy);
221
222 tooBigPicture();
223 }
224
225 // If the transformed picture is correctly created, we'll upload
226 // it.
227 // Else we upload the original file.
228 synchronized (this) {
229 if (this.transformedPictureFile != null) {
230 this.uploadLength = this.transformedPictureFile.length();
231 setMimeTypeByExtension(this.targetPictureFormat);
232 } else {
233 this.uploadLength = getFile().length();
234 }
235 }
236 }
237 }
238
239 // Let's check that everything is Ok
240 // More debug output, to understand where the applet freezes.
241 uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): before call to super.beforeUpload()",
242 100);
243
244 super.beforeUpload(uploadFileRoot);
245
246 // Let's check that everything is Ok
247 // More debug output, to understand where the applet freezes.
248 uploadPolicy.displayDebug(this.getClass().getName() + ".beforeUpload(): after call to super.beforeUpload()",
249 100);
250 }
251
252 /**
253 * Returns the number of bytes, for this upload. If needed, that is, if uploadlength is unknown,
254 * {@link #beforeUpload()} is called.
255 *
256 * @return The length of upload. In this class, this is ... the size of the original file, or the transformed file!
257 */
258 @Override
259 public long getUploadLength() {
260 // Just for debug, to be removed before release.
261
262 if (!this.preparedForUpload) {
263 throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
264 }
265 return this.uploadLength;
266 }
267
268 /**
269 * This function create an input stream for this file. The caller is responsible for closing this input stream. <BR>
270 * This function assumes that the {@link #getUploadLength()} method has already be called : it is responsible for
271 * creating the temporary file (if needed). If not called, the original file will be sent.
272 *
273 * @return An inputStream
274 */
275 @Override
276 public synchronized InputStream getInputStream() throws JUploadException {
277 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.getInputStream()", 95);
278 if (!this.preparedForUpload) {
279 throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
280 }
281 // Do we have to transform the picture ?
282 if (this.transformedPictureFile != null) {
283 try {
284 return new FileInputStream(this.transformedPictureFile);
285 } catch (FileNotFoundException e) {
286 throw new JUploadIOException(e);
287 }
288 }
289 // Otherwise : we read the file, in the standard way.
290 return super.getInputStream();
291 }
292
293 /**
294 * Cleaning of the temporary file on the hard drive, if any. <BR>
295 * <B>Note:</B> if the debugLevel is 100 (or more) this temporary file is not removed. This allow control of this
296 * created file.
297 */
298 @Override
299 public void afterUpload() {
300 // Free the temporary file ... if any.
301 deleteTransformedPictureFile();
302 deleteWorkingCopyPictureFile();
303 this.uploadLength = -1;
304
305 super.afterUpload();
306 }
307
308 /**
309 * This method creates a new Image, from the current picture. The resulting width and height will be less or equal
310 * than the given maximum width and height. The scale is maintained. Thus the width or height may be inferior than
311 * the given values.
312 *
313 * @param canvas The canvas on which the picture will be displayed.
314 * @param shadow True if the pictureFileData should store this picture. False if the pictureFileData instance should
315 * not store this picture. Store this picture avoid calculating the image each time the user selects it
316 * in the file panel.
317 * @return The rescaled image.
318 * @throws JUploadException Encapsulation of the Exception, if any would occurs.
319 */
320 public Image getImage(Canvas canvas, boolean shadow) throws JUploadException {
321 Image localImage = null;
322
323 // ////////////////////////////////////////////////////////////////////////
324 // ////////////// Some preliminary tests.
325 // ////////////////////////////////////////////////////////////////////////
326
327 if (canvas == null) {
328 throw new JUploadException("canvas null in PictureFileData.getImage");
329 }
330
331 if (shadow && this.offscreenImage != null) {
332 return this.offscreenImage;
333 }
334
335 int canvasWidth = canvas.getWidth();
336 int canvasHeight = canvas.getHeight();
337 if (canvasWidth <= 0 || canvasHeight <= 0) {
338 uploadPolicy.displayDebug("canvas width and/or height null in PictureFileData.getImage()", 1);
339 return null;
340 }
341
342 if (!this.isPicture) {
343 uploadPolicy
344 .displayWarn("canvas width and/or height null in PictureFileData.getImage(). PictureFileData.getImage will return null");
345 return null;
346 }
347 // ////////////////////////////////////////////////////////////////////////
348 // ////////////// End of preliminary tests: let's work.
349 // ////////////////////////////////////////////////////////////////////////
350
351 try {
352 // First: load the picture.
353 ImageReaderWriterHelper irwh = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy, this);
354 BufferedImage sourceImage = irwh.readImage(0);
355 irwh.dispose();
356 irwh = null;
357 ImageHelper ih = new ImageHelper((PictureUploadPolicy) uploadPolicy, this, canvasWidth, canvasHeight,
358 this.quarterRotation);
359 localImage = ih.getBufferedImage(((PictureUploadPolicy) uploadPolicy).getHighQualityPreview(), sourceImage);
360 // We free memory ASAP.
361 sourceImage.flush();
362 sourceImage = null;
363 } catch (OutOfMemoryError e) {
364 // Too bad
365 localImage = null;
366 tooBigPicture();
367 }
368
369 // We store it, if asked to.
370 if (shadow) {
371 this.offscreenImage = localImage;
372 }
373
374 freeMemory("end of " + this.getClass().getName() + ".getImage()", uploadPolicy);
375
376 // The picture is now loaded. We clear the progressBar
377 uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar().setValue(0);
378
379 return localImage;
380 }// getImage
381
382 /**
383 * This function is used to rotate the picture. The current rotation state is kept in the quarterRotation private
384 * attribute.
385 *
386 * @param quarter Number of quarters (90 degrees) the picture should rotate. 1 means rotating of 90 degrees
387 * clockwise. Can be negative.
388 */
389 public void addRotation(int quarter) {
390 this.quarterRotation += quarter;
391
392 // We'll have to recalculate the upload length, as the resulting file is
393 // different.
394 // If any file has been prepared, they must be deleted
395 deleteWorkingCopyPictureFile();
396 deleteTransformedPictureFile();
397 this.uploadLength = -1;
398
399 // We keep the 'quarter' in the segment [0;4[
400 while (this.quarterRotation < 0) {
401 this.quarterRotation += 4;
402 }
403 while (this.quarterRotation >= 4) {
404 this.quarterRotation -= 4;
405 }
406
407 // We need to change the precalculated picture, if any
408 if (this.offscreenImage != null) {
409 this.offscreenImage.flush();
410 this.offscreenImage = null;
411 }
412 }
413
414 /**
415 * Indicates if this file is actually a picture or not.
416 *
417 * @return the isPicture flag.
418 */
419 public boolean isPicture() {
420 return this.isPicture;
421 }
422
423 /** @see FileData#getMimeType() */
424 @Override
425 public String getMimeType() {
426 return this.mimeType;
427 }
428
429 // ///////////////////////////////////////////////////////////////////////////////////////////
430 // /////////////////////////// private METHODS
431 // ///////////////////////////////////////////////////////////////////////////////////////////
432
433 /**
434 * File.deleteOnExit() is pretty unreliable, especially in applets. Therefore the applet provides a callback which
435 * is executed during applet termination. This method performs the actual cleanup.
436 */
437 public synchronized void deleteTransformedPictureFile() {
438 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.deleteTransformedPictureFile()", 95);
439 // Free the temporary file ... if any.
440 if (null != this.transformedPictureFile && uploadPolicy.getDebugLevel() <= 100) {
441 if (!this.transformedPictureFile.delete()) {
442 uploadPolicy.displayWarn("Unable to delete " + this.transformedPictureFile.getName());
443 }
444 this.transformedPictureFile = null;
445
446 }
447 }
448
449 /**
450 * Creation of a temporary file, that contains the transformed picture. For instance, it can be resized or rotated.
451 * This method doesn't throw exception when there is an IOException within its procedure. If an exception occurs
452 * while building the temporary file, the exception is caught, a warning is displayed, the temporary file is deleted
453 * (if it was created), and the upload will go on with the original file. <BR>
454 * Note: any JUploadException thrown by a method called within getTransformedPictureFile() will be thrown within
455 * this method.
456 */
457 void initTransformedPictureFile() throws JUploadException {
458 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.initTransformedPictureFile()", 95);
459 int targetMaxWidth;
460 int targetMaxHeight;
461
462 // If the image is rotated, we compare to realMaxWidth and
463 // realMaxHeight, instead of maxWidth and maxHeight. This allows
464 // to have a different picture size for rotated and not rotated
465 // pictures. See the UploadPolicy javadoc for details ... and a
466 // good reason ! ;-)
467 if (this.quarterRotation == 0) {
468 targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getMaxWidth();
469 targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getMaxHeight();
470 } else {
471 targetMaxWidth = ((PictureUploadPolicy) uploadPolicy).getRealMaxWidth();
472 targetMaxHeight = ((PictureUploadPolicy) uploadPolicy).getRealMaxHeight();
473 }
474
475 // Some Helper will .. help us !
476 // I like useful comment :-)
477 ImageHelper imageHelper = new ImageHelper((PictureUploadPolicy) uploadPolicy, this, targetMaxWidth,
478 targetMaxHeight, this.quarterRotation);
479
480 // Should transform the file, and do we already created the transformed
481 // file ?
482 synchronized (this) {
483 if (imageHelper.hasToTransformPicture() && this.transformedPictureFile == null) {
484
485 // We have to create a resized or rotated picture file, and all
486 // needed information.
487 // ...let's do it
488 try {
489 createTranformedPictureFile(imageHelper);
490 } catch (JUploadException e) {
491 // Hum, too bad.
492 // if any file was created, we remove it.
493 deleteTransformedPictureFile();
494 throw e;
495 }
496 }
497 }
498 }// end of initTransformedPictureFile
499
500 /**
501 * Creates a transformed picture file of the given max width and max height. If the {@link #transformedPictureFile}
502 * attribute is not set before calling this method, it will be set. If set before, the existing
503 * {@link #transformedPictureFile} is replaced by the newly transformed picture file. It is cleared if an error
504 * occured. <BR>
505 *
506 * @param imageHelper The {@link ImageHelper} that was initialized with current parameters.
507 */
508 synchronized void createTranformedPictureFile(ImageHelper imageHelper) throws JUploadException {
509 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedPictureFile()", 95);
510
511 IIOMetadata metadata = null;
512 IIOImage iioImage = null;
513 BufferedImage originalImage = null;
514 BufferedImage transformedImage = null;
515 ImageReaderWriterHelper imageWriterHelper = new ImageReaderWriterHelper((PictureUploadPolicy) uploadPolicy,
516 this);
517 boolean transmitMetadata = ((PictureUploadPolicy) uploadPolicy).getPictureTransmitMetadata();
518
519 // Creation of the transformed picture file.
520 createTransformedTempFile();
521 this.targetPictureFormat = imageWriterHelper.getTargetPictureFormat();
522 imageWriterHelper.setOutput(this.transformedPictureFile);
523
524 // How many picture should we read from the input file.
525 // Default number of pictures is one.
526 int nbPictures = 1;
527 // For gif file, we put a max to MAX_VALUE, and we check the
528 // IndexOutOfBoundsException to identify when we've read all pictures
529 if (getExtension(getFileName()).equalsIgnoreCase("gif")) {
530 nbPictures = Integer.MAX_VALUE;
531 }
532 uploadPolicy.displayDebug("Reading image with imageWriterHelper.readImage(i)", 50);
533 // Now, we have to read each picture from the original file, apply
534 // the calculated transformation, and write each transformed picture
535 // to the writer.
536 // As indicated in javadoc for ImageReader.getNumImages(), we go
537 // through pictures, until we get an IndexOutOfBoundsException.
538 try {
539 for (int i = 0; i < nbPictures; i += 1) {
540 originalImage = imageWriterHelper.readImage(i);
541 transformedImage = imageHelper.getBufferedImage(true, originalImage);
542
543 // If necessary, we load the metadata for the current
544 // picture
545 if (transmitMetadata) {
546 metadata = imageWriterHelper.getImageMetadata(i);
547 }
548
549 iioImage = new IIOImage(transformedImage, null, metadata);
550 imageWriterHelper.write(iioImage);
551
552 // Let's clear picture, to force getBufferedImage to read a new
553 // one,
554 // in the next loop.
555 if (originalImage != null) {
556 originalImage.flush();
557 originalImage = null;
558 }
559 }// for
560 } catch (IndexOutOfBoundsException e) {
561 // Was sent by imageWriterHelper.readImage(i)
562 // Ok, no more picture to read. We just want to go out of
563 // the loop. No error.
564 uploadPolicy
565 .displayDebug("IndexOutOfBoundsException catched: end of reading for file " + getFileName(), 10);
566 }
567
568 if (originalImage != null) {
569 originalImage.flush();
570 originalImage = null;
571 }
572
573 // Let's free any used resource.
574 imageWriterHelper.dispose();
575
576 }
577
578 /**
579 * This method is called when an OutOfMemoryError occurs. This can easily happen within the navigator, with big
580 * pictures: I've put a lot of freeMemory calls within the code, but they don't seem to work very well. When running
581 * from eclipse, the memory is freed Ok !
582 */
583 void tooBigPicture() {
584 String msg = uploadPolicy.getLocalizedString("tooBigPicture", getFileName());
585 uploadPolicy.displayWarn(msg);
586 JOptionPane.showMessageDialog(null, msg, "Warning", JOptionPane.WARNING_MESSAGE);
587 }
588
589 /**
590 * This methods set the {@link DefaultFileData#mimeType} to the image mime type, that should be associate with the
591 * picture.
592 */
593 void setMimeTypeByExtension(String fileExtension) {
594 String ext = fileExtension.toLowerCase();
595 if (ext.equals("jpg")) {
596 ext = "jpeg";
597 }
598 this.mimeType = "image/" + ext;
599 }
600
601 /**
602 * If {@link #transformedPictureFile} is null, create a new temporary file, and assign it to
603 * {@link #transformedPictureFile}. Otherwise, no action.
604 *
605 * @throws IOException
606 */
607 synchronized void createTransformedTempFile() throws JUploadIOException {
608 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createTransformedTempFile()", 95);
609
610 if (this.transformedPictureFile == null) {
611 try {
612 this.transformedPictureFile = File.createTempFile("jupload_", ".tmp");
613 } catch (IOException e) {
614 throw new JUploadIOException("PictureFileData.createTransformedTempFile()", e);
615 }
616 uploadPolicy.getContext().registerUnload(this, "deleteTransformedPictureFile");
617 uploadPolicy.displayDebug("Using transformed temp file " + this.transformedPictureFile.getAbsolutePath()
618 + " for " + getFileName(), 30);
619 }
620 }
621
622 /**
623 * This method loads the picture width and height of the picture. It's called by the current instance when
624 * necessary.
625 *
626 * @throws JUploadIOException
627 * @see #getOriginalHeight()
628 * @see #getOriginalWidth()
629 */
630 void initWidthAndHeight() throws JUploadIOException {
631 // Is it a picture?
632 if (this.isPicture && (this.originalHeight < 0 || this.originalWidth < 0)) {
633 // Ok: it's a picture and is original width and height have not been
634 // loaded yet.
635 // In the windows world, file extension may be in upper case, which
636 // is not compatible with the core Java API.
637 Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(getFileExtension().toLowerCase());
638 if (iter.hasNext()) {
639 // It's a picture: we store its original width and height, for
640 // further calculation (rescaling and rotation).
641 try {
642 FileImageInputStream fiis = new FileImageInputStream(getFile());
643 ImageReader ir = iter.next();
644 ir.setInput(fiis);
645 this.originalHeight = ir.getHeight(0);
646 this.originalWidth = ir.getWidth(0);
647 ir.dispose();
648 fiis.close();
649 } catch (IOException e) {
650 throw new JUploadIOException("PictureFileData()", e);
651 }
652 }
653 }
654 }
655
656 /**
657 * If {@link #workingCopyTempFile} is null, create a new temporary file, and assign it to
658 * {@link #transformedPictureFile}. Otherwise, no action.
659 *
660 * @throws IOException
661 */
662 synchronized void createWorkingCopyTempFile() throws IOException {
663 uploadPolicy.displayDebug(this.hashCode() + "|Entering PictureFileData.createWorkingCopyTempFile()", 95);
664 if (this.workingCopyTempFile == null) {
665 // The temporary file must have the correct extension, so that
666 // native Java method works on it.
667 this.workingCopyTempFile = File.createTempFile("jupload_",
668 ".tmp." + DefaultFileData.getExtension(getFileName()));
669 uploadPolicy.getContext().registerUnload(this, "deleteWorkingCopyPictureFile");
670 uploadPolicy.displayDebug("Using working copy temp file " + this.workingCopyTempFile.getAbsolutePath()
671 + " for " + getFileName(), 30);
672 }
673 }
674
675 /**
676 * File.deleteOnExit() is pretty unreliable, especially in applets. Therefore the applet provides a callback which
677 * is executed during applet termination. This method performs the actual cleanup.
678 */
679 public synchronized void deleteWorkingCopyPictureFile() {
680 // for debug : if the debugLevel is enough, we keep the temporary
681 // file (for check).
682 if (null != this.workingCopyTempFile && uploadPolicy.getDebugLevel() <= 100) {
683 if (!this.workingCopyTempFile.delete()) {
684 uploadPolicy.displayWarn("Unable to delete " + this.workingCopyTempFile.getName());
685 }
686 this.workingCopyTempFile = null;
687
688 }
689 }
690
691 /**
692 * Get the file that contains the original picture. This is used as a workaround for the following JVM bug: once in
693 * the navigator, it can't transform picture read from a file whose name contains non-ASCII characters, like French
694 * accents.
695 *
696 * @return The file that contains the original picture, as the source for picture transformation
697 * @throws JUploadIOException
698 */
699 public synchronized File getWorkingSourceFile() throws JUploadIOException {
700
701 if (this.workingCopyTempFile == null) {
702 uploadPolicy.displayDebug("[getWorkingSourceFile] Creating a copy of " + getFileName()
703 + " as a source working target.", 30);
704 FileInputStream is = null;
705 FileOutputStream os = null;
706 try {
707 createWorkingCopyTempFile();
708
709 is = new FileInputStream(getFile());
710 os = new FileOutputStream(this.workingCopyTempFile);
711 byte b[] = new byte[1024];
712 int l;
713 while ((l = is.read(b)) > 0) {
714 os.write(b, 0, l);
715 }
716 } catch (IOException e) {
717 throw new JUploadIOException("ImageReaderWriterHelper.getWorkingSourceFile()", e);
718 } finally {
719 if (is != null) {
720 try {
721 is.close();
722 } catch (IOException e) {
723 uploadPolicy
724 .displayWarn(e.getClass().getName()
725 + " while trying to close FileInputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
726 } finally {
727 is = null;
728 }
729 }
730 if (os != null) {
731 try {
732 os.close();
733 } catch (IOException e) {
734 uploadPolicy
735 .displayWarn(e.getClass().getName()
736 + " while trying to close FileOutputStream, in PictureUploadPolicy.copyOriginalToWorkingCopyTempFile.");
737 } finally {
738 os = null;
739 }
740 }
741 }
742 }
743 return this.workingCopyTempFile;
744 }// getWorkingSourceFile()
745
746 /**
747 * @return the originalWidth of the picture
748 * @throws JUploadIOException
749 */
750 public int getOriginalWidth() throws JUploadIOException {
751 initWidthAndHeight();
752 return this.originalWidth;
753 }
754
755 /**
756 * @return the originalHeight of the picture
757 * @throws JUploadIOException
758 */
759 public int getOriginalHeight() throws JUploadIOException {
760 initWidthAndHeight();
761 return this.originalHeight;
762 }
763
764 // ////////////////////////////////////////////////////////////////////////////////////////////////////
765 // /////////////////////// static methods
766 // ////////////////////////////////////////////////////////////////////////////////////////////////////
767
768 /**
769 * Returns an ImageIcon for the given file, resized according to the given dimensions. If the original file contains
770 * a pictures smaller than these width and height, the picture is returned as is (nor resized).
771 *
772 * @param pictureFile The file, containing a picture, from which the user wants to extract a static picture.
773 * @param maxWidth The maximum allowed width for the static picture to generate.
774 * @param maxHeight The maximum allowed height for the static picture to generate.
775 * @param uploadPolicy The current upload policy, for logging when calling freeMemory.
776 * @return The created static picture, or null if the file is null.
777 * @throws JUploadException If the ImageIcon can not be loaded.
778 */
779 public static ImageIcon getImageIcon(File pictureFile, int maxWidth, int maxHeight, UploadPolicy uploadPolicy)
780 throws JUploadException {
781 ImageIcon imageIcon = null;
782
783 if (pictureFile != null) {
784 BufferedImage tmpImage = null;
785 try {
786 // We need a reader for this picture.
787 if (pictureFile.canRead() && pictureFile.isFile()) {
788 // It seems to be a valid file. Can we get its icon?
789 if (ImageIO.getImageReaders(new FileImageInputStream(pictureFile)).hasNext()) {
790 tmpImage = ImageIO.read(pictureFile);
791 }
792 }
793 } catch (IOException e) {
794 throw new JUploadIOException("An error occured while loading the image for "
795 + pictureFile.getAbsolutePath(), e);
796 }
797 if (tmpImage != null) {
798 // Let's calculate the asked icon.
799 double scaleWidth = ((double) maxWidth) / tmpImage.getWidth();
800 double scaleHeight = ((double) maxHeight) / tmpImage.getHeight();
801 double scale = Math.min(scaleWidth, scaleHeight);
802
803 if (scale < 1) {
804 int width = (int) (scale * tmpImage.getWidth());
805 int height = (int) (scale * tmpImage.getHeight());
806 // We need to miniaturize the current Icon
807 Image rescaledImage = tmpImage.getScaledInstance(width, height, Image.SCALE_FAST);
808 BufferedImage tempBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
809 tempBufferedImage.getGraphics().drawImage(rescaledImage, 0, 0, null);
810 imageIcon = new ImageIcon(tempBufferedImage);
811 uploadPolicy.displayDebug("Icon generated (witdh=" + imageIcon.getIconWidth() + ", imageheight="
812 + imageIcon.getIconHeight() + ")", 80);
813 }
814 tmpImage.flush();
815 tmpImage = null;
816
817 freeMemory("PictureFileData.getImageIcon()", uploadPolicy);
818 }
819 }
820 return imageIcon;
821 }
822
823 /**
824 * Indicates whether a file is a picture or not. The information is based on the fact the an ImageRead is found, or
825 * not, for this file. This test uses the core Java API. As in the windows world, file extension may be in
826 * uppercase, the test is based on the lowercase value for the given file extension.
827 *
828 * @param file
829 * @return true if the file can be opened as a picture, false otherwise.
830 */
831 public static boolean isFileAPicture(File file) {
832 // In the windows world, file extension may be in uppercase, which is
833 // not compatible with the core Java API.
834 Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(DefaultFileData.getExtension(file.getName())
835 .toLowerCase());
836 return iter.hasNext();
837 }
838 }