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