View Javadoc
1   //
2   // $Id$
3   //
4   // jupload - A file upload applet.
5   //
6   // Copyright 2008 The JUpload Team
7   //
8   // Created: 12 fevr. 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;
29  import java.awt.geom.AffineTransform;
30  import java.awt.image.AffineTransformOp;
31  import java.awt.image.BufferedImage;
32  import java.awt.image.ImageObserver;
33  
34  import wjhk.jupload2.exception.JUploadException;
35  import wjhk.jupload2.exception.JUploadIOException;
36  import wjhk.jupload2.filedata.DefaultFileData;
37  import wjhk.jupload2.filedata.PictureFileData;
38  import wjhk.jupload2.policies.PictureUploadPolicy;
39  
40  /**
41   * Class that contains various utilities about picture, mainly about picture transformation.
42   * 
43   * @author etienne_sf
44   */
45  public class ImageHelper implements ImageObserver {
46  
47      /**
48       * hasToTransformPicture indicates whether the picture should be transformed. Null if unknown. This can happen (for
49       * instance) if no calcul where done (during initialization), or after rotating the picture back to the original
50       * orientation. <BR>
51       * <B>Note:</B> this attribute is from the class Boolean (and not a simple boolean), to allow null value, meaning
52       * <I>unknown</I>.
53       */
54      private Boolean hasToTransformPicture = null;
55  
56      /**
57       * The {@link PictureFileData} that this helper will have to help.
58       */
59      private PictureFileData pictureFileData;
60  
61      /**
62       * Current rotation of the picture: 0 to 3.
63       * 
64       * @see PictureFileData
65       */
66      private int quarterRotation;
67  
68      /**
69       * Maximum width for the current transformation
70       */
71      private int maxWidth;
72  
73      /**
74       * Maximum height for the current transformation
75       */
76      private int maxHeight;
77  
78      /**
79       * Defines the number of pixel for the current picture. Used to update the progress bar.
80       * 
81       * @see #getBufferedImage(boolean, BufferedImage)
82       * @see #imageUpdate(Image, int, int, int, int, int)
83       */
84      private int nbPixelsTotal = -1;
85  
86      /**
87       * Indicates the number of pixels that have been read.
88       * 
89       * @see #nbPixelsTotal
90       * @see #imageUpdate(Image, int, int, int, int, int)
91       */
92      private int nbPixelsRead = 0;
93  
94      /**
95       * Width of picture, after rescaling but without rotation. It should be scale*originalWidth, but, due to rounding
96       * number, it can be transformed to scale*originalWidth-1.
97       * 
98       * @see #initScale()
99       */
100     private int scaledNonRotatedWidth = -1;
101 
102     /**
103      * Same as {@link #scaledNonRotatedWidth}
104      */
105     private int scaledNonRotatedHeight = -1;
106 
107     /**
108      * The value that has the progress bar when starting to load the picture. The
109      * {@link #imageUpdate(Image, int, int, int, int, int)} method will add from 0 to 100, to indicate progress with a
110      * percentage value of picture loading.
111      */
112     private int progressBarBaseValue = 0;
113 
114     /**
115      * Current scaling factor. If less than 1, means a picture reduction.
116      * 
117      * @see #initScale()
118      */
119     private double scale = 1;
120 
121     /**
122      * Width of picture, after re-scaling and rotation. It should be scale*originalWidth or scale*originalHeight
123      * (depending on the rotation). But, due to rounding number, it can be transformed to scale*originalWidth-1 or
124      * scale*originalHeight-1.
125      * 
126      * @see #initScale()
127      */
128     private int scaledRotatedWidth = -1;
129 
130     /**
131      * Same as {@link #scaledRotatedWidth}, for the height.
132      */
133     private int scaledRotatedHeight = -1;
134 
135     /**
136      * The current upload policy must be a {@link PictureUploadPolicy}
137      */
138     PictureUploadPolicy uploadPolicy;
139 
140     /**
141      * Standard constructor.
142      * 
143      * @param uploadPolicy The current upload policy
144      * @param pictureFileData The picture file data to help
145      * @param targetMaxWidth
146      * @param targetMaxHeight
147      * @param quarterRotation Current quarter rotation (from 0 to 3)
148      * @throws JUploadIOException
149      */
150     public ImageHelper(PictureUploadPolicy uploadPolicy, PictureFileData pictureFileData, int targetMaxWidth,
151             int targetMaxHeight, int quarterRotation) throws JUploadIOException {
152         this.uploadPolicy = uploadPolicy;
153         this.pictureFileData = pictureFileData;
154         this.maxWidth = targetMaxWidth;
155         this.maxHeight = targetMaxHeight;
156         this.quarterRotation = quarterRotation;
157 
158         // Pre-calculation: should the current picture be rescaled, to match the
159         // given target size ?
160         initScale();
161     }
162 
163     /**
164      * Intialization of scale factor, for the current picture state. The scale is based on the maximum width and height,
165      * the current rotation, and the picture size.
166      */
167     private void initScale() throws JUploadIOException {
168         double theta = Math.toRadians(90 * this.quarterRotation);
169 
170         // The width and height depend on the current rotation :
171         // calculation of the width and height of picture after
172         // rotation.
173         int nonScaledRotatedWidth = this.pictureFileData.getOriginalWidth();
174         int nonScaledRotatedHeight = this.pictureFileData.getOriginalHeight();
175         if (this.quarterRotation % 2 != 0) {
176             // 90 degrees or 270 degrees rotation: width and height are
177             // switched.
178             nonScaledRotatedWidth = this.pictureFileData.getOriginalHeight();
179             nonScaledRotatedHeight = this.pictureFileData.getOriginalWidth();
180         }
181         // Now, we can compare these width and height to the maximum
182         // width and height
183         double scaleWidth = ((this.maxWidth < 0) ? 1 : ((double) this.maxWidth) / nonScaledRotatedWidth);
184         double scaleHeight = ((this.maxHeight < 0) ? 1 : ((double) this.maxHeight) / nonScaledRotatedHeight);
185         this.scale = Math.min(scaleWidth, scaleHeight);
186         if (this.scale < 1) {
187             // With number rounding, it can happen that width or size
188             // became one pixel too big. Let's correct it.
189             if ((this.maxWidth > 0 && this.maxWidth < (int) (this.scale * Math.cos(theta) * nonScaledRotatedWidth))
190                     || (this.maxHeight > 0 && this.maxHeight < (int) (this.scale * Math.cos(theta) * nonScaledRotatedHeight))) {
191                 scaleWidth = ((this.maxWidth < 0) ? 1 : ((double) this.maxWidth - 1) / (nonScaledRotatedWidth));
192                 scaleHeight = ((this.maxHeight < 0) ? 1 : ((double) this.maxHeight - 1) / (nonScaledRotatedHeight));
193                 this.scale = Math.min(scaleWidth, scaleHeight);
194             }
195         }
196 
197         // These variables contain the actual width and height after
198         // rescaling, and before rotation.
199         this.scaledRotatedWidth = nonScaledRotatedWidth;
200         this.scaledRotatedHeight = nonScaledRotatedHeight;
201         // Is there any rescaling to do ?
202         // Patch for the first bug, tracked in the sourceforge bug
203         // tracker ! ;-)
204         if (this.scale < 1) {
205             this.scaledRotatedWidth *= this.scale;
206             this.scaledRotatedHeight *= this.scale;
207             this.uploadPolicy.displayDebug("Resizing factor (scale): " + this.scale, 30);
208         } else {
209             this.uploadPolicy.displayDebug("Resizing factor (scale): no resizing (calculated scale was " + this.scale
210                     + ")", 30);
211         }
212         // Due to rounded numbers, the resulting targetWidth or
213         // targetHeight
214         // may be one pixel too big. Let's check that.
215         if (this.scaledRotatedWidth > this.maxWidth) {
216             this.uploadPolicy.displayDebug("Correcting rounded width: " + this.scaledRotatedWidth + " to "
217                     + this.maxWidth, 50);
218             this.scaledRotatedWidth = this.maxWidth;
219         }
220         if (this.scaledRotatedHeight > this.maxHeight) {
221             this.uploadPolicy.displayDebug("Correcting rounded height: " + this.scaledRotatedHeight + " to "
222                     + this.maxHeight, 50);
223             this.scaledRotatedHeight = this.maxHeight;
224         }
225 
226         // getBufferedImage will need the two following value:
227         if (this.quarterRotation % 2 == 0) {
228             this.scaledNonRotatedWidth = this.scaledRotatedWidth;
229             this.scaledNonRotatedHeight = this.scaledRotatedHeight;
230         } else {
231             this.scaledNonRotatedWidth = this.scaledRotatedHeight;
232             this.scaledNonRotatedHeight = this.scaledRotatedWidth;
233         }
234     }
235 
236     /**
237      * This function indicate if the picture has to be modified. For instance : a maximum width, height, a target
238      * format...
239      * 
240      * @return true if the picture must be transformed. false if the file can be directly transmitted.
241      * @throws JUploadException Contains any exception that could be thrown in this method
242      */
243     public boolean hasToTransformPicture() throws JUploadException {
244         // Animated gif must be transmit as is, as I can't find a way to
245         // recreate them.
246         if (DefaultFileData.getExtension(this.pictureFileData.getFileName()).equalsIgnoreCase("gif")) {
247             // If this is an animated gif, no transformation... I can't succeed
248             // to create a transformed picture file for them.
249             ImageReaderWriterHelper irwh = new ImageReaderWriterHelper(this.uploadPolicy, this.pictureFileData);
250             int nbImages = irwh.getNumImages(true);
251             irwh.dispose();
252             irwh = null;
253             if (nbImages > 1) {
254                 // Too bad. We can not transform it.
255                 this.hasToTransformPicture = Boolean.FALSE;
256                 this.uploadPolicy
257                         .displayWarn("No transformation for gif picture file, that contain several pictures. (see JUpload documentation for details)");
258             }
259         }
260 
261         // Did we already estimate if transformation is needed ?
262         if (this.hasToTransformPicture == null) {
263 
264             // First : the easiest test. Should we block metadata ?
265             if (this.hasToTransformPicture == null && !(this.uploadPolicy).getPictureTransmitMetadata()) {
266                 this.hasToTransformPicture = Boolean.TRUE;
267                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName()
268                         + " : hasToTransformPicture=true (pictureTransmitMetadata is false)", 80);
269             }
270             // Second : another easy test. A rotation is needed ?
271             if (this.hasToTransformPicture == null && this.quarterRotation != 0) {
272                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName()
273                         + " : hasToTransformPicture = true (quarterRotation != 0)", 10);
274                 this.hasToTransformPicture = Boolean.TRUE;
275             }
276 
277             // Third : the picture format is the same ?
278             String targetFormat = this.uploadPolicy.getImageFileConversionInfo().getTargetFormatOrNull(
279                     this.pictureFileData.getFileExtension());
280             if (this.hasToTransformPicture == null && targetFormat != null) {
281                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName()
282                         + " : hasToTransformPicture = true (targetPictureFormat)", 10);
283                 this.hasToTransformPicture = Boolean.TRUE;
284             }
285 
286             // Fourth : should we resize the picture ?
287             if (this.hasToTransformPicture == null && this.scale < 1) {
288                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName()
289                         + " : hasToTransformPicture = true (scale < 1)", 10);
290                 this.hasToTransformPicture = Boolean.TRUE;
291             }
292 
293             // If we find no reason to transform the picture, then let's let the
294             // picture unmodified.
295             if (this.hasToTransformPicture == null) {
296                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName() + " : hasToTransformPicture = false",
297                         10);
298                 this.hasToTransformPicture = Boolean.FALSE;
299                 this.uploadPolicy.displayDebug(this.pictureFileData.getFileName() + " : hasToTransformPicture = false",
300                         10);
301             }
302         }
303 
304         return this.hasToTransformPicture.booleanValue();
305     }// end of hasToTransformPicture
306 
307     /**
308      * This function resizes the picture, if necessary, according to the maxWidth and maxHeight, given to the
309      * ImageHelper constructor. <BR>
310      * This function should only be called if isPicture is true. Otherwise, an exception is raised. <BR>
311      * Note (Update given by David Gnedt): the highquality will condition the call of getScaledInstance, instead of a
312      * basic scale Transformation. The generated picture is of better quality, but this is longer, especially on 'small'
313      * CPU. Time samples, with one picture from my canon EOS20D, on a PII 500M: <BR>
314      * ~3s for the full screen preview with highquality to false, and a quarter rotation. 12s to 20s with highquality to
315      * true. <BR>
316      * ~5s for the first (small) preview of the picture, with both highquality to false or true.
317      * 
318      * @param highquality (added by David Gnedt): if set to true, the BufferedImage.getScaledInstance() is called. This
319      *            generates better image, but consumes more CPU.
320      * @param sourceBufferedImage The image to resize or rotate or both or no tranformation...
321      * @return A BufferedImage which contains the picture according to current parameters (resizing, rotation...), or
322      *         null if this is not a picture.
323      * @throws JUploadException Contains any exception thrown from within this method.
324      */
325     public BufferedImage getBufferedImage(boolean highquality, BufferedImage sourceBufferedImage)
326             throws JUploadException {
327         long msGetBufferedImage = System.currentTimeMillis();
328         double theta = Math.toRadians(90 * this.quarterRotation);
329 
330         BufferedImage returnedBufferedImage = null;
331 
332         this.uploadPolicy.displayDebug("getBufferedImage: start", 10);
333 
334         try {
335             AffineTransform transform = new AffineTransform();
336 
337             if (this.quarterRotation != 0) {
338                 double translationX = 0, translationY = 0;
339                 this.uploadPolicy.displayDebug("getBufferedImage: quarter: " + this.quarterRotation, 50);
340 
341                 // quarterRotation is one of 0, 1, 2, 3 : see addRotation.
342                 // If we're here : it's not 0, so it's one of 1, 2 or 3.
343                 switch (this.quarterRotation) {
344                     case 1:
345                         translationX = 0;
346                         translationY = -this.scaledRotatedWidth;
347                         break;
348                     case 2:
349                         translationX = -this.scaledRotatedWidth;
350                         translationY = -this.scaledRotatedHeight;
351                         break;
352                     case 3:
353                         translationX = -this.scaledRotatedHeight;
354                         translationY = 0;
355                         break;
356                     default:
357                         this.uploadPolicy.displayWarn("Invalid quarterRotation : " + this.quarterRotation);
358                         this.quarterRotation = 0;
359                         theta = 0;
360                 }
361                 transform.rotate(theta);
362                 transform.translate(translationX, translationY);
363             }
364 
365             // If we have to rescale the picture, we first do it:
366             if (this.scale < 1) {
367                 if (highquality) {
368                     this.uploadPolicy
369                             .displayDebug("getBufferedImage: Resizing picture(using high quality picture)", 30);
370 
371                     // SCALE_AREA_AVERAGING forces the picture calculation
372                     // algorithm.
373                     // Other parameters give bad picture quality.
374                     Image img = sourceBufferedImage.getScaledInstance(this.scaledNonRotatedWidth,
375                             this.scaledNonRotatedHeight, Image.SCALE_AREA_AVERAGING);
376 
377                     // the localBufferedImage may be 'unknown'.
378                     int localImageType = sourceBufferedImage.getType();
379                     if (localImageType == BufferedImage.TYPE_CUSTOM) {
380                         localImageType = BufferedImage.TYPE_INT_BGR;
381                     }
382 
383                     BufferedImage tempBufferedImage = new BufferedImage(this.scaledNonRotatedWidth,
384                             this.scaledNonRotatedHeight, localImageType);
385 
386                     // drawImage can be long. Let's follow its progress,
387                     // with the applet progress bar.
388                     this.nbPixelsTotal = this.scaledNonRotatedWidth * this.scaledNonRotatedHeight;
389                     this.nbPixelsRead = 0;
390 
391                     // Let's draw the picture: this code do the rescaling.
392                     this.uploadPolicy.displayDebug("getBufferedImage: Before drawImage", 50);
393                     tempBufferedImage.getGraphics().drawImage(img, 0, 0, this);
394                     this.uploadPolicy.displayDebug("getBufferedImage: After drawImage", 50);
395 
396                     tempBufferedImage.flush();
397 
398                     img.flush();
399                     img = null;
400                     PictureFileData.freeMemory("ImageHelper.getBufferedImage()", this.uploadPolicy);
401 
402                     // tempBufferedImage contains the rescaled picture. It's
403                     // the source image for the next step (rotation).
404                     sourceBufferedImage = tempBufferedImage;
405                     tempBufferedImage = null;
406                 } else {
407                     // 'low' quality
408                     //
409                     // The scale method adds scaling before current
410                     // transformation.
411                     this.uploadPolicy.displayDebug(
412                             "getBufferedImage: Resizing picture(using standard quality picture)", 50);
413                     transform.scale(this.scale, this.scale);
414                 }
415             }
416 
417             if (transform.isIdentity()) {
418                 returnedBufferedImage = sourceBufferedImage;
419             } else {
420                 AffineTransformOp affineTransformOp = null;
421                 // Pictures are Ok.
422                 affineTransformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
423                 returnedBufferedImage = affineTransformOp.createCompatibleDestImage(sourceBufferedImage, null);
424                 // Checks, after the fact the pictures produces by the Canon
425                 // EOS 30D are not properly resized: colors are 'strange'
426                 // after resizing.
427                 this.uploadPolicy.displayDebug("getBufferedImage: returnedBufferedImage.getColorModel(): "
428                         + sourceBufferedImage.getColorModel().toString(), 50);
429                 this.uploadPolicy.displayDebug("getBufferedImage: returnedBufferedImage.getColorModel(): "
430                         + sourceBufferedImage.getColorModel().toString(), 50);
431                 affineTransformOp.filter(sourceBufferedImage, returnedBufferedImage);
432                 affineTransformOp = null;
433 
434                 returnedBufferedImage.flush();
435             }
436         } catch (Exception e) {
437             throw new JUploadException(e.getClass().getName() + " (" + this.getClass().getName()
438                     + ".getBufferedImage()) : " + e.getMessage());
439         }
440 
441         if (returnedBufferedImage != null && this.uploadPolicy.getDebugLevel() >= 50) {
442             this.uploadPolicy.displayDebug("getBufferedImage: " + returnedBufferedImage, 50);
443             this.uploadPolicy.displayDebug("getBufferedImage: MinX=" + returnedBufferedImage.getMinX(), 50);
444             this.uploadPolicy.displayDebug("getBufferedImage: MinY=" + returnedBufferedImage.getMinY(), 50);
445         }
446 
447         this.uploadPolicy.displayDebug("getBufferedImage: was " + (System.currentTimeMillis() - msGetBufferedImage)
448                 + " ms long", 50);
449         PictureFileData.freeMemory("ImageHelper.getBufferedImage()", this.uploadPolicy);
450         return returnedBufferedImage;
451     }
452 
453     /**
454      * This method is a work in progress
455      * 
456      * @param highquality
457      * @param sourceBufferedImage
458      * @return The calculated BufferedImage, resized and rotated according to the current configuration.
459      * @throws JUploadException
460      */
461     BufferedImage getBufferedImage2(boolean highquality, BufferedImage sourceBufferedImage) throws JUploadException {
462         long msGetBufferedImage = System.currentTimeMillis();
463         BufferedImage dest = null;
464 
465         // Scale factor calculation
466         this.uploadPolicy.displayDebug("getBufferedImage: quarter: " + this.quarterRotation, 50);
467 
468         // quarterRotation is one of 0, 1, 2, 3 : see addRotation.
469         @SuppressWarnings("unused")
470         int maxWidthBeforeRotation, maxHeigthBeforeRotation, widthBeforeRotation, heigthBeforeRotation, widthAfterRotation, heigthAfterRotation;
471         @SuppressWarnings("unused")
472         double theta = Math.toRadians(90 * this.quarterRotation);
473         switch (this.quarterRotation) {
474             case 0:
475             case 2:
476                 maxWidthBeforeRotation = this.uploadPolicy.getMaxWidth();
477                 maxHeigthBeforeRotation = this.uploadPolicy.getMaxHeight();
478                 widthBeforeRotation = sourceBufferedImage.getWidth();
479                 heigthBeforeRotation = sourceBufferedImage.getHeight();
480                 widthAfterRotation = sourceBufferedImage.getWidth();
481                 heigthAfterRotation = sourceBufferedImage.getHeight();
482                 break;
483             case 1:
484             case 3:
485                 maxWidthBeforeRotation = this.uploadPolicy.getMaxHeight();
486                 maxHeigthBeforeRotation = this.uploadPolicy.getMaxWidth();
487                 widthBeforeRotation = sourceBufferedImage.getHeight();
488                 heigthBeforeRotation = sourceBufferedImage.getWidth();
489                 widthAfterRotation = sourceBufferedImage.getHeight();
490                 heigthAfterRotation = sourceBufferedImage.getWidth();
491                 break;
492             default:
493                 throw new JUploadException("Invalid quarter rotation: <" + this.quarterRotation + ">");
494         }
495         double scaleWidthBeforeRotation = widthBeforeRotation / maxWidthBeforeRotation;
496         double scaleHeigthBeforeRotation = heigthBeforeRotation / maxHeigthBeforeRotation;
497         double scale = Math.min(scaleWidthBeforeRotation, scaleHeigthBeforeRotation);
498 
499         // First: we scale the picture... if necessary.
500         @SuppressWarnings("unused")
501         Image scaledPicture = sourceBufferedImage;
502         if (scale < 1) {
503             int targetWidthBeforeRotation, targetHeigthBeforeRotation;
504             if (scaleWidthBeforeRotation < scaleHeigthBeforeRotation) {
505                 // The constraint is on the width.
506                 targetWidthBeforeRotation = maxWidthBeforeRotation;
507                 targetHeigthBeforeRotation = (int) (heigthBeforeRotation * scale);
508             } else {
509                 // The constraint is on the heigth
510                 targetHeigthBeforeRotation = maxHeigthBeforeRotation;
511                 targetWidthBeforeRotation = (int) (widthBeforeRotation * scale);
512             }
513             int scale_xxx = highquality ? Image.SCALE_SMOOTH : Image.SCALE_FAST;
514             scaledPicture = sourceBufferedImage.getScaledInstance(targetWidthBeforeRotation,
515                     targetHeigthBeforeRotation, scale_xxx);
516         }// if (scale < 1)
517 
518         // Then, rotation of the scaled picture.
519         if (this.quarterRotation != 0) {
520             @SuppressWarnings("unused")
521             AffineTransform rotationTransform;
522             switch (this.quarterRotation) {
523                 case 0:
524                 case 2:
525                     maxWidthBeforeRotation = this.uploadPolicy.getMaxWidth();
526                     maxHeigthBeforeRotation = this.uploadPolicy.getMaxHeight();
527                     widthBeforeRotation = sourceBufferedImage.getWidth();
528                     heigthBeforeRotation = sourceBufferedImage.getHeight();
529                     widthAfterRotation = sourceBufferedImage.getWidth();
530                     heigthAfterRotation = sourceBufferedImage.getHeight();
531                     break;
532                 case 1:
533                 case 3:
534                     maxWidthBeforeRotation = this.uploadPolicy.getMaxHeight();
535                     maxHeigthBeforeRotation = this.uploadPolicy.getMaxWidth();
536                     widthBeforeRotation = sourceBufferedImage.getHeight();
537                     heigthBeforeRotation = sourceBufferedImage.getWidth();
538                     widthAfterRotation = sourceBufferedImage.getHeight();
539                     heigthAfterRotation = sourceBufferedImage.getWidth();
540                     break;
541                 default:
542                     throw new JUploadException("Invalid quarter rotation: <" + this.quarterRotation + ">");
543             }
544             // TODO finish this new version
545             // dest = new BufferedImage(widthAfterRotation, heigthAfterRotation,
546             // BufferedImage.TYPE_BYTE_INDEXED,sourceBufferedImage.getColorModel())
547 
548             // It's finished !
549             this.uploadPolicy.displayDebug("getBufferedImage: was " + (System.currentTimeMillis() - msGetBufferedImage)
550                     + " ms long", 50);
551         }
552         return dest;
553     }
554 
555     /**
556      * Implementation of the ImageObserver interface. Used to follow the drawImage progression, and update the applet
557      * progress bar.
558      * 
559      * @param img
560      * @param infoflags
561      * @param x
562      * @param y
563      * @param width
564      * @param height
565      * @return Whether or not the work must go on.
566      */
567     public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
568         if ((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) {
569             this.progressBarBaseValue = this.uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar()
570                     .getValue();
571             this.uploadPolicy.displayDebug("  imageUpdate (start of), progressBar geValue: "
572                     + this.progressBarBaseValue, 50);
573             int max = this.uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar().getMaximum();
574             this.uploadPolicy.displayDebug("  imageUpdate (start of), progressBar maximum: " + max, 50);
575         } else if ((infoflags & ImageObserver.SOMEBITS) == ImageObserver.SOMEBITS) {
576             this.nbPixelsRead += width * height;
577             int percentage = (int) ((long) this.nbPixelsRead * 100 / this.nbPixelsTotal);
578             this.uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar()
579                     .setValue(this.progressBarBaseValue + percentage);
580             // TODO: drawImage in another thread, to allow repaint of the
581             // progress bar ?
582             // Current status: the progress bar is only updated ... when
583             // draImage returns, that is: when everything is finished. NO
584             // interest.
585             this.uploadPolicy.getContext().getUploadPanel().getPreparationProgressBar().repaint();
586         } else if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) {
587             this.uploadPolicy.displayDebug("  imageUpdate, total number of pixels: " + this.nbPixelsRead + " read", 50);
588         }
589 
590         // We want to go on, after these bits
591         return true;
592     }
593 }