ImageIcon
constructed from disabled image of passed icon.
+ */
+ @Nullable
+ public static Icon getDisabledIcon(Icon icon) {
+ if (icon instanceof LazyIcon) icon = ((LazyIcon)icon).getOrComputeIcon();
+ if (icon == null) return null;
+
+ Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
+ if (disabledIcon == null) {
+ if (!isGoodSize(icon)) {
+ return EMPTY_ICON;
+ }
+ final int scale = UIUtil.isRetina() ? 2 : 1;
+ @SuppressWarnings("UndesirableClassUsage")
+ BufferedImage image = new BufferedImage(scale*icon.getIconWidth(), scale*icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+ final Graphics2D graphics = image.createGraphics();
+
+ graphics.setColor(UIUtil.TRANSPARENT_COLOR);
+ graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
+ graphics.scale(scale, scale);
+ icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0);
+
+ graphics.dispose();
+
+ Image img = ImageUtil.filter(image, UIUtil.getGrayFilter());
+ if (UIUtil.isRetina()) img = RetinaImage.createFrom(img);
+
+ disabledIcon = new JBImageIcon(img);
+ ourIcon2DisabledIcon.put(icon, disabledIcon);
+ }
+ return disabledIcon;
+ }
+
+ public static Icon getTransparentIcon(@NotNull final Icon icon) {
+ return getTransparentIcon(icon, 0.5f);
+ }
+
+ public static Icon getTransparentIcon(@NotNull final Icon icon, final float alpha) {
+ return new RetrievableIcon() {
+ @Nullable
+ @Override
+ public Icon retrieveIcon() {
+ return icon;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return icon.getIconHeight();
+ }
+
+ @Override
+ public int getIconWidth() {
+ return icon.getIconWidth();
+ }
+
+ @Override
+ public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
+ final Graphics2D g2 = (Graphics2D)g;
+ final Composite saveComposite = g2.getComposite();
+ g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+ icon.paintIcon(c, g2, x, y);
+ g2.setComposite(saveComposite);
+ }
+ };
+ }
+
+ /**
+ * Gets a snapshot of the icon, immune to changes made by these calls:
+ * {@link IconLoader#setScale(float)}, {@link IconLoader#setFilter(ImageFilter)}, {@link IconLoader#setUseDarkIcons(boolean)}
+ *
+ * @param icon the source icon
+ * @return the icon snapshot
+ */
+ @NotNull
+ public static Icon getIconSnapshot(@NotNull Icon icon) {
+ if (icon instanceof CachedImageIcon) {
+ return ((CachedImageIcon)icon).getRealIcon();
+ }
+ return icon;
+ }
+
+ public static final class CachedImageIcon implements ScalableIcon {
+ private volatile Object myRealIcon;
+ public String myOriginalPath;
+ private ClassLoader myClassLoader;
+ @NotNull
+ private URL myUrl;
+ private volatile boolean dark;
+ private volatile float scale;
+ private volatile int numberOfPatchers = 0;
+
+ private volatile ImageFilter filter;
+ private final MyScaledIconsCache myScaledIconsCache = new MyScaledIconsCache();
+
+ public CachedImageIcon(@NotNull URL url) {
+ myUrl = url;
+ dark = USE_DARK_ICONS;
+ scale = SCALE;
+ filter = IMAGE_FILTER;
+ }
+
+ @NotNull
+ private synchronized ImageIcon getRealIcon() {
+ if (isLoaderDisabled() && (myRealIcon == null || dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER)) return EMPTY_ICON;
+
+ if (!isValid()) {
+ myRealIcon = null;
+ dark = USE_DARK_ICONS;
+ scale = SCALE;
+ filter = IMAGE_FILTER;
+ myScaledIconsCache.clear();
+ }
+ Object realIcon = myRealIcon;
+ if (realIcon instanceof Icon) return (ImageIcon)realIcon;
+
+ ImageIcon icon;
+ if (realIcon instanceof Reference) {
+ icon = ((Referenceobj
in the src
array.
+ * Returns -1
if passed object isn't found. This method uses
+ * equals
of arrays elements to compare obj
with
+ * these elements.
+ */
+ public static #abc123
,
+ * ABC123
,
+ * ab5
,
+ * #FFF
.
+ *
+ * @param str hex string
+ * @return Color object
+ */
+ public static Color fromHex(String str) {
+ if (str.startsWith("#")) {
+ str = str.substring(1);
+ }
+ if (str.length() == 3) {
+ return new Color(
+ 17 * Integer.valueOf(String.valueOf(str.charAt(0)), 16),
+ 17 * Integer.valueOf(String.valueOf(str.charAt(1)), 16),
+ 17 * Integer.valueOf(String.valueOf(str.charAt(2)), 16));
+ } else if (str.length() == 6) {
+ return Color.decode("0x" + str);
+ } else {
+ throw new IllegalArgumentException("Should be String of 3 or 6 chars length.");
+ }
+ }
+
+ public static Color fromHex(String str, Color defaultValue) {
+ try {
+ return fromHex(str);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Checks whether color is dark or not based on perceptional luminosity
+ * http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
+ *
+ * @param c color to check
+ * @return dark or not
+ */
+ public static boolean isDark(final Color c) {
+ // based on perceptional luminosity, see
+ return (1 - (0.299 * c.getRed() + 0.587 * c.getGreen() + 0.114 * c.getBlue()) / 255) >= 0.5;
+ }
+}
diff --git a/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/ComparingUtils.java b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/ComparingUtils.java
new file mode 100644
index 000000000..274201ab0
--- /dev/null
+++ b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/ComparingUtils.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bulenkov.iconloader.util;
+
+import java.util.*;
+
+/**
+ * @author Konstantin Bulenkov
+ */
+public class ComparingUtils {
+ private ComparingUtils() {
+ }
+
+ public static Insets
object with the
+ * specified top, left, bottom, and right insets.
+ *
+ * @param top the inset from the top.
+ * @param left the inset from the left.
+ * @param bottom the inset from the bottom.
+ * @param right the inset from the right.
+ */
+ public JBInsets(int top, int left, int bottom, int right) {
+ super(scale(top), scale(left), scale(bottom), scale(right));
+ }
+
+ public int width() {
+ return left + right;
+ }
+
+ public int height() {
+ return top + bottom;
+ }
+
+ public static JBInsets create(Insets insets) {
+ if (insets instanceof JBInsets) {
+ JBInsets copy = new JBInsets(0, 0, 0, 0);
+ copy.top = insets.top;
+ copy.left = insets.left;
+ copy.bottom = insets.bottom;
+ copy.right = insets.right;
+ return copy;
+ }
+ return new JBInsets(insets.top, insets.left, insets.bottom, insets.right);
+ }
+
+ public JBInsetsUIResource asUIResource() {
+ return new JBInsetsUIResource(this);
+ }
+
+ public static class JBInsetsUIResource extends JBInsets implements UIResource {
+ public JBInsetsUIResource(JBInsets insets) {
+ super(0, 0, 0, 0);
+ top = insets.top;
+ left = insets.left;
+ bottom = insets.bottom;
+ right = insets.right;
+ }
+ }
+
+ /**
+ * @param dimension the size to increase
+ * @param insets the insets to add
+ */
+ public static void addTo(Dimension dimension, Insets insets) {
+ if (insets != null) {
+ dimension.width += insets.left + insets.right;
+ dimension.height += insets.top + insets.bottom;
+ }
+ }
+
+ /**
+ * @param dimension the size to decrease
+ * @param insets the insets to remove
+ */
+ public static void removeFrom(Dimension dimension, Insets insets) {
+ if (insets != null) {
+ dimension.width -= insets.left + insets.right;
+ dimension.height -= insets.top + insets.bottom;
+ }
+ }
+
+ /**
+ * @param rectangle the size to increase and the location to move
+ * @param insets the insets to add
+ */
+ public static void addTo(Rectangle rectangle, Insets insets) {
+ if (insets != null) {
+ rectangle.x -= insets.left;
+ rectangle.y -= insets.top;
+ rectangle.width += insets.left + insets.right;
+ rectangle.height += insets.top + insets.bottom;
+ }
+ }
+
+ /**
+ * @param rectangle the size to decrease and the location to move
+ * @param insets the insets to remove
+ */
+ public static void removeFrom(Rectangle rectangle, Insets insets) {
+ if (insets != null) {
+ rectangle.x += insets.left;
+ rectangle.y += insets.top;
+ rectangle.width -= insets.left + insets.right;
+ rectangle.height -= insets.top + insets.bottom;
+ }
+ }
+}
diff --git a/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/JBUI.java b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/JBUI.java
new file mode 100644
index 000000000..6050e3aae
--- /dev/null
+++ b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/JBUI.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bulenkov.iconloader.util;
+
+import com.bulenkov.iconloader.IconLoader;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+import java.awt.*;
+
+/**
+ * @author Konstantin Bulenkov
+ */
+public class JBUI {
+ private static float scaleFactor = 1.0f;
+
+ static {
+ calculateScaleFactor();
+ }
+
+ private static void calculateScaleFactor() {
+ if (SystemInfo.isMac) {
+ scaleFactor = 1.0f;
+ return;
+ }
+
+ if (System.getProperty("hidpi") != null && !"true".equalsIgnoreCase(System.getProperty("hidpi"))) {
+ scaleFactor = 1.0f;
+ return;
+ }
+
+ UIUtil.initSystemFontData();
+ Pair+ * The results generated by imgscalr using this method, as compared to a single + * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} scale operation look much + * better, especially when using the {@link Method#ULTRA_QUALITY} method. + *
+ * Only when scaling using the {@link Method#AUTOMATIC} method will this class + * look at the size of the image before selecting an approach to scaling the + * image. If {@link Method#QUALITY} is specified, the best-looking algorithm + * possible is always used. + * + * Minor modifications are made to Campbell's original implementation in the + * form of: + *targetWidth
as the primary dimension and re-calculate the
+ * targetHeight
regardless of what is passed in.targetHeight
as the
+ * primary dimension and re-calculate the targetWidth
regardless of
+ * what is passed in.resize
method,
+ * the image's orientation is ignored and the scaled image is fit to the
+ * preferred dimension by using the value passed in by the user for that
+ * dimension and recalculating the other (regardless of image orientation). This
+ * is useful, for example, when working with PORTRAIT oriented images that you
+ * need to all be the same width or visa-versa (e.g. showing user profile
+ * pictures in a directory listing).BufferedImage.TYPE_*
variables, unfortunately not all image
+ * types are supported equally in the Java2D rendering pipeline.
+ *
+ * Some more obscure image types either have poor or no support, leading to
+ * severely degraded quality and processing performance when an attempt is made
+ * by imgscalr to create a scaled instance of the same type as the
+ * source image. In many cases, especially when applying {@link BufferedImageOp}
+ * s, using poorly supported image types can even lead to exceptions or total
+ * corruption of the image (e.g. solid black image).
+ *
+ * imgscalr specifically accounts for and automatically hands
+ * ALL of these pain points for you internally by shuffling all
+ * images into one of two types:
+ * + * Workaround: A workaround to this issue with all version of + * Java is to simply save a GIF as a PNG; no change to your code needs to be + * made except when the image is saved out, e.g. using {@link ImageIO}. + *
+ * When a file type of "PNG" is used, both the transparency and high color + * quality will be maintained as the PNG code path in Java2D is superior to the + * GIF implementation. + *
+ * If the issue with optional {@link BufferedImageOp}s destroying GIF image + * content is ever fixed in the platform, saving out resulting images as GIFs + * should suddenly start working. + *
+ * More can be read about the issue here and here. + *
static
); this class maintains no internal state while
+ * performing any of the provided operations and is safe to call simultaneously
+ * from multiple threads.
+ * System.out
via the printf
method. This
+ * allows the logging to be light weight and easy to capture (every imgscalr log
+ * message is prefixed with the {@link #LOG_PREFIX} string) while adding no
+ * dependencies to the library.
+ *
+ * Implementation of logging in this class is as efficient as possible; avoiding
+ * any calls to the logger method or passing of arguments if logging is not
+ * enabled to avoid the (hidden) cost of constructing the Object[] argument for
+ * the varargs-based method call.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 1.1
+ */
+public class Scalr {
+ /**
+ * System property name used to define the debug boolean flag.
+ *
+ * Value is "imgscalr.debug
".
+ */
+ public static final String DEBUG_PROPERTY_NAME = "imgscalr.debug";
+
+ /**
+ * System property name used to define a custom log prefix.
+ *
+ * Value is "imgscalr.logPrefix
".
+ */
+ public static final String LOG_PREFIX_PROPERTY_NAME = "imgscalr.logPrefix";
+
+ /**
+ * Flag used to indicate if debugging output has been enabled by setting the
+ * "imgscalr.debug
" system property to true
. This
+ * value will be false
if the "imgscalr.debug
"
+ * system property is undefined or set to false
.
+ *
+ * This property can be set on startup with:
+ * -Dimgscalr.debug=true
+ *
or by calling {@link System#setProperty(String, String)} to set a
+ * new property value for {@link #DEBUG_PROPERTY_NAME} before this class is
+ * loaded.
+ *
+ * Default value is false
.
+ */
+ public static final boolean DEBUG = Boolean.getBoolean(DEBUG_PROPERTY_NAME);
+
+ /**
+ * Prefix to every log message this library logs. Using a well-defined
+ * prefix helps make it easier both visually and programmatically to scan
+ * log files for messages produced by this library.
+ *
+ * This property can be set on startup with:
+ * -Dimgscalr.logPrefix=<YOUR PREFIX HERE>
+ *
or by calling {@link System#setProperty(String, String)} to set a
+ * new property value for {@link #LOG_PREFIX_PROPERTY_NAME} before this
+ * class is loaded.
+ *
+ * Default value is "[imgscalr]
" (including the space).
+ */
+ public static final String LOG_PREFIX = System.getProperty(
+ LOG_PREFIX_PROPERTY_NAME, "[imgscalr] ");
+
+ /**
+ * A {@link ConvolveOp} using a very light "blur" kernel that acts like an
+ * anti-aliasing filter (softens the image a bit) when applied to an image.
+ *
+ * A common request by users of the library was that they wished to "soften"
+ * resulting images when scaling them down drastically. After quite a bit of
+ * A/B testing, the kernel used by this Op was selected as the closest match
+ * for the target which was the softer results from the deprecated
+ * {@link AreaAveragingScaleFilter} (which is used internally by the
+ * deprecated {@link Image#getScaledInstance(int, int, int)} method in the
+ * JDK that imgscalr is meant to replace).
+ *
+ * This ConvolveOp uses a 3x3 kernel with the values:
+ * .0f | + *.08f | + *.0f | + *
.08f | + *.68f | + *.08f | + *
.0f | + *.08f | + *.0f | + *
QUALITY
,
+ * BALANCED
or SPEED
scaling algorithms.
+ *
+ * By default the thresholds chosen will give nearly the best looking
+ * result in the fastest amount of time. We intend this method to work
+ * for 80% of people looking to scale an image quickly and get a good
+ * looking result.
+ */
+ AUTOMATIC,
+ /**
+ * Used to indicate that the scaling implementation should scale as fast
+ * as possible and return a result. For smaller images (800px in size)
+ * this can result in noticeable aliasing but it can be a few magnitudes
+ * times faster than using the QUALITY method.
+ */
+ SPEED,
+ /**
+ * Used to indicate that the scaling implementation should use a scaling
+ * operation balanced between SPEED and QUALITY. Sometimes SPEED looks
+ * too low quality to be useful (e.g. text can become unreadable when
+ * scaled using SPEED) but using QUALITY mode will increase the
+ * processing time too much. This mode provides a "better than SPEED"
+ * quality in a "less than QUALITY" amount of time.
+ */
+ BALANCED,
+ /**
+ * Used to indicate that the scaling implementation should do everything
+ * it can to create as nice of a result as possible. This approach is
+ * most important for smaller pictures (800px or smaller) and less
+ * important for larger pictures as the difference between this method
+ * and the SPEED method become less and less noticeable as the
+ * source-image size increases. Using the AUTOMATIC method will
+ * automatically prefer the QUALITY method when scaling an image down
+ * below 800px in size.
+ */
+ QUALITY,
+ /**
+ * Used to indicate that the scaling implementation should go above and
+ * beyond the work done by {@link Method#QUALITY} to make the image look
+ * exceptionally good at the cost of more processing time. This is
+ * especially evident when generating thumbnails of images that look
+ * jagged with some of the other {@link Method}s (even
+ * {@link Method#QUALITY}).
+ */
+ ULTRA_QUALITY;
+ }
+
+ /**
+ * Used to define the different modes of resizing that the algorithm can
+ * use.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 3.1
+ */
+ public static enum Mode {
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image by looking at the image's
+ * orientation and generating proportional dimensions that best fit into
+ * the target width and height given
+ *
+ * See "Image Proportions" in the {@link Scalr} class description for
+ * more detail.
+ */
+ AUTOMATIC,
+ /**
+ * Used to fit the image to the exact dimensions given regardless of the
+ * image's proportions. If the dimensions are not proportionally
+ * correct, this will introduce vertical or horizontal stretching to the
+ * image.
+ *
+ * It is recommended that you use one of the other FIT_TO
+ * modes or {@link Mode#AUTOMATIC} if you want the image to look
+ * correct, but if dimension-fitting is the #1 priority regardless of
+ * how it makes the image look, that is what this mode is for.
+ */
+ FIT_EXACT,
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image that best-fit within the given
+ * width, regardless of the orientation of the image.
+ */
+ FIT_TO_WIDTH,
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image that best-fit within the given
+ * height, regardless of the orientation of the image.
+ */
+ FIT_TO_HEIGHT;
+ }
+
+ /**
+ * Used to define the different types of rotations that can be applied to an
+ * image during a resize operation.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 3.2
+ */
+ public static enum Rotation {
+ /**
+ * 90-degree, clockwise rotation (to the right). This is equivalent to a
+ * quarter-turn of the image to the right; moving the picture on to its
+ * right side.
+ */
+ CW_90,
+ /**
+ * 180-degree, clockwise rotation (to the right). This is equivalent to
+ * 1 half-turn of the image to the right; rotating the picture around
+ * until it is upside down from the original position.
+ */
+ CW_180,
+ /**
+ * 270-degree, clockwise rotation (to the right). This is equivalent to
+ * a quarter-turn of the image to the left; moving the picture on to its
+ * left side.
+ */
+ CW_270,
+ /**
+ * Flip the image horizontally by reflecting it around the y axis.
+ *
+ * This is not a standard rotation around a center point, but instead
+ * creates the mirrored reflection of the image horizontally.
+ *
+ * More specifically, the vertical orientation of the image stays the
+ * same (the top stays on top, and the bottom on bottom), but the right
+ * and left sides flip. This is different than a standard rotation where
+ * the top and bottom would also have been flipped.
+ */
+ FLIP_HORZ,
+ /**
+ * Flip the image vertically by reflecting it around the x axis.
+ *
+ * This is not a standard rotation around a center point, but instead
+ * creates the mirrored reflection of the image vertically.
+ *
+ * More specifically, the horizontal orientation of the image stays the
+ * same (the left stays on the left and the right stays on the right),
+ * but the top and bottom sides flip. This is different than a standard
+ * rotation where the left and right would also have been flipped.
+ */
+ FLIP_VERT;
+ }
+
+ /**
+ * Threshold (in pixels) at which point the scaling operation using the
+ * {@link Method#AUTOMATIC} method will decide if a {@link Method#BALANCED}
+ * method will be used (if smaller than or equal to threshold) or a
+ * {@link Method#SPEED} method will be used (if larger than threshold).
+ *
+ * The bigger the image is being scaled to, the less noticeable degradations
+ * in the image becomes and the faster algorithms can be selected.
+ *
+ * The value of this threshold (1600) was chosen after visual, by-hand, A/B
+ * testing between different types of images scaled with this library; both
+ * photographs and screenshots. It was determined that images below this
+ * size need to use a {@link Method#BALANCED} scale method to look decent in
+ * most all cases while using the faster {@link Method#SPEED} method for
+ * images bigger than this threshold showed no noticeable degradation over a
+ * BALANCED
scale.
+ */
+ public static final int THRESHOLD_BALANCED_SPEED = 1600;
+
+ /**
+ * Threshold (in pixels) at which point the scaling operation using the
+ * {@link Method#AUTOMATIC} method will decide if a {@link Method#QUALITY}
+ * method will be used (if smaller than or equal to threshold) or a
+ * {@link Method#BALANCED} method will be used (if larger than threshold).
+ *
+ * The bigger the image is being scaled to, the less noticeable degradations
+ * in the image becomes and the faster algorithms can be selected.
+ *
+ * The value of this threshold (800) was chosen after visual, by-hand, A/B
+ * testing between different types of images scaled with this library; both
+ * photographs and screenshots. It was determined that images below this
+ * size need to use a {@link Method#QUALITY} scale method to look decent in
+ * most all cases while using the faster {@link Method#BALANCED} method for
+ * images bigger than this threshold showed no noticeable degradation over a
+ * QUALITY
scale.
+ */
+ public static final int THRESHOLD_QUALITY_BALANCED = 800;
+
+ /**
+ * Used to apply, in the order given, 1 or more {@link BufferedImageOp}s to
+ * a given {@link BufferedImage} and return the result.
+ *
+ * Feature: This implementation works around a
+ * decade-old JDK bug that can cause a {@link RasterFormatException}
+ * when applying a perfectly valid {@link BufferedImageOp}s to images.
+ *
+ * Feature: This implementation also works around
+ * {@link BufferedImageOp}s failing to apply and throwing
+ * {@link ImagingOpException}s when run against a src
image
+ * type that is poorly supported. Unfortunately using {@link ImageIO} and
+ * standard Java methods to load images provides no consistency in getting
+ * images in well-supported formats. This method automatically accounts and
+ * corrects for all those problems (if necessary).
+ *
+ * It is recommended you always use this method to apply any
+ * {@link BufferedImageOp}s instead of relying on directly using the
+ * {@link BufferedImageOp#filter(BufferedImage, BufferedImage)} method.
+ *
+ * Performance: Not all {@link BufferedImageOp}s are
+ * hardware accelerated operations, but many of the most popular (like
+ * {@link ConvolveOp}) are. For more information on if your image op is
+ * hardware accelerated or not, check the source code of the underlying JDK
+ * class that actually executes the Op code, sun.awt.image.ImagingLib.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will have the ops applied to it.
+ * @param ops
+ * 1
or more ops to apply to the image.
+ *
+ * @return a new {@link BufferedImage} that represents the src
+ * with all the given operations applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if ops
is null
or empty.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage apply(BufferedImage src, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (ops == null || ops.length == 0)
+ throw new IllegalArgumentException("ops cannot be null or empty");
+
+ int type = src.getType();
+
+ /*
+ * Ensure the src image is in the best supported image type before we
+ * continue, otherwise it is possible our calls below to getBounds2D and
+ * certainly filter(...) may fail if not.
+ *
+ * Java2D makes an attempt at applying most BufferedImageOps using
+ * hardware acceleration via the ImagingLib internal library.
+ *
+ * Unfortunately may of the BufferedImageOp are written to simply fail
+ * with an ImagingOpException if the operation cannot be applied with no
+ * additional information about what went wrong or attempts at
+ * re-applying it in different ways.
+ *
+ * This is assuming the failing BufferedImageOp even returns a null
+ * image after failing to apply; some simply return a corrupted/black
+ * image that result in no exception and it is up to the user to
+ * discover this.
+ *
+ * In internal testing, EVERY failure I've ever seen was the result of
+ * the source image being in a poorly-supported BufferedImage Type like
+ * BGR or ABGR (even though it was loaded with ImageIO).
+ *
+ * To avoid this nasty/stupid surprise with BufferedImageOps, we always
+ * ensure that the src image starts in an optimally supported format
+ * before we try and apply the filter.
+ */
+ if (!(type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB))
+ src = copyToOptimalImage(src);
+
+ if (DEBUG)
+ log(0, "Applying %d BufferedImageOps...", ops.length);
+
+ boolean hasReassignedSrc = false;
+
+ for (int i = 0; i < ops.length; i++) {
+ long subT = System.currentTimeMillis();
+ BufferedImageOp op = ops[i];
+
+ // Skip null ops instead of throwing an exception.
+ if (op == null)
+ continue;
+
+ if (DEBUG)
+ log(1, "Applying BufferedImageOp [class=%s, toString=%s]...",
+ op.getClass(), op.toString());
+
+ /*
+ * Must use op.getBounds instead of src.getWidth and src.getHeight
+ * because we are trying to create an image big enough to hold the
+ * result of this operation (which may be to scale the image
+ * smaller), in that case the bounds reported by this op and the
+ * bounds reported by the source image will be different.
+ */
+ Rectangle2D resultBounds = op.getBounds2D(src);
+
+ // Watch out for flaky/misbehaving ops that fail to work right.
+ if (resultBounds == null)
+ throw new ImagingOpException(
+ "BufferedImageOp ["
+ + op.toString()
+ + "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op.");
+
+ /*
+ * We must manually create the target image; we cannot rely on the
+ * null-destination filter() method to create a valid destination
+ * for us thanks to this JDK bug that has been filed for almost a
+ * decade:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606
+ */
+ BufferedImage dest = createOptimalImage(src,
+ (int) Math.round(resultBounds.getWidth()),
+ (int) Math.round(resultBounds.getHeight()));
+
+ // Perform the operation, update our result to return.
+ BufferedImage result = op.filter(src, dest);
+
+ /*
+ * Flush the 'src' image ONLY IF it is one of our interim temporary
+ * images being used when applying 2 or more operations back to
+ * back. We never want to flush the original image passed in.
+ */
+ if (hasReassignedSrc)
+ src.flush();
+
+ /*
+ * Incase there are more operations to perform, update what we
+ * consider the 'src' reference to our last result so on the next
+ * iteration the next op is applied to this result and not back
+ * against the original src passed in.
+ */
+ src = result;
+
+ /*
+ * Keep track of when we re-assign 'src' to an interim temporary
+ * image, so we know when we can explicitly flush it and clean up
+ * references on future iterations.
+ */
+ hasReassignedSrc = true;
+
+ if (DEBUG)
+ log(1,
+ "Applied BufferedImageOp in %d ms, result [width=%d, height=%d]",
+ System.currentTimeMillis() - subT, result.getWidth(),
+ result.getHeight());
+ }
+
+ if (DEBUG)
+ log(0, "All %d BufferedImageOps applied in %d ms", ops.length,
+ System.currentTimeMillis() - t);
+
+ return src;
+ }
+
+ /**
+ * Used to crop the given src
image from the top-left corner
+ * and applying any optional {@link BufferedImageOp}s to the result before
+ * returning it.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image to crop.
+ * @param width
+ * The width of the bounding cropping box.
+ * @param height
+ * The height of the bounding cropping box.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing the cropped region of
+ * the src
image with any optional operations applied
+ * to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if any coordinates of the bounding crop box is invalid within
+ * the bounds of the src
image (e.g. negative or
+ * too big).
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage crop(BufferedImage src, int width, int height,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return crop(src, 0, 0, width, height, ops);
+ }
+
+ /**
+ * Used to crop the given src
image and apply any optional
+ * {@link BufferedImageOp}s to it before returning the result.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image to crop.
+ * @param x
+ * The x-coordinate of the top-left corner of the bounding box
+ * used for cropping.
+ * @param y
+ * The y-coordinate of the top-left corner of the bounding box
+ * used for cropping.
+ * @param width
+ * The width of the bounding cropping box.
+ * @param height
+ * The height of the bounding cropping box.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing the cropped region of
+ * the src
image with any optional operations applied
+ * to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if any coordinates of the bounding crop box is invalid within
+ * the bounds of the src
image (e.g. negative or
+ * too big).
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage crop(BufferedImage src, int x, int y,
+ int width, int height, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (x < 0 || y < 0 || width < 0 || height < 0)
+ throw new IllegalArgumentException("Invalid crop bounds: x [" + x
+ + "], y [" + y + "], width [" + width + "] and height ["
+ + height + "] must all be >= 0");
+
+ int srcWidth = src.getWidth();
+ int srcHeight = src.getHeight();
+
+ if ((x + width) > srcWidth)
+ throw new IllegalArgumentException(
+ "Invalid crop bounds: x + width [" + (x + width)
+ + "] must be <= src.getWidth() [" + srcWidth + "]");
+ if ((y + height) > srcHeight)
+ throw new IllegalArgumentException(
+ "Invalid crop bounds: y + height [" + (y + height)
+ + "] must be <= src.getHeight() [" + srcHeight
+ + "]");
+
+ if (DEBUG)
+ log(0,
+ "Cropping Image [width=%d, height=%d] to [x=%d, y=%d, width=%d, height=%d]...",
+ srcWidth, srcHeight, x, y, width, height);
+
+ // Create a target image of an optimal type to render into.
+ BufferedImage result = createOptimalImage(src, width, height);
+ Graphics g = result.getGraphics();
+
+ /*
+ * Render the region specified by our crop bounds from the src image
+ * directly into our result image (which is the exact size of the crop
+ * region).
+ */
+ g.drawImage(src, 0, 0, width, height, x, y, (x + width), (y + height),
+ null);
+ g.dispose();
+
+ if (DEBUG)
+ log(0, "Cropped Image in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to apply padding around the edges of an image using
+ * {@link Color#BLACK} to fill the extra padded space and then return the
+ * result.
+ *
+ * The amount of padding
specified is applied to all sides;
+ * more specifically, a padding
of 2
would add 2
+ * extra pixels of space (filled by the given color
) on the
+ * top, bottom, left and right sides of the resulting image causing the
+ * result to be 4 pixels wider and 4 pixels taller than the src
+ * image.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image the padding will be added to.
+ * @param padding
+ * The number of pixels of padding to add to each side in the
+ * resulting image. If this value is 0
then
+ * src
is returned unmodified.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing src
with
+ * the given padding applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if padding
is < 1
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage pad(BufferedImage src, int padding,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return pad(src, padding, Color.BLACK);
+ }
+
+ /**
+ * Used to apply padding around the edges of an image using the given color
+ * to fill the extra padded space and then return the result. {@link Color}s
+ * using an alpha channel (i.e. transparency) are supported.
+ *
+ * The amount of padding
specified is applied to all sides;
+ * more specifically, a padding
of 2
would add 2
+ * extra pixels of space (filled by the given color
) on the
+ * top, bottom, left and right sides of the resulting image causing the
+ * result to be 4 pixels wider and 4 pixels taller than the src
+ * image.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image the padding will be added to.
+ * @param padding
+ * The number of pixels of padding to add to each side in the
+ * resulting image. If this value is 0
then
+ * src
is returned unmodified.
+ * @param color
+ * The color to fill the padded space with. {@link Color}s using
+ * an alpha channel (i.e. transparency) are supported.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing src
with
+ * the given padding applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if padding
is < 1
.
+ * @throws IllegalArgumentException
+ * if color
is null
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage pad(BufferedImage src, int padding,
+ Color color, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (padding < 1)
+ throw new IllegalArgumentException("padding [" + padding
+ + "] must be > 0");
+ if (color == null)
+ throw new IllegalArgumentException("color cannot be null");
+
+ int srcWidth = src.getWidth();
+ int srcHeight = src.getHeight();
+
+ /*
+ * Double the padding to account for all sides of the image. More
+ * specifically, if padding is "1" we add 2 pixels to width and 2 to
+ * height, so we have 1 new pixel of padding all the way around our
+ * image.
+ */
+ int sizeDiff = (padding * 2);
+ int newWidth = srcWidth + sizeDiff;
+ int newHeight = srcHeight + sizeDiff;
+
+ if (DEBUG)
+ log(0,
+ "Padding Image from [originalWidth=%d, originalHeight=%d, padding=%d] to [newWidth=%d, newHeight=%d]...",
+ srcWidth, srcHeight, padding, newWidth, newHeight);
+
+ boolean colorHasAlpha = (color.getAlpha() != 255);
+ boolean imageHasAlpha = (src.getTransparency() != BufferedImage.OPAQUE);
+
+ BufferedImage result;
+
+ /*
+ * We need to make sure our resulting image that we render into contains
+ * alpha if either our original image OR the padding color we are using
+ * contain it.
+ */
+ if (colorHasAlpha || imageHasAlpha) {
+ if (DEBUG)
+ log(1,
+ "Transparency FOUND in source image or color, using ARGB image type...");
+
+ result = new BufferedImage(newWidth, newHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ } else {
+ if (DEBUG)
+ log(1,
+ "Transparency NOT FOUND in source image or color, using RGB image type...");
+
+ result = new BufferedImage(newWidth, newHeight,
+ BufferedImage.TYPE_INT_RGB);
+ }
+
+ Graphics g = result.getGraphics();
+
+ // "Clear" the background of the new image with our padding color first.
+ g.setColor(color);
+ g.fillRect(0, 0, newWidth, newHeight);
+
+ // Draw the image into the center of the new padded image.
+ g.drawImage(src, padding, padding, null);
+ g.dispose();
+
+ if (DEBUG)
+ log(0, "Padding Applied in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} and mode of
+ * {@link Mode#AUTOMATIC} are used.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage resize(BufferedImage src, int targetSize,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
using the given scaling
+ * method and apply the given {@link BufferedImageOp}s (if any) to the
+ * result before returning it.
+ *
+ * A mode of {@link Mode#AUTOMATIC} is used.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, scalingMethod, Mode.AUTOMATIC, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
(or fitting the image to
+ * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode}
+ * specified) and apply the given {@link BufferedImageOp}s (if any) to the
+ * result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} is used.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Mode resizeMode,
+ int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, resizeMode, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
(or fitting the image to
+ * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode}
+ * specified) using the given scaling method and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ Mode resizeMode, int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, scalingMethod, resizeMode, targetSize, targetSize,
+ ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height and apply the given {@link BufferedImageOp}s (if any) to
+ * the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} and mode of
+ * {@link Mode#AUTOMATIC} are used.
+ *
+ * TIP: See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage resize(BufferedImage src, int targetWidth,
+ int targetHeight, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height using the given scaling method and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A mode of {@link Mode#AUTOMATIC} is used.
+ *
+ * TIP: See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ int targetWidth, int targetHeight, BufferedImageOp... ops) {
+ return resize(src, scalingMethod, Mode.AUTOMATIC, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height (or fitting the image to the given WIDTH or HEIGHT
+ * explicitly, depending on the {@link Mode} specified) and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} is used.
+ *
+ * TIP: See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Mode resizeMode,
+ int targetWidth, int targetHeight, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, resizeMode, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height (or fitting the image to the given WIDTH or HEIGHT
+ * explicitly, depending on the {@link Mode} specified) using the given
+ * scaling method and apply the given {@link BufferedImageOp}s (if any) to
+ * the result before returning it.
+ *
+ * TIP: See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ Mode resizeMode, int targetWidth, int targetHeight,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ long t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (targetWidth < 0)
+ throw new IllegalArgumentException("targetWidth must be >= 0");
+ if (targetHeight < 0)
+ throw new IllegalArgumentException("targetHeight must be >= 0");
+ if (scalingMethod == null)
+ throw new IllegalArgumentException(
+ "scalingMethod cannot be null. A good default value is Method.AUTOMATIC.");
+ if (resizeMode == null)
+ throw new IllegalArgumentException(
+ "resizeMode cannot be null. A good default value is Mode.AUTOMATIC.");
+
+ BufferedImage result = null;
+
+ int currentWidth = src.getWidth();
+ int currentHeight = src.getHeight();
+
+ // <= 1 is a square or landscape-oriented image, > 1 is a portrait.
+ float ratio = ((float) currentHeight / (float) currentWidth);
+
+ if (DEBUG)
+ log(0,
+ "Resizing Image [size=%dx%d, resizeMode=%s, orientation=%s, ratio(H/W)=%f] to [targetSize=%dx%d]",
+ currentWidth, currentHeight, resizeMode,
+ (ratio <= 1 ? "Landscape/Square" : "Portrait"), ratio,
+ targetWidth, targetHeight);
+
+ /*
+ * First determine if ANY size calculation needs to be done, in the case
+ * of FIT_EXACT, ignore image proportions and orientation and just use
+ * what the user sent in, otherwise the proportion of the picture must
+ * be honored.
+ *
+ * The way that is done is to figure out if the image is in a
+ * LANDSCAPE/SQUARE or PORTRAIT orientation and depending on its
+ * orientation, use the primary dimension (width for LANDSCAPE/SQUARE
+ * and height for PORTRAIT) to recalculate the alternative (height and
+ * width respectively) value that adheres to the existing ratio.
+ *
+ * This helps make life easier for the caller as they don't need to
+ * pre-compute proportional dimensions before calling the API, they can
+ * just specify the dimensions they would like the image to roughly fit
+ * within and it will do the right thing without mangling the result.
+ */
+ if (resizeMode != Mode.FIT_EXACT) {
+ if ((ratio <= 1 && resizeMode == Mode.AUTOMATIC)
+ || (resizeMode == Mode.FIT_TO_WIDTH)) {
+ // First make sure we need to do any work in the first place
+ if (targetWidth == src.getWidth())
+ return src;
+
+ // Save for detailed logging (this is cheap).
+ int originalTargetHeight = targetHeight;
+
+ /*
+ * Landscape or Square Orientation: Ignore the given height and
+ * re-calculate a proportionally correct value based on the
+ * targetWidth.
+ */
+ targetHeight = Math.round((float) targetWidth * ratio);
+
+ if (DEBUG && originalTargetHeight != targetHeight)
+ log(1,
+ "Auto-Corrected targetHeight [from=%d to=%d] to honor image proportions.",
+ originalTargetHeight, targetHeight);
+ } else {
+ // First make sure we need to do any work in the first place
+ if (targetHeight == src.getHeight())
+ return src;
+
+ // Save for detailed logging (this is cheap).
+ int originalTargetWidth = targetWidth;
+
+ /*
+ * Portrait Orientation: Ignore the given width and re-calculate
+ * a proportionally correct value based on the targetHeight.
+ */
+ targetWidth = Math.round((float) targetHeight / ratio);
+
+ if (DEBUG && originalTargetWidth != targetWidth)
+ log(1,
+ "Auto-Corrected targetWidth [from=%d to=%d] to honor image proportions.",
+ originalTargetWidth, targetWidth);
+ }
+ } else {
+ if (DEBUG)
+ log(1,
+ "Resize Mode FIT_EXACT used, no width/height checking or re-calculation will be done.");
+ }
+
+ // If AUTOMATIC was specified, determine the real scaling method.
+ if (scalingMethod == Scalr.Method.AUTOMATIC)
+ scalingMethod = determineScalingMethod(targetWidth, targetHeight,
+ ratio);
+
+ if (DEBUG)
+ log(1, "Using Scaling Method: %s", scalingMethod);
+
+ // Now we scale the image
+ if (scalingMethod == Scalr.Method.SPEED) {
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ } else if (scalingMethod == Scalr.Method.BALANCED) {
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ } else if (scalingMethod == Scalr.Method.QUALITY
+ || scalingMethod == Scalr.Method.ULTRA_QUALITY) {
+ /*
+ * If we are scaling up (in either width or height - since we know
+ * the image will stay proportional we just check if either are
+ * being scaled up), directly using a single BICUBIC will give us
+ * better results then using Chris Campbell's incremental scaling
+ * operation (and take a lot less time).
+ *
+ * If we are scaling down, we must use the incremental scaling
+ * algorithm for the best result.
+ */
+ if (targetWidth > currentWidth || targetHeight > currentHeight) {
+ if (DEBUG)
+ log(1,
+ "QUALITY scale-up, a single BICUBIC scale operation will be used...");
+
+ /*
+ * BILINEAR and BICUBIC look similar the smaller the scale jump
+ * upwards is, if the scale is larger BICUBIC looks sharper and
+ * less fuzzy. But most importantly we have to use BICUBIC to
+ * match the contract of the QUALITY rendering scalingMethod.
+ * This note is just here for anyone reading the code and
+ * wondering how they can speed their own calls up.
+ */
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ } else {
+ if (DEBUG)
+ log(1,
+ "QUALITY scale-down, incremental scaling will be used...");
+
+ /*
+ * Originally we wanted to use BILINEAR interpolation here
+ * because it takes 1/3rd the time that the BICUBIC
+ * interpolation does, however, when scaling large images down
+ * to most sizes bigger than a thumbnail we witnessed noticeable
+ * "softening" in the resultant image with BILINEAR that would
+ * be unexpectedly annoying to a user expecting a "QUALITY"
+ * scale of their original image. Instead BICUBIC was chosen to
+ * honor the contract of a QUALITY scale of the original image.
+ */
+ result = scaleImageIncrementally(src, targetWidth,
+ targetHeight, scalingMethod,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ }
+ }
+
+ if (DEBUG)
+ log(0, "Resized Image in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to apply a {@link Rotation} and then 0
or more
+ * {@link BufferedImageOp}s to a given image and return the result.
+ *
+ * TIP: This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will have the rotation applied to it.
+ * @param rotation
+ * The rotation that will be applied to the image.
+ * @param ops
+ * Zero or more optional image operations (e.g. sharpen, blur,
+ * etc.) that can be applied to the final result before returning
+ * the image.
+ *
+ * @return a new {@link BufferedImage} representing src
rotated
+ * by the given amount and any optional ops applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if rotation
is null
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Rotation
+ */
+ public static BufferedImage rotate(BufferedImage src, Rotation rotation,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ long t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (rotation == null)
+ throw new IllegalArgumentException("rotation cannot be null");
+
+ if (DEBUG)
+ log(0, "Rotating Image [%s]...", rotation);
+
+ /*
+ * Setup the default width/height values from our image.
+ *
+ * In the case of a 90 or 270 (-90) degree rotation, these two values
+ * flip-flop and we will correct those cases down below in the switch
+ * statement.
+ */
+ int newWidth = src.getWidth();
+ int newHeight = src.getHeight();
+
+ /*
+ * We create a transform per operation request as (oddly enough) it ends
+ * up being faster for the VM to create, use and destroy these instances
+ * than it is to re-use a single AffineTransform per-thread via the
+ * AffineTransform.setTo(...) methods which was my first choice (less
+ * object creation); after benchmarking this explicit case and looking
+ * at just how much code gets run inside of setTo() I opted for a new AT
+ * for every rotation.
+ *
+ * Besides the performance win, trying to safely reuse AffineTransforms
+ * via setTo(...) would have required ThreadLocal instances to avoid
+ * race conditions where two or more resize threads are manipulating the
+ * same transform before applying it.
+ *
+ * Misusing ThreadLocals are one of the #1 reasons for memory leaks in
+ * server applications and since we have no nice way to hook into the
+ * init/destroy Servlet cycle or any other initialization cycle for this
+ * library to automatically call ThreadLocal.remove() to avoid the
+ * memory leak, it would have made using this library *safely* on the
+ * server side much harder.
+ *
+ * So we opt for creating individual transforms per rotation op and let
+ * the VM clean them up in a GC. I only clarify all this reasoning here
+ * for anyone else reading this code and being tempted to reuse the AT
+ * instances of performance gains; there aren't any AND you get a lot of
+ * pain along with it.
+ */
+ AffineTransform tx = new AffineTransform();
+
+ switch (rotation) {
+ case CW_90:
+ /*
+ * A 90 or -90 degree rotation will cause the height and width to
+ * flip-flop from the original image to the rotated one.
+ */
+ newWidth = src.getHeight();
+ newHeight = src.getWidth();
+
+ // Reminder: newWidth == result.getHeight() at this point
+ tx.translate(newWidth, 0);
+ tx.rotate(Math.toRadians(90));
+
+ break;
+
+ case CW_270:
+ /*
+ * A 90 or -90 degree rotation will cause the height and width to
+ * flip-flop from the original image to the rotated one.
+ */
+ newWidth = src.getHeight();
+ newHeight = src.getWidth();
+
+ // Reminder: newHeight == result.getWidth() at this point
+ tx.translate(0, newHeight);
+ tx.rotate(Math.toRadians(-90));
+ break;
+
+ case CW_180:
+ tx.translate(newWidth, newHeight);
+ tx.rotate(Math.toRadians(180));
+ break;
+
+ case FLIP_HORZ:
+ tx.translate(newWidth, 0);
+ tx.scale(-1.0, 1.0);
+ break;
+
+ case FLIP_VERT:
+ tx.translate(0, newHeight);
+ tx.scale(1.0, -1.0);
+ break;
+ }
+
+ // Create our target image we will render the rotated result to.
+ BufferedImage result = createOptimalImage(src, newWidth, newHeight);
+ Graphics2D g2d = (Graphics2D) result.createGraphics();
+
+ /*
+ * Render the resultant image to our new rotatedImage buffer, applying
+ * the AffineTransform that we calculated above during rendering so the
+ * pixels from the old position are transposed to the new positions in
+ * the resulting image correctly.
+ */
+ g2d.drawImage(src, tx, null);
+ g2d.dispose();
+
+ if (DEBUG)
+ log(0, "Rotation Applied in %d ms, result [width=%d, height=%d]",
+ System.currentTimeMillis() - t, result.getWidth(),
+ result.getHeight());
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to write out a useful and well-formatted log message by any piece of
+ * code inside of the imgscalr library.
+ *
+ * If a message cannot be logged (logging is disabled) then this method
+ * returns immediately.
+ *
+ * NOTE: Because Java will auto-box primitive arguments
+ * into Objects when building out the params
array, care should
+ * be taken not to call this method with primitive values unless
+ * {@link Scalr#DEBUG} is true
; otherwise the VM will be
+ * spending time performing unnecessary auto-boxing calculations.
+ *
+ * @param depth
+ * The indentation level of the log message.
+ * @param message
+ * The log message in format string syntax that will be logged.
+ * @param params
+ * The parameters that will be swapped into all the place holders
+ * in the original messages before being logged.
+ *
+ * @see Scalr#LOG_PREFIX
+ * @see Scalr#LOG_PREFIX_PROPERTY_NAME
+ */
+ protected static void log(int depth, String message, Object... params) {
+ if (Scalr.DEBUG) {
+ System.out.print(Scalr.LOG_PREFIX);
+
+ for (int i = 0; i < depth; i++)
+ System.out.print("\t");
+
+ System.out.printf(message, params);
+ System.out.println();
+ }
+ }
+
+ /**
+ * Used to create a {@link BufferedImage} with the most optimal RGB TYPE (
+ * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB}
+ * ) capable of being rendered into from the given src
. The
+ * width and height of both images will be identical.
+ *
+ * This does not perform a copy of the image data from src
into
+ * the result image; see {@link #copyToOptimalImage(BufferedImage)} for
+ * that.
+ *
+ * We force all rendering results into one of these two types, avoiding the
+ * case where a source image is of an unsupported (or poorly supported)
+ * format by Java2D causing the rendering result to end up looking terrible
+ * (common with GIFs) or be totally corrupt (e.g. solid black image).
+ *
+ * Originally reported by Magnus Kvalheim from Movellas when scaling certain
+ * GIF and PNG images.
+ *
+ * @param src
+ * The source image that will be analyzed to determine the most
+ * optimal image type it can be rendered into.
+ *
+ * @return a new {@link BufferedImage} representing the most optimal target
+ * image type that src
can be rendered into.
+ *
+ * @see How
+ * Java2D handles poorly supported image types
+ * @see Thanks
+ * to Morten Nobel for implementation hint
+ */
+ protected static BufferedImage createOptimalImage(BufferedImage src) {
+ return createOptimalImage(src, src.getWidth(), src.getHeight());
+ }
+
+ /**
+ * Used to create a {@link BufferedImage} with the given dimensions and the
+ * most optimal RGB TYPE ( {@link BufferedImage#TYPE_INT_RGB} or
+ * {@link BufferedImage#TYPE_INT_ARGB} ) capable of being rendered into from
+ * the given src
.
+ *
+ * This does not perform a copy of the image data from src
into
+ * the result image; see {@link #copyToOptimalImage(BufferedImage)} for
+ * that.
+ *
+ * We force all rendering results into one of these two types, avoiding the
+ * case where a source image is of an unsupported (or poorly supported)
+ * format by Java2D causing the rendering result to end up looking terrible
+ * (common with GIFs) or be totally corrupt (e.g. solid black image).
+ *
+ * Originally reported by Magnus Kvalheim from Movellas when scaling certain
+ * GIF and PNG images.
+ *
+ * @param src
+ * The source image that will be analyzed to determine the most
+ * optimal image type it can be rendered into.
+ * @param width
+ * The width of the newly created resulting image.
+ * @param height
+ * The height of the newly created resulting image.
+ *
+ * @return a new {@link BufferedImage} representing the most optimal target
+ * image type that src
can be rendered into.
+ *
+ * @throws IllegalArgumentException
+ * if width
or height
are < 0.
+ *
+ * @see How
+ * Java2D handles poorly supported image types
+ * @see Thanks
+ * to Morten Nobel for implementation hint
+ */
+ protected static BufferedImage createOptimalImage(BufferedImage src,
+ int width, int height) throws IllegalArgumentException {
+ if (width < 0 || height < 0)
+ throw new IllegalArgumentException("width [" + width
+ + "] and height [" + height + "] must be >= 0");
+
+ return new BufferedImage(
+ width,
+ height,
+ (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
+ : BufferedImage.TYPE_INT_ARGB));
+ }
+
+ /**
+ * Used to copy a {@link BufferedImage} from a non-optimal type into a new
+ * {@link BufferedImage} instance of an optimal type (RGB or ARGB). If
+ * src
is already of an optimal type, then it is returned
+ * unmodified.
+ *
+ * This method is meant to be used by any calling code (imgscalr's or
+ * otherwise) to convert any inbound image from a poorly supported image
+ * type into the 2 most well-supported image types in Java2D (
+ * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB}
+ * ) in order to ensure all subsequent graphics operations are performed as
+ * efficiently and correctly as possible.
+ *
+ * When using Java2D to work with image types that are not well supported,
+ * the results can be anything from exceptions bubbling up from the depths
+ * of Java2D to images being completely corrupted and just returned as solid
+ * black.
+ *
+ * @param src
+ * The image to copy (if necessary) into an optimally typed
+ * {@link BufferedImage}.
+ *
+ * @return a representation of the src
image in an optimally
+ * typed {@link BufferedImage}, otherwise src
if it was
+ * already of an optimal type.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ */
+ protected static BufferedImage copyToOptimalImage(BufferedImage src)
+ throws IllegalArgumentException {
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+
+ // Calculate the type depending on the presence of alpha.
+ int type = (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
+ : BufferedImage.TYPE_INT_ARGB);
+ BufferedImage result = new BufferedImage(src.getWidth(),
+ src.getHeight(), type);
+
+ // Render the src image into our new optimal source.
+ Graphics g = result.getGraphics();
+ g.drawImage(src, 0, 0, null);
+ g.dispose();
+
+ return result;
+ }
+
+ /**
+ * Used to determine the scaling {@link Method} that is best suited for
+ * scaling the image to the targeted dimensions.
+ *
+ * This method is intended to be used to select a specific scaling
+ * {@link Method} when a {@link Method#AUTOMATIC} method is specified. This
+ * method utilizes the {@link Scalr#THRESHOLD_QUALITY_BALANCED} and
+ * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds when selecting which
+ * method should be used by comparing the primary dimension (width or
+ * height) against the threshold and seeing where the image falls. The
+ * primary dimension is determined by looking at the orientation of the
+ * image: landscape or square images use their width and portrait-oriented
+ * images use their height.
+ *
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param ratio
+ * A height/width ratio used to determine the orientation of the
+ * image so the primary dimension (width or height) can be
+ * selected to test if it is greater than or less than a
+ * particular threshold.
+ *
+ * @return the fastest {@link Method} suited for scaling the image to the
+ * specified dimensions while maintaining a good-looking result.
+ */
+ protected static Method determineScalingMethod(int targetWidth,
+ int targetHeight, float ratio) {
+ // Get the primary dimension based on the orientation of the image
+ int length = (ratio <= 1 ? targetWidth : targetHeight);
+
+ // Default to speed
+ Method result = Method.SPEED;
+
+ // Figure out which scalingMethod should be used
+ if (length <= Scalr.THRESHOLD_QUALITY_BALANCED)
+ result = Method.QUALITY;
+ else if (length <= Scalr.THRESHOLD_BALANCED_SPEED)
+ result = Method.BALANCED;
+
+ if (DEBUG)
+ log(2, "AUTOMATIC scaling method selected: %s", result.name());
+
+ return result;
+ }
+
+ /**
+ * Used to implement a straight-forward image-scaling operation using Java
+ * 2D.
+ *
+ * This method uses the Oracle-encouraged method of
+ * Graphics2D.drawImage(...)
to scale the given image with the
+ * given interpolation hint.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param interpolationHintValue
+ * The {@link RenderingHints} interpolation value used to
+ * indicate the method that {@link Graphics2D} should use when
+ * scaling the image.
+ *
+ * @return the result of scaling the original src
to the given
+ * dimensions using the given interpolation method.
+ */
+ protected static BufferedImage scaleImage(BufferedImage src,
+ int targetWidth, int targetHeight, Object interpolationHintValue) {
+ // Setup the rendering resources to match the source image's
+ BufferedImage result = createOptimalImage(src, targetWidth,
+ targetHeight);
+ Graphics2D resultGraphics = result.createGraphics();
+
+ // Scale the image to the new buffer using the specified rendering hint.
+ resultGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ interpolationHintValue);
+ resultGraphics.drawImage(src, 0, 0, targetWidth, targetHeight, null);
+
+ // Just to be clean, explicitly dispose our temporary graphics object
+ resultGraphics.dispose();
+
+ // Return the scaled image to the caller.
+ return result;
+ }
+
+ /**
+ * Used to implement Chris Campbell's incremental-scaling algorithm: http://today.java.net/pub/a/today/2007/04/03/perils
+ * -of-image-getscaledinstance.html.
+ *
+ * Modifications to the original algorithm are variable names and comments
+ * added for clarity and the hard-coding of using BICUBIC interpolation as
+ * well as the explicit "flush()" operation on the interim BufferedImage
+ * instances to avoid resource leaking.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param scalingMethod
+ * The scaling method specified by the user (or calculated by
+ * imgscalr) to use for this incremental scaling operation.
+ * @param interpolationHintValue
+ * The {@link RenderingHints} interpolation value used to
+ * indicate the method that {@link Graphics2D} should use when
+ * scaling the image.
+ *
+ * @return an image scaled to the given dimensions using the given rendering
+ * hint.
+ */
+ protected static BufferedImage scaleImageIncrementally(BufferedImage src,
+ int targetWidth, int targetHeight, Method scalingMethod,
+ Object interpolationHintValue) {
+ boolean hasReassignedSrc = false;
+ int incrementCount = 0;
+ int currentWidth = src.getWidth();
+ int currentHeight = src.getHeight();
+
+ /*
+ * The original QUALITY mode, representing Chris Campbell's algorithm,
+ * is to step down by 1/2s every time when scaling the image
+ * incrementally. Users pointed out that using this method to scale
+ * images with noticeable straight lines left them really jagged in
+ * smaller thumbnail format.
+ *
+ * After investigation it was discovered that scaling incrementally by
+ * smaller increments was the ONLY way to make the thumbnail sized
+ * images look less jagged and more accurate; almost matching the
+ * accuracy of Mac's built in thumbnail generation which is the highest
+ * quality resize I've come across (better than GIMP Lanczos3 and
+ * Windows 7).
+ *
+ * A divisor of 7 was chose as using 5 still left some jaggedness in the
+ * image while a divisor of 8 or higher made the resulting thumbnail too
+ * soft; like our OP_ANTIALIAS convolve op had been forcibly applied to
+ * the result even if the user didn't want it that soft.
+ *
+ * Using a divisor of 7 for the ULTRA_QUALITY seemed to be the sweet
+ * spot.
+ *
+ * NOTE: Below when the actual fraction is used to calculate the small
+ * portion to subtract from the current dimension, this is a
+ * progressively smaller and smaller chunk. When the code was changed to
+ * do a linear reduction of the image of equal steps for each
+ * incremental resize (e.g. say 50px each time) the result was
+ * significantly worse than the progressive approach used below; even
+ * when a very high number of incremental steps (13) was tested.
+ */
+ int fraction = (scalingMethod == Method.ULTRA_QUALITY ? 7 : 2);
+
+ do {
+ int prevCurrentWidth = currentWidth;
+ int prevCurrentHeight = currentHeight;
+
+ /*
+ * If the current width is bigger than our target, cut it in half
+ * and sample again.
+ */
+ if (currentWidth > targetWidth) {
+ currentWidth -= (currentWidth / fraction);
+
+ /*
+ * If we cut the width too far it means we are on our last
+ * iteration. Just set it to the target width and finish up.
+ */
+ if (currentWidth < targetWidth)
+ currentWidth = targetWidth;
+ }
+
+ /*
+ * If the current height is bigger than our target, cut it in half
+ * and sample again.
+ */
+
+ if (currentHeight > targetHeight) {
+ currentHeight -= (currentHeight / fraction);
+
+ /*
+ * If we cut the height too far it means we are on our last
+ * iteration. Just set it to the target height and finish up.
+ */
+
+ if (currentHeight < targetHeight)
+ currentHeight = targetHeight;
+ }
+
+ /*
+ * Stop when we cannot incrementally step down anymore.
+ *
+ * This used to use a || condition, but that would cause problems
+ * when using FIT_EXACT such that sometimes the width OR height
+ * would not change between iterations, but the other dimension
+ * would (e.g. resizing 500x500 to 500x250).
+ *
+ * Now changing this to an && condition requires that both
+ * dimensions do not change between a resize iteration before we
+ * consider ourselves done.
+ */
+ if (prevCurrentWidth == currentWidth
+ && prevCurrentHeight == currentHeight)
+ break;
+
+ if (DEBUG)
+ log(2, "Scaling from [%d x %d] to [%d x %d]", prevCurrentWidth,
+ prevCurrentHeight, currentWidth, currentHeight);
+
+ // Render the incremental scaled image.
+ BufferedImage incrementalImage = scaleImage(src, currentWidth,
+ currentHeight, interpolationHintValue);
+
+ /*
+ * Before re-assigning our interim (partially scaled)
+ * incrementalImage to be the new src image before we iterate around
+ * again to process it down further, we want to flush() the previous
+ * src image IF (and only IF) it was one of our own temporary
+ * BufferedImages created during this incremental down-sampling
+ * cycle. If it wasn't one of ours, then it was the original
+ * caller-supplied BufferedImage in which case we don't want to
+ * flush() it and just leave it alone.
+ */
+ if (hasReassignedSrc)
+ src.flush();
+
+ /*
+ * Now treat our incremental partially scaled image as the src image
+ * and cycle through our loop again to do another incremental
+ * scaling of it (if necessary).
+ */
+ src = incrementalImage;
+
+ /*
+ * Keep track of us re-assigning the original caller-supplied source
+ * image with one of our interim BufferedImages so we know when to
+ * explicitly flush the interim "src" on the next cycle through.
+ */
+ hasReassignedSrc = true;
+
+ // Track how many times we go through this cycle to scale the image.
+ incrementCount++;
+ } while (currentWidth != targetWidth || currentHeight != targetHeight);
+
+ if (DEBUG)
+ log(2, "Incrementally Scaled Image in %d steps.", incrementCount);
+
+ /*
+ * Once the loop has exited, the src image argument is now our scaled
+ * result image that we want to return.
+ */
+ return src;
+ }
+}
\ No newline at end of file
diff --git a/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/SoftReference.java b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/SoftReference.java
new file mode 100644
index 000000000..97185ecf6
--- /dev/null
+++ b/fine-iconloader/src/main/java/com/bulenkov/iconloader/util/SoftReference.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bulenkov.iconloader.util;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * The class is necessary to debug memory allocations via soft references. All IDEA classes should use this SoftReference
+ * instead of original from java.lang.ref. Whenever we suspect soft memory allocation overhead this easily becomes a hard
+ * reference so we can see allocations and memory consumption in memory profiler.
+ *
+ * @author max
+ */
+@SuppressWarnings("ClassNameSameAsAncestorName")
+public class SoftReference