22
22
import java .awt .image .AreaAveragingScaleFilter ;
23
23
import java .awt .image .BufferedImage ;
24
24
import java .awt .image .BufferedImageOp ;
25
+ import java .awt .image .ColorModel ;
25
26
import java .awt .image .ConvolveOp ;
27
+ import java .awt .image .IndexColorModel ;
26
28
import java .awt .image .Kernel ;
27
29
30
+ import javax .imageio .ImageIO ;
31
+
32
+ // TODO: Add rotate and flip functionality
33
+
28
34
/**
29
35
* Class used to implement performant, good-quality and intelligent image
30
36
* scaling algorithms in native Java 2D. This class utilizes the Java2D
122
128
* <p/>
123
129
* Implementation of logging in this class is as efficient as possible; avoiding
124
130
* any calls to the logger or passing of arguments if logging is not enabled.
131
+ * <h3>GIF Transparency</h3>
132
+ * Unfortunately in Java 6 and earlier, support for GIF's
133
+ * {@link IndexColorModel} is sub-par, both in accurate color-selection and in
134
+ * maintaining transparency when moving to an image of type
135
+ * {@link BufferedImage#TYPE_INT_ARGB}; because of this issue when a GIF image
136
+ * is processed by imgscalr and the result saved as a GIF file, it is possible
137
+ * to lose the alpha channel of a transparent image or in the case of applying
138
+ * an optional {@link BufferedImageOp}, lose the entire picture all together in
139
+ * the result. Scalr currently does nothing to work around this manually because
140
+ * it is a defect in the platform that is half-fixed in Java 7 and all
141
+ * workarounds are relatively expensive, in the form of hand-creating and
142
+ * setting RGB values pixel-by-pixel with a custom {@link ColorModel} in the
143
+ * scaled image.
144
+ * <p>
145
+ * <strong>Workaround</strong>: A workaround to this issue with all version of
146
+ * Java is to simply save a GIF as a PNG; no change to your code needs to be
147
+ * made except when the image is saved out, e.g. using {@link ImageIO}. When a
148
+ * file type of "PNG" is used, both the transparency and high color quality will
149
+ * be maintained.
150
+ * <p>
151
+ * If the issue with optional {@link BufferedImageOp}s destroying GIF image
152
+ * content is ever fixed in the platform, saving out resulting images as GIFs
153
+ * should suddenly start working.
154
+ * <p>
155
+ * More can be read about the issue <a
156
+ * href="http://gman.eichberger.de/2007/07/transparent-gifs-in-java.html"
157
+ * >here</a> and <a
158
+ * href="http://ubuntuforums.org/archive/index.php/t-1060128.html">here</a>.
125
159
*
126
160
* @author Riyad Kalla (software@thebuzzmedia.com)
127
161
*/
128
162
public class Scalr {
163
+ /*
164
+ * TODO: Adjust the BufferedImageOps to be var-arg so you can pass multiple
165
+ * args.
166
+ *
167
+ * TODO: Add predefined AffineTransforms for FLIP and ROTATE. - 90 degree CW
168
+ * - 90 degree CCW
169
+ *
170
+ * these + Flip allow an image to be in any of 4 possible orientations
171
+ *
172
+ * TODO: Provide pre-defined AffineTransformOps that can be added to the op
173
+ * list that will perform the manipulation. Q: Maybe not, the target image
174
+ * dimensions wouldn't be known until after the rotation... maybe stick to
175
+ * the Enum approach and hide all the details. NOTE: Nevermind, should be
176
+ * OK, the Op itself will output an image of the right size I believe, need
177
+ * to test.
178
+ */
179
+
129
180
/**
130
181
* Flag used to indicate if debugging output has been enabled by setting the
131
182
* "imgscalr.debug" system property to <code>true</code>. This value will be
@@ -173,6 +224,11 @@ public class Scalr {
173
224
* <h3>Performance</h3>
174
225
* Use of this (and other) {@link ConvolveOp}s are hardware accelerated when
175
226
* possible.
227
+ * <h3>Known Issues</h3>
228
+ * In all versions of Java (tested up to Java 7 preview Build 131), running
229
+ * this op against a GIF with transparency and attempting to save the
230
+ * resulting image as a GIF results in a corrupted/empty file. The file must
231
+ * be saved out as a PNG to maintain the transparency.
176
232
*/
177
233
public static final ConvolveOp DEFAULT_ANTIALIAS_OP = new ConvolveOp (
178
234
new Kernel (3 , 3 , new float [] { .0f , .08f , .0f , .08f , .68f , .08f ,
@@ -290,7 +346,8 @@ public static enum Method {
290
346
*/
291
347
public static BufferedImage resize (BufferedImage src , int targetSize )
292
348
throws IllegalArgumentException {
293
- return resize (src , Method .AUTOMATIC , targetSize , targetSize , null );
349
+ return resize (src , Method .AUTOMATIC , targetSize , targetSize ,
350
+ (BufferedImageOp ) null );
294
351
}
295
352
296
353
/**
@@ -312,9 +369,10 @@ public static BufferedImage resize(BufferedImage src, int targetSize)
312
369
* @param targetSize
313
370
* The target width and height (square) that you wish the image
314
371
* to fit within.
315
- * @param op
316
- * The image operation (e.g. sharpen, blur, etc.) that can be
317
- * applied to the final result before returning the image.
372
+ * @param ops
373
+ * Zero or more optional image operations (e.g. sharpen, blur,
374
+ * etc.) that can be applied to the final result before returning
375
+ * the image.
318
376
*
319
377
* @return the proportionally scaled image with either a width or height of
320
378
* the given target size.
@@ -325,8 +383,8 @@ public static BufferedImage resize(BufferedImage src, int targetSize)
325
383
* @see #DEFAULT_ANTIALIAS_OP
326
384
*/
327
385
public static BufferedImage resize (BufferedImage src , int targetSize ,
328
- BufferedImageOp op ) throws IllegalArgumentException {
329
- return resize (src , Method .AUTOMATIC , targetSize , targetSize , op );
386
+ BufferedImageOp ... ops ) throws IllegalArgumentException {
387
+ return resize (src , Method .AUTOMATIC , targetSize , targetSize , ops );
330
388
}
331
389
332
390
/**
@@ -353,7 +411,8 @@ public static BufferedImage resize(BufferedImage src, int targetSize,
353
411
*/
354
412
public static BufferedImage resize (BufferedImage src , int targetWidth ,
355
413
int targetHeight ) throws IllegalArgumentException {
356
- return resize (src , Method .AUTOMATIC , targetWidth , targetHeight , null );
414
+ return resize (src , Method .AUTOMATIC , targetWidth , targetHeight ,
415
+ (BufferedImageOp ) null );
357
416
}
358
417
359
418
/**
@@ -381,9 +440,10 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
381
440
* The target width that you wish the image to have.
382
441
* @param targetHeight
383
442
* The target height that you wish the image to have.
384
- * @param op
385
- * The image operation (e.g. sharpen, blur, etc.) that can be
386
- * applied to the final result before returning the image.
443
+ * @param ops
444
+ * Zero or more optional image operations (e.g. sharpen, blur,
445
+ * etc.) that can be applied to the final result before returning
446
+ * the image.
387
447
*
388
448
* @return the proportionally scaled image with either a width or height of
389
449
* the given target size.
@@ -394,9 +454,9 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
394
454
* @see #DEFAULT_ANTIALIAS_OP
395
455
*/
396
456
public static BufferedImage resize (BufferedImage src , int targetWidth ,
397
- int targetHeight , BufferedImageOp op )
457
+ int targetHeight , BufferedImageOp ... ops )
398
458
throws IllegalArgumentException {
399
- return resize (src , Method .AUTOMATIC , targetWidth , targetHeight , op );
459
+ return resize (src , Method .AUTOMATIC , targetWidth , targetHeight , ops );
400
460
}
401
461
402
462
/**
@@ -422,7 +482,50 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
422
482
*/
423
483
public static BufferedImage resize (BufferedImage src , Method scalingMethod ,
424
484
int targetSize ) throws IllegalArgumentException {
425
- return resize (src , scalingMethod , targetSize , targetSize , null );
485
+ return resize (src , scalingMethod , targetSize , targetSize ,
486
+ (BufferedImageOp ) null );
487
+ }
488
+
489
+ /**
490
+ * Resize a given image (maintaining its original proportion) to a width and
491
+ * height of the given <code>targetSize</code> using the given scaling
492
+ * method and applying the given {@link BufferedImageOp} to the final result
493
+ * before returning it if one is provided.
494
+ * <p/>
495
+ * <strong>Performance</strong>: Not all {@link BufferedImageOp}s are
496
+ * hardware accelerated operations, but many of the most popular (like
497
+ * {@link ConvolveOp}) are. For more information on if your image op is
498
+ * hardware accelerated or not, check the source code of the underlying JDK
499
+ * class that actually executes the Op code, <a href=
500
+ * "http://www.docjar.com/html/api/sun/awt/image/ImagingLib.java.html"
501
+ * >sun.awt.image.ImagingLib</a>.
502
+ *
503
+ * @param src
504
+ * The image that will be scaled.
505
+ * @param scalingMethod
506
+ * The method used for scaling the image; preferring speed to
507
+ * quality or a balance of both.
508
+ * @param targetSize
509
+ * The target width and height (square) that you wish the image
510
+ * to fit within.
511
+ * @param ops
512
+ * Zero or more optional image operations (e.g. sharpen, blur,
513
+ * etc.) that can be applied to the final result before returning
514
+ * the image.
515
+ *
516
+ * @return the proportionally scaled image with either a width or height of
517
+ * the given target size.
518
+ *
519
+ * @throws IllegalArgumentException
520
+ * if <code>scalingMethod</code> is <code>null</code> or if
521
+ * <code>targetSize</code> is < 0.
522
+ *
523
+ * @see #DEFAULT_ANTIALIAS_OP
524
+ */
525
+ public static BufferedImage resize (BufferedImage src , Method scalingMethod ,
526
+ int targetSize , BufferedImageOp ... ops )
527
+ throws IllegalArgumentException {
528
+ return resize (src , scalingMethod , targetSize , targetSize , ops );
426
529
}
427
530
428
531
/**
@@ -454,7 +557,8 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
454
557
*/
455
558
public static BufferedImage resize (BufferedImage src , Method scalingMethod ,
456
559
int targetWidth , int targetHeight ) throws IllegalArgumentException {
457
- return resize (src , scalingMethod , targetWidth , targetHeight , null );
560
+ return resize (src , scalingMethod , targetWidth , targetHeight ,
561
+ (BufferedImageOp ) null );
458
562
}
459
563
460
564
/**
@@ -485,9 +589,10 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
485
589
* The target width that you wish the image to have.
486
590
* @param targetHeight
487
591
* The target height that you wish the image to have.
488
- * @param op
489
- * The image operation (e.g. sharpen, blur, etc.) that can be
490
- * applied to the final result before returning the image.
592
+ * @param ops
593
+ * Zero or more optional image operations (e.g. sharpen, blur,
594
+ * etc.) that can be applied to the final result before returning
595
+ * the image.
491
596
*
492
597
* @return the proportionally scaled image no bigger than the given width
493
598
* and height.
@@ -500,7 +605,7 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
500
605
* @see #DEFAULT_ANTIALIAS_OP
501
606
*/
502
607
public static BufferedImage resize (BufferedImage src , Method scalingMethod ,
503
- int targetWidth , int targetHeight , BufferedImageOp op )
608
+ int targetWidth , int targetHeight , BufferedImageOp ... ops )
504
609
throws IllegalArgumentException {
505
610
if (scalingMethod == null )
506
611
throw new IllegalArgumentException ("scalingMethod cannot be null" );
@@ -634,9 +739,24 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
634
739
}
635
740
}
636
741
637
- // Apply the image op if one was provided
638
- if (op != null )
639
- result = op .filter (result , null );
742
+ // Apply the image ops if any were provided
743
+ if (ops != null && ops .length > 0 ) {
744
+ if (DEBUG )
745
+ log ("Applying %d Image Ops to Result" , ops .length );
746
+
747
+ for (BufferedImageOp op : ops ) {
748
+ // In case a null op was passed in, skip it instead of dying
749
+ if (op == null )
750
+ continue ;
751
+
752
+ long opStartTime = System .currentTimeMillis ();
753
+ result = op .filter (result , null );
754
+
755
+ if (DEBUG )
756
+ log ("\t Image Op Applied in %d ms, Op: %s" ,
757
+ (System .currentTimeMillis () - opStartTime ), op );
758
+ }
759
+ }
640
760
641
761
if (DEBUG ) {
642
762
long elapsedTime = System .currentTimeMillis () - startTime ;
@@ -753,11 +873,11 @@ else if (length <= THRESHOLD_BALANCED_SPEED)
753
873
protected static BufferedImage scaleImage (BufferedImage src ,
754
874
int targetWidth , int targetHeight , Object interpolationHintValue ) {
755
875
/*
756
- * Determine the TYPE of image (plain RGB or RGB + Alpha) that we want
757
- * to render the scaled instance into. We force all rendering results
758
- * into one of these two types, avoiding the case where a source image
759
- * is of an unsupported (or poorly supported) format by Java2D and the
760
- * written results, when attempting to re-create and write out that
876
+ * Determine the RGB-based TYPE of image (plain RGB or RGB + Alpha) that
877
+ * we want to render the scaled instance into. We force all rendering
878
+ * results into one of these two types, avoiding the case where a source
879
+ * image is of an unsupported (or poorly supported) format by Java2D and
880
+ * the written results, when attempting to re-create and write out that
761
881
* format, is garbage.
762
882
*
763
883
* Originally reported by Magnus Kvalheim from Movellas when scaling
0 commit comments