Skip to content

Commit 03add0e

Browse files
author
Riyad Kalla
committed
Preparing for 3.1 release; made BIO a var-arg so you can execute 1 or more ops. It should have
been this way from the beginning.
1 parent a75b664 commit 03add0e

File tree

1 file changed

+146
-26
lines changed

1 file changed

+146
-26
lines changed

src/main/java/com/thebuzzmedia/imgscalr/Scalr.java

+146-26
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@
2222
import java.awt.image.AreaAveragingScaleFilter;
2323
import java.awt.image.BufferedImage;
2424
import java.awt.image.BufferedImageOp;
25+
import java.awt.image.ColorModel;
2526
import java.awt.image.ConvolveOp;
27+
import java.awt.image.IndexColorModel;
2628
import java.awt.image.Kernel;
2729

30+
import javax.imageio.ImageIO;
31+
32+
// TODO: Add rotate and flip functionality
33+
2834
/**
2935
* Class used to implement performant, good-quality and intelligent image
3036
* scaling algorithms in native Java 2D. This class utilizes the Java2D
@@ -122,10 +128,55 @@
122128
* <p/>
123129
* Implementation of logging in this class is as efficient as possible; avoiding
124130
* 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>.
125159
*
126160
* @author Riyad Kalla (software@thebuzzmedia.com)
127161
*/
128162
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+
129180
/**
130181
* Flag used to indicate if debugging output has been enabled by setting the
131182
* "imgscalr.debug" system property to <code>true</code>. This value will be
@@ -173,6 +224,11 @@ public class Scalr {
173224
* <h3>Performance</h3>
174225
* Use of this (and other) {@link ConvolveOp}s are hardware accelerated when
175226
* 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.
176232
*/
177233
public static final ConvolveOp DEFAULT_ANTIALIAS_OP = new ConvolveOp(
178234
new Kernel(3, 3, new float[] { .0f, .08f, .0f, .08f, .68f, .08f,
@@ -290,7 +346,8 @@ public static enum Method {
290346
*/
291347
public static BufferedImage resize(BufferedImage src, int targetSize)
292348
throws IllegalArgumentException {
293-
return resize(src, Method.AUTOMATIC, targetSize, targetSize, null);
349+
return resize(src, Method.AUTOMATIC, targetSize, targetSize,
350+
(BufferedImageOp) null);
294351
}
295352

296353
/**
@@ -312,9 +369,10 @@ public static BufferedImage resize(BufferedImage src, int targetSize)
312369
* @param targetSize
313370
* The target width and height (square) that you wish the image
314371
* 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.
318376
*
319377
* @return the proportionally scaled image with either a width or height of
320378
* the given target size.
@@ -325,8 +383,8 @@ public static BufferedImage resize(BufferedImage src, int targetSize)
325383
* @see #DEFAULT_ANTIALIAS_OP
326384
*/
327385
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);
330388
}
331389

332390
/**
@@ -353,7 +411,8 @@ public static BufferedImage resize(BufferedImage src, int targetSize,
353411
*/
354412
public static BufferedImage resize(BufferedImage src, int targetWidth,
355413
int targetHeight) throws IllegalArgumentException {
356-
return resize(src, Method.AUTOMATIC, targetWidth, targetHeight, null);
414+
return resize(src, Method.AUTOMATIC, targetWidth, targetHeight,
415+
(BufferedImageOp) null);
357416
}
358417

359418
/**
@@ -381,9 +440,10 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
381440
* The target width that you wish the image to have.
382441
* @param targetHeight
383442
* 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.
387447
*
388448
* @return the proportionally scaled image with either a width or height of
389449
* the given target size.
@@ -394,9 +454,9 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
394454
* @see #DEFAULT_ANTIALIAS_OP
395455
*/
396456
public static BufferedImage resize(BufferedImage src, int targetWidth,
397-
int targetHeight, BufferedImageOp op)
457+
int targetHeight, BufferedImageOp... ops)
398458
throws IllegalArgumentException {
399-
return resize(src, Method.AUTOMATIC, targetWidth, targetHeight, op);
459+
return resize(src, Method.AUTOMATIC, targetWidth, targetHeight, ops);
400460
}
401461

402462
/**
@@ -422,7 +482,50 @@ public static BufferedImage resize(BufferedImage src, int targetWidth,
422482
*/
423483
public static BufferedImage resize(BufferedImage src, Method scalingMethod,
424484
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 &lt; 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);
426529
}
427530

428531
/**
@@ -454,7 +557,8 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
454557
*/
455558
public static BufferedImage resize(BufferedImage src, Method scalingMethod,
456559
int targetWidth, int targetHeight) throws IllegalArgumentException {
457-
return resize(src, scalingMethod, targetWidth, targetHeight, null);
560+
return resize(src, scalingMethod, targetWidth, targetHeight,
561+
(BufferedImageOp) null);
458562
}
459563

460564
/**
@@ -485,9 +589,10 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
485589
* The target width that you wish the image to have.
486590
* @param targetHeight
487591
* 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.
491596
*
492597
* @return the proportionally scaled image no bigger than the given width
493598
* and height.
@@ -500,7 +605,7 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
500605
* @see #DEFAULT_ANTIALIAS_OP
501606
*/
502607
public static BufferedImage resize(BufferedImage src, Method scalingMethod,
503-
int targetWidth, int targetHeight, BufferedImageOp op)
608+
int targetWidth, int targetHeight, BufferedImageOp... ops)
504609
throws IllegalArgumentException {
505610
if (scalingMethod == null)
506611
throw new IllegalArgumentException("scalingMethod cannot be null");
@@ -634,9 +739,24 @@ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
634739
}
635740
}
636741

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("\tImage Op Applied in %d ms, Op: %s",
757+
(System.currentTimeMillis() - opStartTime), op);
758+
}
759+
}
640760

641761
if (DEBUG) {
642762
long elapsedTime = System.currentTimeMillis() - startTime;
@@ -753,11 +873,11 @@ else if (length <= THRESHOLD_BALANCED_SPEED)
753873
protected static BufferedImage scaleImage(BufferedImage src,
754874
int targetWidth, int targetHeight, Object interpolationHintValue) {
755875
/*
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
761881
* format, is garbage.
762882
*
763883
* Originally reported by Magnus Kvalheim from Movellas when scaling

0 commit comments

Comments
 (0)