001 /** 002 * Copyright 2011 The Buzz Media, LLC 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.imgscalr; 017 018 import java.awt.Color; 019 import java.awt.Graphics; 020 import java.awt.Graphics2D; 021 import java.awt.Image; 022 import java.awt.RenderingHints; 023 import java.awt.Transparency; 024 import java.awt.color.ColorSpace; 025 import java.awt.geom.AffineTransform; 026 import java.awt.geom.Rectangle2D; 027 import java.awt.image.AreaAveragingScaleFilter; 028 import java.awt.image.BufferedImage; 029 import java.awt.image.BufferedImageOp; 030 import java.awt.image.ColorConvertOp; 031 import java.awt.image.ColorModel; 032 import java.awt.image.ConvolveOp; 033 import java.awt.image.ImagingOpException; 034 import java.awt.image.IndexColorModel; 035 import java.awt.image.Kernel; 036 import java.awt.image.RasterFormatException; 037 import java.awt.image.RescaleOp; 038 039 import javax.imageio.ImageIO; 040 041 /** 042 * Class used to implement performant, high-quality and intelligent image 043 * scaling and manipulation algorithms in native Java 2D. 044 * <p/> 045 * This class utilizes the Java2D "best practices" for image manipulation, 046 * ensuring that all operations (even most user-provided {@link BufferedImageOp} 047 * s) are hardware accelerated if provided by the platform and host-VM. 048 * <p/> 049 * <h3>Image Quality</h3> 050 * This class implements a few different methods for scaling an image, providing 051 * either the best-looking result, the fastest result or a balanced result 052 * between the two depending on the scaling hint provided (see {@link Method}). 053 * <p/> 054 * This class also implements an optimized version of the incremental scaling 055 * algorithm presented by Chris Campbell in his <a href="http://today.java 056 * .net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html">Perils of 057 * Image.getScaledInstance()</a> article in order to give the best-looking image 058 * resize results (e.g. generating thumbnails that aren't blurry or jagged). 059 * <p> 060 * The results generated by imgscalr using this method, as compared to a single 061 * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} scale operation look much 062 * better, especially when using the {@link Method#ULTRA_QUALITY} method. 063 * <p/> 064 * Only when scaling using the {@link Method#AUTOMATIC} method will this class 065 * look at the size of the image before selecting an approach to scaling the 066 * image. If {@link Method#QUALITY} is specified, the best-looking algorithm 067 * possible is always used. 068 * <p/> 069 * Minor modifications are made to Campbell's original implementation in the 070 * form of: 071 * <ol> 072 * <li>Instead of accepting a user-supplied interpolation method, 073 * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} interpolation is always 074 * used. This was done after A/B comparison testing with large images 075 * down-scaled to thumbnail sizes showed noticeable "blurring" when BILINEAR 076 * interpolation was used. Given that Campbell's algorithm is only used in 077 * QUALITY mode when down-scaling, it was determined that the user's expectation 078 * of a much less blurry picture would require that BICUBIC be the default 079 * interpolation in order to meet the QUALITY expectation.</li> 080 * <li>After each iteration of the do-while loop that incrementally scales the 081 * source image down, an explicit effort is made to call 082 * {@link BufferedImage#flush()} on the interim temporary {@link BufferedImage} 083 * instances created by the algorithm in an attempt to ensure a more complete GC 084 * cycle by the VM when cleaning up the temporary instances (this is in addition 085 * to disposing of the temporary {@link Graphics2D} references as well).</li> 086 * <li>Extensive comments have been added to increase readability of the code.</li> 087 * <li>Variable names have been expanded to increase readability of the code.</li> 088 * </ol> 089 * <p/> 090 * <strong>NOTE</strong>: This class does not call {@link BufferedImage#flush()} 091 * on any of the <em>source images</em> passed in by calling code; it is up to 092 * the original caller to dispose of their source images when they are no longer 093 * needed so the VM can most efficiently GC them. 094 * <h3>Image Proportions</h3> 095 * All scaling operations implemented by this class maintain the proportions of 096 * the original image unless a mode of {@link Mode#FIT_EXACT} is specified; in 097 * which case the orientation and proportion of the source image is ignored and 098 * the image is stretched (if necessary) to fit the exact dimensions given. 099 * <p/> 100 * When not using {@link Mode#FIT_EXACT}, in order to maintain the 101 * proportionality of the original images, this class implements the following 102 * behavior: 103 * <ol> 104 * <li>If the image is LANDSCAPE-oriented or SQUARE, treat the 105 * <code>targetWidth</code> as the primary dimension and re-calculate the 106 * <code>targetHeight</code> regardless of what is passed in.</li> 107 * <li>If image is PORTRAIT-oriented, treat the <code>targetHeight</code> as the 108 * primary dimension and re-calculate the <code>targetWidth</code> regardless of 109 * what is passed in.</li> 110 * <li>If a {@link Mode} value of {@link Mode#FIT_TO_WIDTH} or 111 * {@link Mode#FIT_TO_HEIGHT} is passed in to the <code>resize</code> method, 112 * the image's orientation is ignored and the scaled image is fit to the 113 * preferred dimension by using the value passed in by the user for that 114 * dimension and recalculating the other (regardless of image orientation). This 115 * is useful, for example, when working with PORTRAIT oriented images that you 116 * need to all be the same width or visa-versa (e.g. showing user profile 117 * pictures in a directory listing).</li> 118 * </ol> 119 * <h3>Optimized Image Handling</h3> 120 * Java2D provides support for a number of different image types defined as 121 * <code>BufferedImage.TYPE_*</code> variables, unfortunately not all image 122 * types are supported equally in the Java2D rendering pipeline. 123 * <p/> 124 * Some more obscure image types either have poor or no support, leading to 125 * severely degraded quality and processing performance when an attempt is made 126 * by imgscalr to create a scaled instance <em>of the same type</em> as the 127 * source image. In many cases, especially when applying {@link BufferedImageOp} 128 * s, using poorly supported image types can even lead to exceptions or total 129 * corruption of the image (e.g. solid black image). 130 * <p/> 131 * imgscalr specifically accounts for and automatically hands 132 * <strong>ALL</strong> of these pain points for you internally by shuffling all 133 * images into one of two types: 134 * <ol> 135 * <li>{@link BufferedImage#TYPE_INT_RGB}</li> 136 * <li>{@link BufferedImage#TYPE_INT_ARGB}</li> 137 * </ol> 138 * depending on if the source image utilizes transparency or not. This is a 139 * recommended approach by the Java2D team for dealing with poorly (or non) 140 * supported image types. More can be read about this issue <a href= 141 * "http://www.mail-archive.com/java2d-interest@capra.eng.sun.com/msg05621.html" 142 * >here</a>. 143 * <p/> 144 * This is also the reason we recommend using 145 * {@link #apply(BufferedImage, BufferedImageOp...)} to apply your own ops to 146 * images even if you aren't using imgscalr for anything else. 147 * <h3>GIF Transparency</h3> 148 * Unfortunately in Java 6 and earlier, support for GIF's 149 * {@link IndexColorModel} is sub-par, both in accurate color-selection and in 150 * maintaining transparency when moving to an image of type 151 * {@link BufferedImage#TYPE_INT_ARGB}; because of this issue when a GIF image 152 * is processed by imgscalr and the result saved as a GIF file (instead of PNG), 153 * it is possible to lose the alpha channel of a transparent image or in the 154 * case of applying an optional {@link BufferedImageOp}, lose the entire picture 155 * all together in the result (long standing JDK bugs are filed for all of these 156 * issues). 157 * <p/> 158 * imgscalr currently does nothing to work around this manually because it is a 159 * defect in the native platform code itself. Fortunately it looks like the 160 * issues are half-fixed in Java 7 and any manual workarounds we could attempt 161 * internally are relatively expensive, in the form of hand-creating and setting 162 * RGB values pixel-by-pixel with a custom {@link ColorModel} in the scaled 163 * image. This would lead to a very measurable negative impact on performance 164 * without the caller understanding why. 165 * <p> 166 * <strong>Workaround</strong>: A workaround to this issue with all version of 167 * Java is to simply save a GIF as a PNG; no change to your code needs to be 168 * made except when the image is saved out, e.g. using {@link ImageIO}. 169 * <p> 170 * When a file type of "PNG" is used, both the transparency and high color 171 * quality will be maintained as the PNG code path in Java2D is superior to the 172 * GIF implementation. 173 * <p> 174 * If the issue with optional {@link BufferedImageOp}s destroying GIF image 175 * content is ever fixed in the platform, saving out resulting images as GIFs 176 * should suddenly start working. 177 * <p> 178 * More can be read about the issue <a 179 * href="http://gman.eichberger.de/2007/07/transparent-gifs-in-java.html" 180 * >here</a> and <a 181 * href="http://ubuntuforums.org/archive/index.php/t-1060128.html">here</a>. 182 * <h3>Thread Safety</h3> 183 * The {@link Scalr} class is <strong>thread-safe</strong> (as all the methods 184 * are <code>static</code>); this class maintains no internal state while 185 * performing any of the provided operations and is safe to call simultaneously 186 * from multiple threads. 187 * <h3>Logging</h3> 188 * This class implements all its debug logging via the 189 * {@link #log(int, String, Object...)} method. At this time logging is done 190 * directly to <code>System.out</code> via the <code>printf</code> method. This 191 * allows the logging to be light weight and easy to capture (every imgscalr log 192 * message is prefixed with the {@link #LOG_PREFIX} string) while adding no 193 * dependencies to the library. 194 * <p/> 195 * Implementation of logging in this class is as efficient as possible; avoiding 196 * any calls to the logger method or passing of arguments if logging is not 197 * enabled to avoid the (hidden) cost of constructing the Object[] argument for 198 * the varargs-based method call. 199 * 200 * @author Riyad Kalla (software@thebuzzmedia.com) 201 * @since 1.1 202 */ 203 public class Scalr { 204 /** 205 * System property name used to define the debug boolean flag. 206 * <p/> 207 * Value is "<code>imgscalr.debug</code>". 208 */ 209 public static final String DEBUG_PROPERTY_NAME = "imgscalr.debug"; 210 211 /** 212 * System property name used to define a custom log prefix. 213 * <p/> 214 * Value is "<code>imgscalr.logPrefix</code>". 215 */ 216 public static final String LOG_PREFIX_PROPERTY_NAME = "imgscalr.logPrefix"; 217 218 /** 219 * Flag used to indicate if debugging output has been enabled by setting the 220 * "<code>imgscalr.debug</code>" system property to <code>true</code>. This 221 * value will be <code>false</code> if the "<code>imgscalr.debug</code>" 222 * system property is undefined or set to <code>false</code>. 223 * <p/> 224 * This property can be set on startup with:<br/> 225 * <code> 226 * -Dimgscalr.debug=true 227 * </code> or by calling {@link System#setProperty(String, String)} to set a 228 * new property value for {@link #DEBUG_PROPERTY_NAME} before this class is 229 * loaded. 230 * <p/> 231 * Default value is <code>false</code>. 232 */ 233 public static final boolean DEBUG = Boolean.getBoolean(DEBUG_PROPERTY_NAME); 234 235 /** 236 * Prefix to every log message this library logs. Using a well-defined 237 * prefix helps make it easier both visually and programmatically to scan 238 * log files for messages produced by this library. 239 * <p/> 240 * This property can be set on startup with:<br/> 241 * <code> 242 * -Dimgscalr.logPrefix=<YOUR PREFIX HERE> 243 * </code> or by calling {@link System#setProperty(String, String)} to set a 244 * new property value for {@link #LOG_PREFIX_PROPERTY_NAME} before this 245 * class is loaded. 246 * <p/> 247 * Default value is "<code>[imgscalr] </code>" (including the space). 248 */ 249 public static final String LOG_PREFIX = System.getProperty( 250 LOG_PREFIX_PROPERTY_NAME, "[imgscalr] "); 251 252 /** 253 * A {@link ConvolveOp} using a very light "blur" kernel that acts like an 254 * anti-aliasing filter (softens the image a bit) when applied to an image. 255 * <p/> 256 * A common request by users of the library was that they wished to "soften" 257 * resulting images when scaling them down drastically. After quite a bit of 258 * A/B testing, the kernel used by this Op was selected as the closest match 259 * for the target which was the softer results from the deprecated 260 * {@link AreaAveragingScaleFilter} (which is used internally by the 261 * deprecated {@link Image#getScaledInstance(int, int, int)} method in the 262 * JDK that imgscalr is meant to replace). 263 * <p/> 264 * This ConvolveOp uses a 3x3 kernel with the values: 265 * <table cellpadding="4" border="1"> 266 * <tr> 267 * <td>.0f</td> 268 * <td>.08f</td> 269 * <td>.0f</td> 270 * </tr> 271 * <tr> 272 * <td>.08f</td> 273 * <td>.68f</td> 274 * <td>.08f</td> 275 * </tr> 276 * <tr> 277 * <td>.0f</td> 278 * <td>.08f</td> 279 * <td>.0f</td> 280 * </tr> 281 * </table> 282 * <p/> 283 * For those that have worked with ConvolveOps before, this Op uses the 284 * {@link ConvolveOp#EDGE_NO_OP} instruction to not process the pixels along 285 * the very edge of the image (otherwise EDGE_ZERO_FILL would create a 286 * black-border around the image). If you have not worked with a ConvolveOp 287 * before, it just means this default OP will "do the right thing" and not 288 * give you garbage results. 289 * <p/> 290 * This ConvolveOp uses no {@link RenderingHints} values as internally the 291 * {@link ConvolveOp} class only uses hints when doing a color conversion 292 * between the source and destination {@link BufferedImage} targets. 293 * imgscalr allows the {@link ConvolveOp} to create its own destination 294 * image every time, so no color conversion is ever needed and thus no 295 * hints. 296 * <h3>Performance</h3> 297 * Use of this (and other) {@link ConvolveOp}s are hardware accelerated when 298 * possible. For more information on if your image op is hardware 299 * accelerated or not, check the source code of the underlying JDK class 300 * that actually executes the Op code, <a href= 301 * "http://www.docjar.com/html/api/sun/awt/image/ImagingLib.java.html" 302 * >sun.awt.image.ImagingLib</a>. 303 * <h3>Known Issues</h3> 304 * In all versions of Java (tested up to Java 7 preview Build 131), running 305 * this op against a GIF with transparency and attempting to save the 306 * resulting image as a GIF results in a corrupted/empty file. The file must 307 * be saved out as a PNG to maintain the transparency. 308 * 309 * @since 3.0 310 */ 311 public static final ConvolveOp OP_ANTIALIAS = new ConvolveOp( 312 new Kernel(3, 3, new float[] { .0f, .08f, .0f, .08f, .68f, .08f, 313 .0f, .08f, .0f }), ConvolveOp.EDGE_NO_OP, null); 314 315 /** 316 * A {@link RescaleOp} used to make any input image 10% darker. 317 * <p/> 318 * This operation can be applied multiple times in a row if greater than 10% 319 * changes in brightness are desired. 320 * 321 * @since 4.0 322 */ 323 public static final RescaleOp OP_DARKER = new RescaleOp(0.9f, 0, null); 324 325 /** 326 * A {@link RescaleOp} used to make any input image 10% brighter. 327 * <p/> 328 * This operation can be applied multiple times in a row if greater than 10% 329 * changes in brightness are desired. 330 * 331 * @since 4.0 332 */ 333 public static final RescaleOp OP_BRIGHTER = new RescaleOp(1.1f, 0, null); 334 335 /** 336 * A {@link ColorConvertOp} used to convert any image to a grayscale color 337 * palette. 338 * <p/> 339 * Applying this op multiple times to the same image has no compounding 340 * effects. 341 * 342 * @since 4.0 343 */ 344 public static final ColorConvertOp OP_GRAYSCALE = new ColorConvertOp( 345 ColorSpace.getInstance(ColorSpace.CS_GRAY), null); 346 347 /** 348 * Static initializer used to prepare some of the variables used by this 349 * class. 350 */ 351 static { 352 log(0, "Debug output ENABLED"); 353 } 354 355 /** 356 * Used to define the different scaling hints that the algorithm can use. 357 * 358 * @author Riyad Kalla (software@thebuzzmedia.com) 359 * @since 1.1 360 */ 361 public static enum Method { 362 /** 363 * Used to indicate that the scaling implementation should decide which 364 * method to use in order to get the best looking scaled image in the 365 * least amount of time. 366 * <p/> 367 * The scaling algorithm will use the 368 * {@link Scalr#THRESHOLD_QUALITY_BALANCED} or 369 * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds as cut-offs to 370 * decide between selecting the <code>QUALITY</code>, 371 * <code>BALANCED</code> or <code>SPEED</code> scaling algorithms. 372 * <p/> 373 * By default the thresholds chosen will give nearly the best looking 374 * result in the fastest amount of time. We intend this method to work 375 * for 80% of people looking to scale an image quickly and get a good 376 * looking result. 377 */ 378 AUTOMATIC, 379 /** 380 * Used to indicate that the scaling implementation should scale as fast 381 * as possible and return a result. For smaller images (800px in size) 382 * this can result in noticeable aliasing but it can be a few magnitudes 383 * times faster than using the QUALITY method. 384 */ 385 SPEED, 386 /** 387 * Used to indicate that the scaling implementation should use a scaling 388 * operation balanced between SPEED and QUALITY. Sometimes SPEED looks 389 * too low quality to be useful (e.g. text can become unreadable when 390 * scaled using SPEED) but using QUALITY mode will increase the 391 * processing time too much. This mode provides a "better than SPEED" 392 * quality in a "less than QUALITY" amount of time. 393 */ 394 BALANCED, 395 /** 396 * Used to indicate that the scaling implementation should do everything 397 * it can to create as nice of a result as possible. This approach is 398 * most important for smaller pictures (800px or smaller) and less 399 * important for larger pictures as the difference between this method 400 * and the SPEED method become less and less noticeable as the 401 * source-image size increases. Using the AUTOMATIC method will 402 * automatically prefer the QUALITY method when scaling an image down 403 * below 800px in size. 404 */ 405 QUALITY, 406 /** 407 * Used to indicate that the scaling implementation should go above and 408 * beyond the work done by {@link Method#QUALITY} to make the image look 409 * exceptionally good at the cost of more processing time. This is 410 * especially evident when generating thumbnails of images that look 411 * jagged with some of the other {@link Method}s (even 412 * {@link Method#QUALITY}). 413 */ 414 ULTRA_QUALITY; 415 } 416 417 /** 418 * Used to define the different modes of resizing that the algorithm can 419 * use. 420 * 421 * @author Riyad Kalla (software@thebuzzmedia.com) 422 * @since 3.1 423 */ 424 public static enum Mode { 425 /** 426 * Used to indicate that the scaling implementation should calculate 427 * dimensions for the resultant image by looking at the image's 428 * orientation and generating proportional dimensions that best fit into 429 * the target width and height given 430 * 431 * See "Image Proportions" in the {@link Scalr} class description for 432 * more detail. 433 */ 434 AUTOMATIC, 435 /** 436 * Used to fit the image to the exact dimensions given regardless of the 437 * image's proportions. If the dimensions are not proportionally 438 * correct, this will introduce vertical or horizontal stretching to the 439 * image. 440 * <p/> 441 * It is recommended that you use one of the other <code>FIT_TO</code> 442 * modes or {@link Mode#AUTOMATIC} if you want the image to look 443 * correct, but if dimension-fitting is the #1 priority regardless of 444 * how it makes the image look, that is what this mode is for. 445 */ 446 FIT_EXACT, 447 /** 448 * Used to indicate that the scaling implementation should calculate 449 * dimensions for the resultant image that best-fit within the given 450 * width, regardless of the orientation of the image. 451 */ 452 FIT_TO_WIDTH, 453 /** 454 * Used to indicate that the scaling implementation should calculate 455 * dimensions for the resultant image that best-fit within the given 456 * height, regardless of the orientation of the image. 457 */ 458 FIT_TO_HEIGHT; 459 } 460 461 /** 462 * Used to define the different types of rotations that can be applied to an 463 * image during a resize operation. 464 * 465 * @author Riyad Kalla (software@thebuzzmedia.com) 466 * @since 3.2 467 */ 468 public static enum Rotation { 469 /** 470 * 90-degree, clockwise rotation (to the right). This is equivalent to a 471 * quarter-turn of the image to the right; moving the picture on to its 472 * right side. 473 */ 474 CW_90, 475 /** 476 * 180-degree, clockwise rotation (to the right). This is equivalent to 477 * 1 half-turn of the image to the right; rotating the picture around 478 * until it is upside down from the original position. 479 */ 480 CW_180, 481 /** 482 * 270-degree, clockwise rotation (to the right). This is equivalent to 483 * a quarter-turn of the image to the left; moving the picture on to its 484 * left side. 485 */ 486 CW_270, 487 /** 488 * Flip the image horizontally by reflecting it around the y axis. 489 * <p/> 490 * This is not a standard rotation around a center point, but instead 491 * creates the mirrored reflection of the image horizontally. 492 * <p/> 493 * More specifically, the vertical orientation of the image stays the 494 * same (the top stays on top, and the bottom on bottom), but the right 495 * and left sides flip. This is different than a standard rotation where 496 * the top and bottom would also have been flipped. 497 */ 498 FLIP_HORZ, 499 /** 500 * Flip the image vertically by reflecting it around the x axis. 501 * <p/> 502 * This is not a standard rotation around a center point, but instead 503 * creates the mirrored reflection of the image vertically. 504 * <p/> 505 * More specifically, the horizontal orientation of the image stays the 506 * same (the left stays on the left and the right stays on the right), 507 * but the top and bottom sides flip. This is different than a standard 508 * rotation where the left and right would also have been flipped. 509 */ 510 FLIP_VERT; 511 } 512 513 /** 514 * Threshold (in pixels) at which point the scaling operation using the 515 * {@link Method#AUTOMATIC} method will decide if a {@link Method#BALANCED} 516 * method will be used (if smaller than or equal to threshold) or a 517 * {@link Method#SPEED} method will be used (if larger than threshold). 518 * <p/> 519 * The bigger the image is being scaled to, the less noticeable degradations 520 * in the image becomes and the faster algorithms can be selected. 521 * <p/> 522 * The value of this threshold (1600) was chosen after visual, by-hand, A/B 523 * testing between different types of images scaled with this library; both 524 * photographs and screenshots. It was determined that images below this 525 * size need to use a {@link Method#BALANCED} scale method to look decent in 526 * most all cases while using the faster {@link Method#SPEED} method for 527 * images bigger than this threshold showed no noticeable degradation over a 528 * <code>BALANCED</code> scale. 529 */ 530 public static final int THRESHOLD_BALANCED_SPEED = 1600; 531 532 /** 533 * Threshold (in pixels) at which point the scaling operation using the 534 * {@link Method#AUTOMATIC} method will decide if a {@link Method#QUALITY} 535 * method will be used (if smaller than or equal to threshold) or a 536 * {@link Method#BALANCED} method will be used (if larger than threshold). 537 * <p/> 538 * The bigger the image is being scaled to, the less noticeable degradations 539 * in the image becomes and the faster algorithms can be selected. 540 * <p/> 541 * The value of this threshold (800) was chosen after visual, by-hand, A/B 542 * testing between different types of images scaled with this library; both 543 * photographs and screenshots. It was determined that images below this 544 * size need to use a {@link Method#QUALITY} scale method to look decent in 545 * most all cases while using the faster {@link Method#BALANCED} method for 546 * images bigger than this threshold showed no noticeable degradation over a 547 * <code>QUALITY</code> scale. 548 */ 549 public static final int THRESHOLD_QUALITY_BALANCED = 800; 550 551 /** 552 * Used to apply, in the order given, 1 or more {@link BufferedImageOp}s to 553 * a given {@link BufferedImage} and return the result. 554 * <p/> 555 * <strong>Feature</strong>: This implementation works around <a 556 * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606">a 557 * decade-old JDK bug</a> that can cause a {@link RasterFormatException} 558 * when applying a perfectly valid {@link BufferedImageOp}s to images. 559 * <p/> 560 * <strong>Feature</strong>: This implementation also works around 561 * {@link BufferedImageOp}s failing to apply and throwing 562 * {@link ImagingOpException}s when run against a <code>src</code> image 563 * type that is poorly supported. Unfortunately using {@link ImageIO} and 564 * standard Java methods to load images provides no consistency in getting 565 * images in well-supported formats. This method automatically accounts and 566 * corrects for all those problems (if necessary). 567 * <p/> 568 * It is recommended you always use this method to apply any 569 * {@link BufferedImageOp}s instead of relying on directly using the 570 * {@link BufferedImageOp#filter(BufferedImage, BufferedImage)} method. 571 * <p/> 572 * <strong>Performance</strong>: Not all {@link BufferedImageOp}s are 573 * hardware accelerated operations, but many of the most popular (like 574 * {@link ConvolveOp}) are. For more information on if your image op is 575 * hardware accelerated or not, check the source code of the underlying JDK 576 * class that actually executes the Op code, <a href= 577 * "http://www.docjar.com/html/api/sun/awt/image/ImagingLib.java.html" 578 * >sun.awt.image.ImagingLib</a>. 579 * <p/> 580 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 581 * image unmodified. If the caller is done with the <code>src</code> image 582 * after getting the result of this operation, remember to call 583 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 584 * resources and make it easier for the GC to collect the unused image. 585 * 586 * @param src 587 * The image that will have the ops applied to it. 588 * @param ops 589 * <code>1</code> or more ops to apply to the image. 590 * 591 * @return a new {@link BufferedImage} that represents the <code>src</code> 592 * with all the given operations applied to it. 593 * 594 * @throws IllegalArgumentException 595 * if <code>src</code> is <code>null</code>. 596 * @throws IllegalArgumentException 597 * if <code>ops</code> is <code>null</code> or empty. 598 * @throws ImagingOpException 599 * if one of the given {@link BufferedImageOp}s fails to apply. 600 * These exceptions bubble up from the inside of most of the 601 * {@link BufferedImageOp} implementations and are explicitly 602 * defined on the imgscalr API to make it easier for callers to 603 * catch the exception (if they are passing along optional ops 604 * to be applied). imgscalr takes detailed steps to avoid the 605 * most common pitfalls that will cause {@link BufferedImageOp}s 606 * to fail, even when using straight forward JDK-image 607 * operations. 608 */ 609 public static BufferedImage apply(BufferedImage src, BufferedImageOp... ops) 610 throws IllegalArgumentException, ImagingOpException { 611 long t = System.currentTimeMillis(); 612 613 if (src == null) 614 throw new IllegalArgumentException("src cannot be null"); 615 if (ops == null || ops.length == 0) 616 throw new IllegalArgumentException("ops cannot be null or empty"); 617 618 int type = src.getType(); 619 620 /* 621 * Ensure the src image is in the best supported image type before we 622 * continue, otherwise it is possible our calls below to getBounds2D and 623 * certainly filter(...) may fail if not. 624 * 625 * Java2D makes an attempt at applying most BufferedImageOps using 626 * hardware acceleration via the ImagingLib internal library. 627 * 628 * Unfortunately may of the BufferedImageOp are written to simply fail 629 * with an ImagingOpException if the operation cannot be applied with no 630 * additional information about what went wrong or attempts at 631 * re-applying it in different ways. 632 * 633 * This is assuming the failing BufferedImageOp even returns a null 634 * image after failing to apply; some simply return a corrupted/black 635 * image that result in no exception and it is up to the user to 636 * discover this. 637 * 638 * In internal testing, EVERY failure I've ever seen was the result of 639 * the source image being in a poorly-supported BufferedImage Type like 640 * BGR or ABGR (even though it was loaded with ImageIO). 641 * 642 * To avoid this nasty/stupid surprise with BufferedImageOps, we always 643 * ensure that the src image starts in an optimally supported format 644 * before we try and apply the filter. 645 */ 646 if (!(type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB)) 647 src = copyToOptimalImage(src); 648 649 if (DEBUG) 650 log(0, "Applying %d BufferedImageOps...", ops.length); 651 652 boolean hasReassignedSrc = false; 653 654 for (int i = 0; i < ops.length; i++) { 655 long subT = System.currentTimeMillis(); 656 BufferedImageOp op = ops[i]; 657 658 // Skip null ops instead of throwing an exception. 659 if (op == null) 660 continue; 661 662 if (DEBUG) 663 log(1, "Applying BufferedImageOp [class=%s, toString=%s]...", 664 op.getClass(), op.toString()); 665 666 /* 667 * Must use op.getBounds instead of src.getWidth and src.getHeight 668 * because we are trying to create an image big enough to hold the 669 * result of this operation (which may be to scale the image 670 * smaller), in that case the bounds reported by this op and the 671 * bounds reported by the source image will be different. 672 */ 673 Rectangle2D resultBounds = op.getBounds2D(src); 674 675 // Watch out for flaky/misbehaving ops that fail to work right. 676 if (resultBounds == null) 677 throw new ImagingOpException( 678 "BufferedImageOp [" 679 + op.toString() 680 + "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op."); 681 682 /* 683 * We must manually create the target image; we cannot rely on the 684 * null-destination filter() method to create a valid destination 685 * for us thanks to this JDK bug that has been filed for almost a 686 * decade: 687 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606 688 */ 689 BufferedImage dest = createOptimalImage(src, 690 (int) Math.round(resultBounds.getWidth()), 691 (int) Math.round(resultBounds.getHeight())); 692 693 // Perform the operation, update our result to return. 694 BufferedImage result = op.filter(src, dest); 695 696 /* 697 * Flush the 'src' image ONLY IF it is one of our interim temporary 698 * images being used when applying 2 or more operations back to 699 * back. We never want to flush the original image passed in. 700 */ 701 if (hasReassignedSrc) 702 src.flush(); 703 704 /* 705 * Incase there are more operations to perform, update what we 706 * consider the 'src' reference to our last result so on the next 707 * iteration the next op is applied to this result and not back 708 * against the original src passed in. 709 */ 710 src = result; 711 712 /* 713 * Keep track of when we re-assign 'src' to an interim temporary 714 * image, so we know when we can explicitly flush it and clean up 715 * references on future iterations. 716 */ 717 hasReassignedSrc = true; 718 719 if (DEBUG) 720 log(1, 721 "Applied BufferedImageOp in %d ms, result [width=%d, height=%d]", 722 System.currentTimeMillis() - subT, result.getWidth(), 723 result.getHeight()); 724 } 725 726 if (DEBUG) 727 log(0, "All %d BufferedImageOps applied in %d ms", ops.length, 728 System.currentTimeMillis() - t); 729 730 return src; 731 } 732 733 /** 734 * Used to crop the given <code>src</code> image from the top-left corner 735 * and applying any optional {@link BufferedImageOp}s to the result before 736 * returning it. 737 * <p/> 738 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 739 * image unmodified. If the caller is done with the <code>src</code> image 740 * after getting the result of this operation, remember to call 741 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 742 * resources and make it easier for the GC to collect the unused image. 743 * 744 * @param src 745 * The image to crop. 746 * @param width 747 * The width of the bounding cropping box. 748 * @param height 749 * The height of the bounding cropping box. 750 * @param ops 751 * <code>0</code> or more ops to apply to the image. If 752 * <code>null</code> or empty then <code>src</code> is return 753 * unmodified. 754 * 755 * @return a new {@link BufferedImage} representing the cropped region of 756 * the <code>src</code> image with any optional operations applied 757 * to it. 758 * 759 * @throws IllegalArgumentException 760 * if <code>src</code> is <code>null</code>. 761 * @throws IllegalArgumentException 762 * if any coordinates of the bounding crop box is invalid within 763 * the bounds of the <code>src</code> image (e.g. negative or 764 * too big). 765 * @throws ImagingOpException 766 * if one of the given {@link BufferedImageOp}s fails to apply. 767 * These exceptions bubble up from the inside of most of the 768 * {@link BufferedImageOp} implementations and are explicitly 769 * defined on the imgscalr API to make it easier for callers to 770 * catch the exception (if they are passing along optional ops 771 * to be applied). imgscalr takes detailed steps to avoid the 772 * most common pitfalls that will cause {@link BufferedImageOp}s 773 * to fail, even when using straight forward JDK-image 774 * operations. 775 */ 776 public static BufferedImage crop(BufferedImage src, int width, int height, 777 BufferedImageOp... ops) throws IllegalArgumentException, 778 ImagingOpException { 779 return crop(src, 0, 0, width, height, ops); 780 } 781 782 /** 783 * Used to crop the given <code>src</code> image and apply any optional 784 * {@link BufferedImageOp}s to it before returning the result. 785 * <p/> 786 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 787 * image unmodified. If the caller is done with the <code>src</code> image 788 * after getting the result of this operation, remember to call 789 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 790 * resources and make it easier for the GC to collect the unused image. 791 * 792 * @param src 793 * The image to crop. 794 * @param x 795 * The x-coordinate of the top-left corner of the bounding box 796 * used for cropping. 797 * @param y 798 * The y-coordinate of the top-left corner of the bounding box 799 * used for cropping. 800 * @param width 801 * The width of the bounding cropping box. 802 * @param height 803 * The height of the bounding cropping box. 804 * @param ops 805 * <code>0</code> or more ops to apply to the image. If 806 * <code>null</code> or empty then <code>src</code> is return 807 * unmodified. 808 * 809 * @return a new {@link BufferedImage} representing the cropped region of 810 * the <code>src</code> image with any optional operations applied 811 * to it. 812 * 813 * @throws IllegalArgumentException 814 * if <code>src</code> is <code>null</code>. 815 * @throws IllegalArgumentException 816 * if any coordinates of the bounding crop box is invalid within 817 * the bounds of the <code>src</code> image (e.g. negative or 818 * too big). 819 * @throws ImagingOpException 820 * if one of the given {@link BufferedImageOp}s fails to apply. 821 * These exceptions bubble up from the inside of most of the 822 * {@link BufferedImageOp} implementations and are explicitly 823 * defined on the imgscalr API to make it easier for callers to 824 * catch the exception (if they are passing along optional ops 825 * to be applied). imgscalr takes detailed steps to avoid the 826 * most common pitfalls that will cause {@link BufferedImageOp}s 827 * to fail, even when using straight forward JDK-image 828 * operations. 829 */ 830 public static BufferedImage crop(BufferedImage src, int x, int y, 831 int width, int height, BufferedImageOp... ops) 832 throws IllegalArgumentException, ImagingOpException { 833 long t = System.currentTimeMillis(); 834 835 if (src == null) 836 throw new IllegalArgumentException("src cannot be null"); 837 if (x < 0 || y < 0 || width < 0 || height < 0) 838 throw new IllegalArgumentException("Invalid crop bounds: x [" + x 839 + "], y [" + y + "], width [" + width + "] and height [" 840 + height + "] must all be >= 0"); 841 842 int srcWidth = src.getWidth(); 843 int srcHeight = src.getHeight(); 844 845 if ((x + width) > srcWidth) 846 throw new IllegalArgumentException( 847 "Invalid crop bounds: x + width [" + (x + width) 848 + "] must be <= src.getWidth() [" + srcWidth + "]"); 849 if ((y + height) > srcHeight) 850 throw new IllegalArgumentException( 851 "Invalid crop bounds: y + height [" + (y + height) 852 + "] must be <= src.getHeight() [" + srcHeight 853 + "]"); 854 855 if (DEBUG) 856 log(0, 857 "Cropping Image [width=%d, height=%d] to [x=%d, y=%d, width=%d, height=%d]...", 858 srcWidth, srcHeight, x, y, width, height); 859 860 // Create a target image of an optimal type to render into. 861 BufferedImage result = createOptimalImage(src, width, height); 862 Graphics g = result.getGraphics(); 863 864 /* 865 * Render the region specified by our crop bounds from the src image 866 * directly into our result image (which is the exact size of the crop 867 * region). 868 */ 869 g.drawImage(src, 0, 0, width, height, x, y, (x + width), (y + height), 870 null); 871 g.dispose(); 872 873 if (DEBUG) 874 log(0, "Cropped Image in %d ms", System.currentTimeMillis() - t); 875 876 // Apply any optional operations (if specified). 877 if (ops != null && ops.length > 0) 878 result = apply(result, ops); 879 880 return result; 881 } 882 883 /** 884 * Used to apply padding around the edges of an image using 885 * {@link Color#BLACK} to fill the extra padded space and then return the 886 * result. 887 * <p/> 888 * The amount of <code>padding</code> specified is applied to all sides; 889 * more specifically, a <code>padding</code> of <code>2</code> would add 2 890 * extra pixels of space (filled by the given <code>color</code>) on the 891 * top, bottom, left and right sides of the resulting image causing the 892 * result to be 4 pixels wider and 4 pixels taller than the <code>src</code> 893 * image. 894 * <p/> 895 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 896 * image unmodified. If the caller is done with the <code>src</code> image 897 * after getting the result of this operation, remember to call 898 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 899 * resources and make it easier for the GC to collect the unused image. 900 * 901 * @param src 902 * The image the padding will be added to. 903 * @param padding 904 * The number of pixels of padding to add to each side in the 905 * resulting image. If this value is <code>0</code> then 906 * <code>src</code> is returned unmodified. 907 * @param ops 908 * <code>0</code> or more ops to apply to the image. If 909 * <code>null</code> or empty then <code>src</code> is return 910 * unmodified. 911 * 912 * @return a new {@link BufferedImage} representing <code>src</code> with 913 * the given padding applied to it. 914 * 915 * @throws IllegalArgumentException 916 * if <code>src</code> is <code>null</code>. 917 * @throws IllegalArgumentException 918 * if <code>padding</code> is < <code>1</code>. 919 * @throws ImagingOpException 920 * if one of the given {@link BufferedImageOp}s fails to apply. 921 * These exceptions bubble up from the inside of most of the 922 * {@link BufferedImageOp} implementations and are explicitly 923 * defined on the imgscalr API to make it easier for callers to 924 * catch the exception (if they are passing along optional ops 925 * to be applied). imgscalr takes detailed steps to avoid the 926 * most common pitfalls that will cause {@link BufferedImageOp}s 927 * to fail, even when using straight forward JDK-image 928 * operations. 929 */ 930 public static BufferedImage pad(BufferedImage src, int padding, 931 BufferedImageOp... ops) throws IllegalArgumentException, 932 ImagingOpException { 933 return pad(src, padding, Color.BLACK); 934 } 935 936 /** 937 * Used to apply padding around the edges of an image using the given color 938 * to fill the extra padded space and then return the result. {@link Color}s 939 * using an alpha channel (i.e. transparency) are supported. 940 * <p/> 941 * The amount of <code>padding</code> specified is applied to all sides; 942 * more specifically, a <code>padding</code> of <code>2</code> would add 2 943 * extra pixels of space (filled by the given <code>color</code>) on the 944 * top, bottom, left and right sides of the resulting image causing the 945 * result to be 4 pixels wider and 4 pixels taller than the <code>src</code> 946 * image. 947 * <p/> 948 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 949 * image unmodified. If the caller is done with the <code>src</code> image 950 * after getting the result of this operation, remember to call 951 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 952 * resources and make it easier for the GC to collect the unused image. 953 * 954 * @param src 955 * The image the padding will be added to. 956 * @param padding 957 * The number of pixels of padding to add to each side in the 958 * resulting image. If this value is <code>0</code> then 959 * <code>src</code> is returned unmodified. 960 * @param color 961 * The color to fill the padded space with. {@link Color}s using 962 * an alpha channel (i.e. transparency) are supported. 963 * @param ops 964 * <code>0</code> or more ops to apply to the image. If 965 * <code>null</code> or empty then <code>src</code> is return 966 * unmodified. 967 * 968 * @return a new {@link BufferedImage} representing <code>src</code> with 969 * the given padding applied to it. 970 * 971 * @throws IllegalArgumentException 972 * if <code>src</code> is <code>null</code>. 973 * @throws IllegalArgumentException 974 * if <code>padding</code> is < <code>1</code>. 975 * @throws IllegalArgumentException 976 * if <code>color</code> is <code>null</code>. 977 * @throws ImagingOpException 978 * if one of the given {@link BufferedImageOp}s fails to apply. 979 * These exceptions bubble up from the inside of most of the 980 * {@link BufferedImageOp} implementations and are explicitly 981 * defined on the imgscalr API to make it easier for callers to 982 * catch the exception (if they are passing along optional ops 983 * to be applied). imgscalr takes detailed steps to avoid the 984 * most common pitfalls that will cause {@link BufferedImageOp}s 985 * to fail, even when using straight forward JDK-image 986 * operations. 987 */ 988 public static BufferedImage pad(BufferedImage src, int padding, 989 Color color, BufferedImageOp... ops) 990 throws IllegalArgumentException, ImagingOpException { 991 long t = System.currentTimeMillis(); 992 993 if (src == null) 994 throw new IllegalArgumentException("src cannot be null"); 995 if (padding < 1) 996 throw new IllegalArgumentException("padding [" + padding 997 + "] must be > 0"); 998 if (color == null) 999 throw new IllegalArgumentException("color cannot be null"); 1000 1001 int srcWidth = src.getWidth(); 1002 int srcHeight = src.getHeight(); 1003 1004 /* 1005 * Double the padding to account for all sides of the image. More 1006 * specifically, if padding is "1" we add 2 pixels to width and 2 to 1007 * height, so we have 1 new pixel of padding all the way around our 1008 * image. 1009 */ 1010 int sizeDiff = (padding * 2); 1011 int newWidth = srcWidth + sizeDiff; 1012 int newHeight = srcHeight + sizeDiff; 1013 1014 if (DEBUG) 1015 log(0, 1016 "Padding Image from [originalWidth=%d, originalHeight=%d, padding=%d] to [newWidth=%d, newHeight=%d]...", 1017 srcWidth, srcHeight, padding, newWidth, newHeight); 1018 1019 boolean colorHasAlpha = (color.getAlpha() != 255); 1020 boolean imageHasAlpha = (src.getTransparency() != BufferedImage.OPAQUE); 1021 1022 BufferedImage result; 1023 1024 /* 1025 * We need to make sure our resulting image that we render into contains 1026 * alpha if either our original image OR the padding color we are using 1027 * contain it. 1028 */ 1029 if (colorHasAlpha || imageHasAlpha) { 1030 if (DEBUG) 1031 log(1, 1032 "Transparency FOUND in source image or color, using ARGB image type..."); 1033 1034 result = new BufferedImage(newWidth, newHeight, 1035 BufferedImage.TYPE_INT_ARGB); 1036 } else { 1037 if (DEBUG) 1038 log(1, 1039 "Transparency NOT FOUND in source image or color, using RGB image type..."); 1040 1041 result = new BufferedImage(newWidth, newHeight, 1042 BufferedImage.TYPE_INT_RGB); 1043 } 1044 1045 Graphics g = result.getGraphics(); 1046 1047 // "Clear" the background of the new image with our padding color first. 1048 g.setColor(color); 1049 g.fillRect(0, 0, newWidth, newHeight); 1050 1051 // Draw the image into the center of the new padded image. 1052 g.drawImage(src, padding, padding, null); 1053 g.dispose(); 1054 1055 if (DEBUG) 1056 log(0, "Padding Applied in %d ms", System.currentTimeMillis() - t); 1057 1058 // Apply any optional operations (if specified). 1059 if (ops != null && ops.length > 0) 1060 result = apply(result, ops); 1061 1062 return result; 1063 } 1064 1065 /** 1066 * Resize a given image (maintaining its original proportion) to a width and 1067 * height no bigger than <code>targetSize</code> and apply the given 1068 * {@link BufferedImageOp}s (if any) to the result before returning it. 1069 * <p/> 1070 * A scaling method of {@link Method#AUTOMATIC} and mode of 1071 * {@link Mode#AUTOMATIC} are used. 1072 * <p/> 1073 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1074 * image unmodified. If the caller is done with the <code>src</code> image 1075 * after getting the result of this operation, remember to call 1076 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1077 * resources and make it easier for the GC to collect the unused image. 1078 * 1079 * @param src 1080 * The image that will be scaled. 1081 * @param targetSize 1082 * The target width and height (square) that you wish the image 1083 * to fit within. 1084 * @param ops 1085 * <code>0</code> or more optional image operations (e.g. 1086 * sharpen, blur, etc.) that can be applied to the final result 1087 * before returning the image. 1088 * 1089 * @return a new {@link BufferedImage} representing the scaled 1090 * <code>src</code> image. 1091 * 1092 * @throws IllegalArgumentException 1093 * if <code>src</code> is <code>null</code>. 1094 * @throws IllegalArgumentException 1095 * if <code>targetSize</code> is < 0. 1096 * @throws ImagingOpException 1097 * if one of the given {@link BufferedImageOp}s fails to apply. 1098 * These exceptions bubble up from the inside of most of the 1099 * {@link BufferedImageOp} implementations and are explicitly 1100 * defined on the imgscalr API to make it easier for callers to 1101 * catch the exception (if they are passing along optional ops 1102 * to be applied). imgscalr takes detailed steps to avoid the 1103 * most common pitfalls that will cause {@link BufferedImageOp}s 1104 * to fail, even when using straight forward JDK-image 1105 * operations. 1106 */ 1107 public static BufferedImage resize(BufferedImage src, int targetSize, 1108 BufferedImageOp... ops) throws IllegalArgumentException, 1109 ImagingOpException { 1110 return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetSize, 1111 targetSize, ops); 1112 } 1113 1114 /** 1115 * Resize a given image (maintaining its original proportion) to a width and 1116 * height no bigger than <code>targetSize</code> using the given scaling 1117 * method and apply the given {@link BufferedImageOp}s (if any) to the 1118 * result before returning it. 1119 * <p/> 1120 * A mode of {@link Mode#AUTOMATIC} is used. 1121 * <p/> 1122 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1123 * image unmodified. If the caller is done with the <code>src</code> image 1124 * after getting the result of this operation, remember to call 1125 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1126 * resources and make it easier for the GC to collect the unused image. 1127 * 1128 * @param src 1129 * The image that will be scaled. 1130 * @param scalingMethod 1131 * The method used for scaling the image; preferring speed to 1132 * quality or a balance of both. 1133 * @param targetSize 1134 * The target width and height (square) that you wish the image 1135 * to fit within. 1136 * @param ops 1137 * <code>0</code> or more optional image operations (e.g. 1138 * sharpen, blur, etc.) that can be applied to the final result 1139 * before returning the image. 1140 * 1141 * @return a new {@link BufferedImage} representing the scaled 1142 * <code>src</code> image. 1143 * 1144 * @throws IllegalArgumentException 1145 * if <code>src</code> is <code>null</code>. 1146 * @throws IllegalArgumentException 1147 * if <code>scalingMethod</code> is <code>null</code>. 1148 * @throws IllegalArgumentException 1149 * if <code>targetSize</code> is < 0. 1150 * @throws ImagingOpException 1151 * if one of the given {@link BufferedImageOp}s fails to apply. 1152 * These exceptions bubble up from the inside of most of the 1153 * {@link BufferedImageOp} implementations and are explicitly 1154 * defined on the imgscalr API to make it easier for callers to 1155 * catch the exception (if they are passing along optional ops 1156 * to be applied). imgscalr takes detailed steps to avoid the 1157 * most common pitfalls that will cause {@link BufferedImageOp}s 1158 * to fail, even when using straight forward JDK-image 1159 * operations. 1160 * 1161 * @see Method 1162 */ 1163 public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1164 int targetSize, BufferedImageOp... ops) 1165 throws IllegalArgumentException, ImagingOpException { 1166 return resize(src, scalingMethod, Mode.AUTOMATIC, targetSize, 1167 targetSize, ops); 1168 } 1169 1170 /** 1171 * Resize a given image (maintaining its original proportion) to a width and 1172 * height no bigger than <code>targetSize</code> (or fitting the image to 1173 * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode} 1174 * specified) and apply the given {@link BufferedImageOp}s (if any) to the 1175 * result before returning it. 1176 * <p/> 1177 * A scaling method of {@link Method#AUTOMATIC} is used. 1178 * <p/> 1179 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1180 * image unmodified. If the caller is done with the <code>src</code> image 1181 * after getting the result of this operation, remember to call 1182 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1183 * resources and make it easier for the GC to collect the unused image. 1184 * 1185 * @param src 1186 * The image that will be scaled. 1187 * @param resizeMode 1188 * Used to indicate how imgscalr should calculate the final 1189 * target size for the image, either fitting the image to the 1190 * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1191 * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1192 * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1193 * proportional dimensions for the scaled image based on its 1194 * orientation (landscape, square or portrait). Unless you have 1195 * very specific size requirements, most of the time you just 1196 * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1197 * @param targetSize 1198 * The target width and height (square) that you wish the image 1199 * to fit within. 1200 * @param ops 1201 * <code>0</code> or more optional image operations (e.g. 1202 * sharpen, blur, etc.) that can be applied to the final result 1203 * before returning the image. 1204 * 1205 * @return a new {@link BufferedImage} representing the scaled 1206 * <code>src</code> image. 1207 * 1208 * @throws IllegalArgumentException 1209 * if <code>src</code> is <code>null</code>. 1210 * @throws IllegalArgumentException 1211 * if <code>resizeMode</code> is <code>null</code>. 1212 * @throws IllegalArgumentException 1213 * if <code>targetSize</code> is < 0. 1214 * @throws ImagingOpException 1215 * if one of the given {@link BufferedImageOp}s fails to apply. 1216 * These exceptions bubble up from the inside of most of the 1217 * {@link BufferedImageOp} implementations and are explicitly 1218 * defined on the imgscalr API to make it easier for callers to 1219 * catch the exception (if they are passing along optional ops 1220 * to be applied). imgscalr takes detailed steps to avoid the 1221 * most common pitfalls that will cause {@link BufferedImageOp}s 1222 * to fail, even when using straight forward JDK-image 1223 * operations. 1224 * 1225 * @see Mode 1226 */ 1227 public static BufferedImage resize(BufferedImage src, Mode resizeMode, 1228 int targetSize, BufferedImageOp... ops) 1229 throws IllegalArgumentException, ImagingOpException { 1230 return resize(src, Method.AUTOMATIC, resizeMode, targetSize, 1231 targetSize, ops); 1232 } 1233 1234 /** 1235 * Resize a given image (maintaining its original proportion) to a width and 1236 * height no bigger than <code>targetSize</code> (or fitting the image to 1237 * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode} 1238 * specified) using the given scaling method and apply the given 1239 * {@link BufferedImageOp}s (if any) to the result before returning it. 1240 * <p/> 1241 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1242 * image unmodified. If the caller is done with the <code>src</code> image 1243 * after getting the result of this operation, remember to call 1244 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1245 * resources and make it easier for the GC to collect the unused image. 1246 * 1247 * @param src 1248 * The image that will be scaled. 1249 * @param scalingMethod 1250 * The method used for scaling the image; preferring speed to 1251 * quality or a balance of both. 1252 * @param resizeMode 1253 * Used to indicate how imgscalr should calculate the final 1254 * target size for the image, either fitting the image to the 1255 * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1256 * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1257 * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1258 * proportional dimensions for the scaled image based on its 1259 * orientation (landscape, square or portrait). Unless you have 1260 * very specific size requirements, most of the time you just 1261 * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1262 * @param targetSize 1263 * The target width and height (square) that you wish the image 1264 * to fit within. 1265 * @param ops 1266 * <code>0</code> or more optional image operations (e.g. 1267 * sharpen, blur, etc.) that can be applied to the final result 1268 * before returning the image. 1269 * 1270 * @return a new {@link BufferedImage} representing the scaled 1271 * <code>src</code> image. 1272 * 1273 * @throws IllegalArgumentException 1274 * if <code>src</code> is <code>null</code>. 1275 * @throws IllegalArgumentException 1276 * if <code>scalingMethod</code> is <code>null</code>. 1277 * @throws IllegalArgumentException 1278 * if <code>resizeMode</code> is <code>null</code>. 1279 * @throws IllegalArgumentException 1280 * if <code>targetSize</code> is < 0. 1281 * @throws ImagingOpException 1282 * if one of the given {@link BufferedImageOp}s fails to apply. 1283 * These exceptions bubble up from the inside of most of the 1284 * {@link BufferedImageOp} implementations and are explicitly 1285 * defined on the imgscalr API to make it easier for callers to 1286 * catch the exception (if they are passing along optional ops 1287 * to be applied). imgscalr takes detailed steps to avoid the 1288 * most common pitfalls that will cause {@link BufferedImageOp}s 1289 * to fail, even when using straight forward JDK-image 1290 * operations. 1291 * 1292 * @see Method 1293 * @see Mode 1294 */ 1295 public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1296 Mode resizeMode, int targetSize, BufferedImageOp... ops) 1297 throws IllegalArgumentException, ImagingOpException { 1298 return resize(src, scalingMethod, resizeMode, targetSize, targetSize, 1299 ops); 1300 } 1301 1302 /** 1303 * Resize a given image (maintaining its original proportion) to the target 1304 * width and height and apply the given {@link BufferedImageOp}s (if any) to 1305 * the result before returning it. 1306 * <p/> 1307 * A scaling method of {@link Method#AUTOMATIC} and mode of 1308 * {@link Mode#AUTOMATIC} are used. 1309 * <p/> 1310 * <strong>TIP</strong>: See the class description to understand how this 1311 * class handles recalculation of the <code>targetWidth</code> or 1312 * <code>targetHeight</code> depending on the image's orientation in order 1313 * to maintain the original proportion. 1314 * <p/> 1315 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1316 * image unmodified. If the caller is done with the <code>src</code> image 1317 * after getting the result of this operation, remember to call 1318 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1319 * resources and make it easier for the GC to collect the unused image. 1320 * 1321 * @param src 1322 * The image that will be scaled. 1323 * @param targetWidth 1324 * The target width that you wish the image to have. 1325 * @param targetHeight 1326 * The target height that you wish the image to have. 1327 * @param ops 1328 * <code>0</code> or more optional image operations (e.g. 1329 * sharpen, blur, etc.) that can be applied to the final result 1330 * before returning the image. 1331 * 1332 * @return a new {@link BufferedImage} representing the scaled 1333 * <code>src</code> image. 1334 * 1335 * @throws IllegalArgumentException 1336 * if <code>src</code> is <code>null</code>. 1337 * @throws IllegalArgumentException 1338 * if <code>targetWidth</code> is < 0 or if 1339 * <code>targetHeight</code> is < 0. 1340 * @throws ImagingOpException 1341 * if one of the given {@link BufferedImageOp}s fails to apply. 1342 * These exceptions bubble up from the inside of most of the 1343 * {@link BufferedImageOp} implementations and are explicitly 1344 * defined on the imgscalr API to make it easier for callers to 1345 * catch the exception (if they are passing along optional ops 1346 * to be applied). imgscalr takes detailed steps to avoid the 1347 * most common pitfalls that will cause {@link BufferedImageOp}s 1348 * to fail, even when using straight forward JDK-image 1349 * operations. 1350 */ 1351 public static BufferedImage resize(BufferedImage src, int targetWidth, 1352 int targetHeight, BufferedImageOp... ops) 1353 throws IllegalArgumentException, ImagingOpException { 1354 return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetWidth, 1355 targetHeight, ops); 1356 } 1357 1358 /** 1359 * Resize a given image (maintaining its original proportion) to the target 1360 * width and height using the given scaling method and apply the given 1361 * {@link BufferedImageOp}s (if any) to the result before returning it. 1362 * <p/> 1363 * A mode of {@link Mode#AUTOMATIC} is used. 1364 * <p/> 1365 * <strong>TIP</strong>: See the class description to understand how this 1366 * class handles recalculation of the <code>targetWidth</code> or 1367 * <code>targetHeight</code> depending on the image's orientation in order 1368 * to maintain the original proportion. 1369 * <p/> 1370 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1371 * image unmodified. If the caller is done with the <code>src</code> image 1372 * after getting the result of this operation, remember to call 1373 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1374 * resources and make it easier for the GC to collect the unused image. 1375 * 1376 * @param src 1377 * The image that will be scaled. 1378 * @param scalingMethod 1379 * The method used for scaling the image; preferring speed to 1380 * quality or a balance of both. 1381 * @param targetWidth 1382 * The target width that you wish the image to have. 1383 * @param targetHeight 1384 * The target height that you wish the image to have. 1385 * @param ops 1386 * <code>0</code> or more optional image operations (e.g. 1387 * sharpen, blur, etc.) that can be applied to the final result 1388 * before returning the image. 1389 * 1390 * @return a new {@link BufferedImage} representing the scaled 1391 * <code>src</code> image. 1392 * 1393 * @throws IllegalArgumentException 1394 * if <code>src</code> is <code>null</code>. 1395 * @throws IllegalArgumentException 1396 * if <code>scalingMethod</code> is <code>null</code>. 1397 * @throws IllegalArgumentException 1398 * if <code>targetWidth</code> is < 0 or if 1399 * <code>targetHeight</code> is < 0. 1400 * @throws ImagingOpException 1401 * if one of the given {@link BufferedImageOp}s fails to apply. 1402 * These exceptions bubble up from the inside of most of the 1403 * {@link BufferedImageOp} implementations and are explicitly 1404 * defined on the imgscalr API to make it easier for callers to 1405 * catch the exception (if they are passing along optional ops 1406 * to be applied). imgscalr takes detailed steps to avoid the 1407 * most common pitfalls that will cause {@link BufferedImageOp}s 1408 * to fail, even when using straight forward JDK-image 1409 * operations. 1410 * 1411 * @see Method 1412 */ 1413 public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1414 int targetWidth, int targetHeight, BufferedImageOp... ops) { 1415 return resize(src, scalingMethod, Mode.AUTOMATIC, targetWidth, 1416 targetHeight, ops); 1417 } 1418 1419 /** 1420 * Resize a given image (maintaining its original proportion) to the target 1421 * width and height (or fitting the image to the given WIDTH or HEIGHT 1422 * explicitly, depending on the {@link Mode} specified) and apply the given 1423 * {@link BufferedImageOp}s (if any) to the result before returning it. 1424 * <p/> 1425 * A scaling method of {@link Method#AUTOMATIC} is used. 1426 * <p/> 1427 * <strong>TIP</strong>: See the class description to understand how this 1428 * class handles recalculation of the <code>targetWidth</code> or 1429 * <code>targetHeight</code> depending on the image's orientation in order 1430 * to maintain the original proportion. 1431 * <p/> 1432 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1433 * image unmodified. If the caller is done with the <code>src</code> image 1434 * after getting the result of this operation, remember to call 1435 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1436 * resources and make it easier for the GC to collect the unused image. 1437 * 1438 * @param src 1439 * The image that will be scaled. 1440 * @param resizeMode 1441 * Used to indicate how imgscalr should calculate the final 1442 * target size for the image, either fitting the image to the 1443 * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1444 * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1445 * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1446 * proportional dimensions for the scaled image based on its 1447 * orientation (landscape, square or portrait). Unless you have 1448 * very specific size requirements, most of the time you just 1449 * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1450 * @param targetWidth 1451 * The target width that you wish the image to have. 1452 * @param targetHeight 1453 * The target height that you wish the image to have. 1454 * @param ops 1455 * <code>0</code> or more optional image operations (e.g. 1456 * sharpen, blur, etc.) that can be applied to the final result 1457 * before returning the image. 1458 * 1459 * @return a new {@link BufferedImage} representing the scaled 1460 * <code>src</code> image. 1461 * 1462 * @throws IllegalArgumentException 1463 * if <code>src</code> is <code>null</code>. 1464 * @throws IllegalArgumentException 1465 * if <code>resizeMode</code> is <code>null</code>. 1466 * @throws IllegalArgumentException 1467 * if <code>targetWidth</code> is < 0 or if 1468 * <code>targetHeight</code> is < 0. 1469 * @throws ImagingOpException 1470 * if one of the given {@link BufferedImageOp}s fails to apply. 1471 * These exceptions bubble up from the inside of most of the 1472 * {@link BufferedImageOp} implementations and are explicitly 1473 * defined on the imgscalr API to make it easier for callers to 1474 * catch the exception (if they are passing along optional ops 1475 * to be applied). imgscalr takes detailed steps to avoid the 1476 * most common pitfalls that will cause {@link BufferedImageOp}s 1477 * to fail, even when using straight forward JDK-image 1478 * operations. 1479 * 1480 * @see Mode 1481 */ 1482 public static BufferedImage resize(BufferedImage src, Mode resizeMode, 1483 int targetWidth, int targetHeight, BufferedImageOp... ops) 1484 throws IllegalArgumentException, ImagingOpException { 1485 return resize(src, Method.AUTOMATIC, resizeMode, targetWidth, 1486 targetHeight, ops); 1487 } 1488 1489 /** 1490 * Resize a given image (maintaining its original proportion) to the target 1491 * width and height (or fitting the image to the given WIDTH or HEIGHT 1492 * explicitly, depending on the {@link Mode} specified) using the given 1493 * scaling method and apply the given {@link BufferedImageOp}s (if any) to 1494 * the result before returning it. 1495 * <p/> 1496 * <strong>TIP</strong>: See the class description to understand how this 1497 * class handles recalculation of the <code>targetWidth</code> or 1498 * <code>targetHeight</code> depending on the image's orientation in order 1499 * to maintain the original proportion. 1500 * <p/> 1501 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1502 * image unmodified. If the caller is done with the <code>src</code> image 1503 * after getting the result of this operation, remember to call 1504 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1505 * resources and make it easier for the GC to collect the unused image. 1506 * 1507 * @param src 1508 * The image that will be scaled. 1509 * @param scalingMethod 1510 * The method used for scaling the image; preferring speed to 1511 * quality or a balance of both. 1512 * @param resizeMode 1513 * Used to indicate how imgscalr should calculate the final 1514 * target size for the image, either fitting the image to the 1515 * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1516 * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1517 * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1518 * proportional dimensions for the scaled image based on its 1519 * orientation (landscape, square or portrait). Unless you have 1520 * very specific size requirements, most of the time you just 1521 * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1522 * @param targetWidth 1523 * The target width that you wish the image to have. 1524 * @param targetHeight 1525 * The target height that you wish the image to have. 1526 * @param ops 1527 * <code>0</code> or more optional image operations (e.g. 1528 * sharpen, blur, etc.) that can be applied to the final result 1529 * before returning the image. 1530 * 1531 * @return a new {@link BufferedImage} representing the scaled 1532 * <code>src</code> image. 1533 * 1534 * @throws IllegalArgumentException 1535 * if <code>src</code> is <code>null</code>. 1536 * @throws IllegalArgumentException 1537 * if <code>scalingMethod</code> is <code>null</code>. 1538 * @throws IllegalArgumentException 1539 * if <code>resizeMode</code> is <code>null</code>. 1540 * @throws IllegalArgumentException 1541 * if <code>targetWidth</code> is < 0 or if 1542 * <code>targetHeight</code> is < 0. 1543 * @throws ImagingOpException 1544 * if one of the given {@link BufferedImageOp}s fails to apply. 1545 * These exceptions bubble up from the inside of most of the 1546 * {@link BufferedImageOp} implementations and are explicitly 1547 * defined on the imgscalr API to make it easier for callers to 1548 * catch the exception (if they are passing along optional ops 1549 * to be applied). imgscalr takes detailed steps to avoid the 1550 * most common pitfalls that will cause {@link BufferedImageOp}s 1551 * to fail, even when using straight forward JDK-image 1552 * operations. 1553 * 1554 * @see Method 1555 * @see Mode 1556 */ 1557 public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1558 Mode resizeMode, int targetWidth, int targetHeight, 1559 BufferedImageOp... ops) throws IllegalArgumentException, 1560 ImagingOpException { 1561 long t = System.currentTimeMillis(); 1562 1563 if (src == null) 1564 throw new IllegalArgumentException("src cannot be null"); 1565 if (targetWidth < 0) 1566 throw new IllegalArgumentException("targetWidth must be >= 0"); 1567 if (targetHeight < 0) 1568 throw new IllegalArgumentException("targetHeight must be >= 0"); 1569 if (scalingMethod == null) 1570 throw new IllegalArgumentException( 1571 "scalingMethod cannot be null. A good default value is Method.AUTOMATIC."); 1572 if (resizeMode == null) 1573 throw new IllegalArgumentException( 1574 "resizeMode cannot be null. A good default value is Mode.AUTOMATIC."); 1575 1576 BufferedImage result = null; 1577 1578 int currentWidth = src.getWidth(); 1579 int currentHeight = src.getHeight(); 1580 1581 // <= 1 is a square or landscape-oriented image, > 1 is a portrait. 1582 float ratio = ((float) currentHeight / (float) currentWidth); 1583 1584 if (DEBUG) 1585 log(0, 1586 "Resizing Image [size=%dx%d, resizeMode=%s, orientation=%s, ratio(H/W)=%f] to [targetSize=%dx%d]", 1587 currentWidth, currentHeight, resizeMode, 1588 (ratio <= 1 ? "Landscape/Square" : "Portrait"), ratio, 1589 targetWidth, targetHeight); 1590 1591 /* 1592 * First determine if ANY size calculation needs to be done, in the case 1593 * of FIT_EXACT, ignore image proportions and orientation and just use 1594 * what the user sent in, otherwise the proportion of the picture must 1595 * be honored. 1596 * 1597 * The way that is done is to figure out if the image is in a 1598 * LANDSCAPE/SQUARE or PORTRAIT orientation and depending on its 1599 * orientation, use the primary dimension (width for LANDSCAPE/SQUARE 1600 * and height for PORTRAIT) to recalculate the alternative (height and 1601 * width respectively) value that adheres to the existing ratio. 1602 * 1603 * This helps make life easier for the caller as they don't need to 1604 * pre-compute proportional dimensions before calling the API, they can 1605 * just specify the dimensions they would like the image to roughly fit 1606 * within and it will do the right thing without mangling the result. 1607 */ 1608 if (resizeMode != Mode.FIT_EXACT) { 1609 if ((ratio <= 1 && resizeMode == Mode.AUTOMATIC) 1610 || (resizeMode == Mode.FIT_TO_WIDTH)) { 1611 // First make sure we need to do any work in the first place 1612 if (targetWidth == src.getWidth()) 1613 return src; 1614 1615 // Save for detailed logging (this is cheap). 1616 int originalTargetHeight = targetHeight; 1617 1618 /* 1619 * Landscape or Square Orientation: Ignore the given height and 1620 * re-calculate a proportionally correct value based on the 1621 * targetWidth. 1622 */ 1623 targetHeight = Math.round((float) targetWidth * ratio); 1624 1625 if (DEBUG && originalTargetHeight != targetHeight) 1626 log(1, 1627 "Auto-Corrected targetHeight [from=%d to=%d] to honor image proportions.", 1628 originalTargetHeight, targetHeight); 1629 } else { 1630 // First make sure we need to do any work in the first place 1631 if (targetHeight == src.getHeight()) 1632 return src; 1633 1634 // Save for detailed logging (this is cheap). 1635 int originalTargetWidth = targetWidth; 1636 1637 /* 1638 * Portrait Orientation: Ignore the given width and re-calculate 1639 * a proportionally correct value based on the targetHeight. 1640 */ 1641 targetWidth = Math.round((float) targetHeight / ratio); 1642 1643 if (DEBUG && originalTargetWidth != targetWidth) 1644 log(1, 1645 "Auto-Corrected targetWidth [from=%d to=%d] to honor image proportions.", 1646 originalTargetWidth, targetWidth); 1647 } 1648 } else { 1649 if (DEBUG) 1650 log(1, 1651 "Resize Mode FIT_EXACT used, no width/height checking or re-calculation will be done."); 1652 } 1653 1654 // If AUTOMATIC was specified, determine the real scaling method. 1655 if (scalingMethod == Scalr.Method.AUTOMATIC) 1656 scalingMethod = determineScalingMethod(targetWidth, targetHeight, 1657 ratio); 1658 1659 if (DEBUG) 1660 log(1, "Using Scaling Method: %s", scalingMethod); 1661 1662 // Now we scale the image 1663 if (scalingMethod == Scalr.Method.SPEED) { 1664 result = scaleImage(src, targetWidth, targetHeight, 1665 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 1666 } else if (scalingMethod == Scalr.Method.BALANCED) { 1667 result = scaleImage(src, targetWidth, targetHeight, 1668 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 1669 } else if (scalingMethod == Scalr.Method.QUALITY 1670 || scalingMethod == Scalr.Method.ULTRA_QUALITY) { 1671 /* 1672 * If we are scaling up (in either width or height - since we know 1673 * the image will stay proportional we just check if either are 1674 * being scaled up), directly using a single BICUBIC will give us 1675 * better results then using Chris Campbell's incremental scaling 1676 * operation (and take a lot less time). 1677 * 1678 * If we are scaling down, we must use the incremental scaling 1679 * algorithm for the best result. 1680 */ 1681 if (targetWidth > currentWidth || targetHeight > currentHeight) { 1682 if (DEBUG) 1683 log(1, 1684 "QUALITY scale-up, a single BICUBIC scale operation will be used..."); 1685 1686 /* 1687 * BILINEAR and BICUBIC look similar the smaller the scale jump 1688 * upwards is, if the scale is larger BICUBIC looks sharper and 1689 * less fuzzy. But most importantly we have to use BICUBIC to 1690 * match the contract of the QUALITY rendering scalingMethod. 1691 * This note is just here for anyone reading the code and 1692 * wondering how they can speed their own calls up. 1693 */ 1694 result = scaleImage(src, targetWidth, targetHeight, 1695 RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1696 } else { 1697 if (DEBUG) 1698 log(1, 1699 "QUALITY scale-down, incremental scaling will be used..."); 1700 1701 /* 1702 * Originally we wanted to use BILINEAR interpolation here 1703 * because it takes 1/3rd the time that the BICUBIC 1704 * interpolation does, however, when scaling large images down 1705 * to most sizes bigger than a thumbnail we witnessed noticeable 1706 * "softening" in the resultant image with BILINEAR that would 1707 * be unexpectedly annoying to a user expecting a "QUALITY" 1708 * scale of their original image. Instead BICUBIC was chosen to 1709 * honor the contract of a QUALITY scale of the original image. 1710 */ 1711 result = scaleImageIncrementally(src, targetWidth, 1712 targetHeight, scalingMethod, 1713 RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1714 } 1715 } 1716 1717 if (DEBUG) 1718 log(0, "Resized Image in %d ms", System.currentTimeMillis() - t); 1719 1720 // Apply any optional operations (if specified). 1721 if (ops != null && ops.length > 0) 1722 result = apply(result, ops); 1723 1724 return result; 1725 } 1726 1727 /** 1728 * Used to apply a {@link Rotation} and then <code>0</code> or more 1729 * {@link BufferedImageOp}s to a given image and return the result. 1730 * <p/> 1731 * <strong>TIP</strong>: This operation leaves the original <code>src</code> 1732 * image unmodified. If the caller is done with the <code>src</code> image 1733 * after getting the result of this operation, remember to call 1734 * {@link BufferedImage#flush()} on the <code>src</code> to free up native 1735 * resources and make it easier for the GC to collect the unused image. 1736 * 1737 * @param src 1738 * The image that will have the rotation applied to it. 1739 * @param rotation 1740 * The rotation that will be applied to the image. 1741 * @param ops 1742 * Zero or more optional image operations (e.g. sharpen, blur, 1743 * etc.) that can be applied to the final result before returning 1744 * the image. 1745 * 1746 * @return a new {@link BufferedImage} representing <code>src</code> rotated 1747 * by the given amount and any optional ops applied to it. 1748 * 1749 * @throws IllegalArgumentException 1750 * if <code>src</code> is <code>null</code>. 1751 * @throws IllegalArgumentException 1752 * if <code>rotation</code> is <code>null</code>. 1753 * @throws ImagingOpException 1754 * if one of the given {@link BufferedImageOp}s fails to apply. 1755 * These exceptions bubble up from the inside of most of the 1756 * {@link BufferedImageOp} implementations and are explicitly 1757 * defined on the imgscalr API to make it easier for callers to 1758 * catch the exception (if they are passing along optional ops 1759 * to be applied). imgscalr takes detailed steps to avoid the 1760 * most common pitfalls that will cause {@link BufferedImageOp}s 1761 * to fail, even when using straight forward JDK-image 1762 * operations. 1763 * 1764 * @see Rotation 1765 */ 1766 public static BufferedImage rotate(BufferedImage src, Rotation rotation, 1767 BufferedImageOp... ops) throws IllegalArgumentException, 1768 ImagingOpException { 1769 long t = System.currentTimeMillis(); 1770 1771 if (src == null) 1772 throw new IllegalArgumentException("src cannot be null"); 1773 if (rotation == null) 1774 throw new IllegalArgumentException("rotation cannot be null"); 1775 1776 if (DEBUG) 1777 log(0, "Rotating Image [%s]...", rotation); 1778 1779 /* 1780 * Setup the default width/height values from our image. 1781 * 1782 * In the case of a 90 or 270 (-90) degree rotation, these two values 1783 * flip-flop and we will correct those cases down below in the switch 1784 * statement. 1785 */ 1786 int newWidth = src.getWidth(); 1787 int newHeight = src.getHeight(); 1788 1789 /* 1790 * We create a transform per operation request as (oddly enough) it ends 1791 * up being faster for the VM to create, use and destroy these instances 1792 * than it is to re-use a single AffineTransform per-thread via the 1793 * AffineTransform.setTo(...) methods which was my first choice (less 1794 * object creation); after benchmarking this explicit case and looking 1795 * at just how much code gets run inside of setTo() I opted for a new AT 1796 * for every rotation. 1797 * 1798 * Besides the performance win, trying to safely reuse AffineTransforms 1799 * via setTo(...) would have required ThreadLocal instances to avoid 1800 * race conditions where two or more resize threads are manipulating the 1801 * same transform before applying it. 1802 * 1803 * Misusing ThreadLocals are one of the #1 reasons for memory leaks in 1804 * server applications and since we have no nice way to hook into the 1805 * init/destroy Servlet cycle or any other initialization cycle for this 1806 * library to automatically call ThreadLocal.remove() to avoid the 1807 * memory leak, it would have made using this library *safely* on the 1808 * server side much harder. 1809 * 1810 * So we opt for creating individual transforms per rotation op and let 1811 * the VM clean them up in a GC. I only clarify all this reasoning here 1812 * for anyone else reading this code and being tempted to reuse the AT 1813 * instances of performance gains; there aren't any AND you get a lot of 1814 * pain along with it. 1815 */ 1816 AffineTransform tx = new AffineTransform(); 1817 1818 switch (rotation) { 1819 case CW_90: 1820 /* 1821 * A 90 or -90 degree rotation will cause the height and width to 1822 * flip-flop from the original image to the rotated one. 1823 */ 1824 newWidth = src.getHeight(); 1825 newHeight = src.getWidth(); 1826 1827 // Reminder: newWidth == result.getHeight() at this point 1828 tx.translate(newWidth, 0); 1829 tx.rotate(Math.toRadians(90)); 1830 1831 break; 1832 1833 case CW_270: 1834 /* 1835 * A 90 or -90 degree rotation will cause the height and width to 1836 * flip-flop from the original image to the rotated one. 1837 */ 1838 newWidth = src.getHeight(); 1839 newHeight = src.getWidth(); 1840 1841 // Reminder: newHeight == result.getWidth() at this point 1842 tx.translate(0, newHeight); 1843 tx.rotate(Math.toRadians(-90)); 1844 break; 1845 1846 case CW_180: 1847 tx.translate(newWidth, newHeight); 1848 tx.rotate(Math.toRadians(180)); 1849 break; 1850 1851 case FLIP_HORZ: 1852 tx.translate(newWidth, 0); 1853 tx.scale(-1.0, 1.0); 1854 break; 1855 1856 case FLIP_VERT: 1857 tx.translate(0, newHeight); 1858 tx.scale(1.0, -1.0); 1859 break; 1860 } 1861 1862 // Create our target image we will render the rotated result to. 1863 BufferedImage result = createOptimalImage(src, newWidth, newHeight); 1864 Graphics2D g2d = (Graphics2D) result.createGraphics(); 1865 1866 /* 1867 * Render the resultant image to our new rotatedImage buffer, applying 1868 * the AffineTransform that we calculated above during rendering so the 1869 * pixels from the old position are transposed to the new positions in 1870 * the resulting image correctly. 1871 */ 1872 g2d.drawImage(src, tx, null); 1873 g2d.dispose(); 1874 1875 if (DEBUG) 1876 log(0, "Rotation Applied in %d ms, result [width=%d, height=%d]", 1877 System.currentTimeMillis() - t, result.getWidth(), 1878 result.getHeight()); 1879 1880 // Apply any optional operations (if specified). 1881 if (ops != null && ops.length > 0) 1882 result = apply(result, ops); 1883 1884 return result; 1885 } 1886 1887 /** 1888 * Used to write out a useful and well-formatted log message by any piece of 1889 * code inside of the imgscalr library. 1890 * <p/> 1891 * If a message cannot be logged (logging is disabled) then this method 1892 * returns immediately. 1893 * <p/> 1894 * <strong>NOTE</strong>: Because Java will auto-box primitive arguments 1895 * into Objects when building out the <code>params</code> array, care should 1896 * be taken not to call this method with primitive values unless 1897 * {@link Scalr#DEBUG} is <code>true</code>; otherwise the VM will be 1898 * spending time performing unnecessary auto-boxing calculations. 1899 * 1900 * @param depth 1901 * The indentation level of the log message. 1902 * @param message 1903 * The log message in <a href= 1904 * "http://download.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax" 1905 * >format string syntax</a> that will be logged. 1906 * @param params 1907 * The parameters that will be swapped into all the place holders 1908 * in the original messages before being logged. 1909 * 1910 * @see Scalr#LOG_PREFIX 1911 * @see Scalr#LOG_PREFIX_PROPERTY_NAME 1912 */ 1913 protected static void log(int depth, String message, Object... params) { 1914 if (Scalr.DEBUG) { 1915 System.out.print(Scalr.LOG_PREFIX); 1916 1917 for (int i = 0; i < depth; i++) 1918 System.out.print("\t"); 1919 1920 System.out.printf(message, params); 1921 System.out.println(); 1922 } 1923 } 1924 1925 /** 1926 * Used to create a {@link BufferedImage} with the most optimal RGB TYPE ( 1927 * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB} 1928 * ) capable of being rendered into from the given <code>src</code>. The 1929 * width and height of both images will be identical. 1930 * <p/> 1931 * This does not perform a copy of the image data from <code>src</code> into 1932 * the result image; see {@link #copyToOptimalImage(BufferedImage)} for 1933 * that. 1934 * <p/> 1935 * We force all rendering results into one of these two types, avoiding the 1936 * case where a source image is of an unsupported (or poorly supported) 1937 * format by Java2D causing the rendering result to end up looking terrible 1938 * (common with GIFs) or be totally corrupt (e.g. solid black image). 1939 * <p/> 1940 * Originally reported by Magnus Kvalheim from Movellas when scaling certain 1941 * GIF and PNG images. 1942 * 1943 * @param src 1944 * The source image that will be analyzed to determine the most 1945 * optimal image type it can be rendered into. 1946 * 1947 * @return a new {@link BufferedImage} representing the most optimal target 1948 * image type that <code>src</code> can be rendered into. 1949 * 1950 * @see <a 1951 * href="http://www.mail-archive.com/java2d-interest@capra.eng.sun.com/msg05621.html">How 1952 * Java2D handles poorly supported image types</a> 1953 * @see <a 1954 * href="http://code.google.com/p/java-image-scaling/source/browse/trunk/src/main/java/com/mortennobel/imagescaling/MultiStepRescaleOp.java">Thanks 1955 * to Morten Nobel for implementation hint</a> 1956 */ 1957 protected static BufferedImage createOptimalImage(BufferedImage src) { 1958 return createOptimalImage(src, src.getWidth(), src.getHeight()); 1959 } 1960 1961 /** 1962 * Used to create a {@link BufferedImage} with the given dimensions and the 1963 * most optimal RGB TYPE ( {@link BufferedImage#TYPE_INT_RGB} or 1964 * {@link BufferedImage#TYPE_INT_ARGB} ) capable of being rendered into from 1965 * the given <code>src</code>. 1966 * <p/> 1967 * This does not perform a copy of the image data from <code>src</code> into 1968 * the result image; see {@link #copyToOptimalImage(BufferedImage)} for 1969 * that. 1970 * <p/> 1971 * We force all rendering results into one of these two types, avoiding the 1972 * case where a source image is of an unsupported (or poorly supported) 1973 * format by Java2D causing the rendering result to end up looking terrible 1974 * (common with GIFs) or be totally corrupt (e.g. solid black image). 1975 * <p/> 1976 * Originally reported by Magnus Kvalheim from Movellas when scaling certain 1977 * GIF and PNG images. 1978 * 1979 * @param src 1980 * The source image that will be analyzed to determine the most 1981 * optimal image type it can be rendered into. 1982 * @param width 1983 * The width of the newly created resulting image. 1984 * @param height 1985 * The height of the newly created resulting image. 1986 * 1987 * @return a new {@link BufferedImage} representing the most optimal target 1988 * image type that <code>src</code> can be rendered into. 1989 * 1990 * @throws IllegalArgumentException 1991 * if <code>width</code> or <code>height</code> are < 0. 1992 * 1993 * @see <a 1994 * href="http://www.mail-archive.com/java2d-interest@capra.eng.sun.com/msg05621.html">How 1995 * Java2D handles poorly supported image types</a> 1996 * @see <a 1997 * href="http://code.google.com/p/java-image-scaling/source/browse/trunk/src/main/java/com/mortennobel/imagescaling/MultiStepRescaleOp.java">Thanks 1998 * to Morten Nobel for implementation hint</a> 1999 */ 2000 protected static BufferedImage createOptimalImage(BufferedImage src, 2001 int width, int height) throws IllegalArgumentException { 2002 if (width < 0 || height < 0) 2003 throw new IllegalArgumentException("width [" + width 2004 + "] and height [" + height + "] must be >= 0"); 2005 2006 return new BufferedImage( 2007 width, 2008 height, 2009 (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB 2010 : BufferedImage.TYPE_INT_ARGB)); 2011 } 2012 2013 /** 2014 * Used to copy a {@link BufferedImage} from a non-optimal type into a new 2015 * {@link BufferedImage} instance of an optimal type (RGB or ARGB). If 2016 * <code>src</code> is already of an optimal type, then it is returned 2017 * unmodified. 2018 * <p/> 2019 * This method is meant to be used by any calling code (imgscalr's or 2020 * otherwise) to convert any inbound image from a poorly supported image 2021 * type into the 2 most well-supported image types in Java2D ( 2022 * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB} 2023 * ) in order to ensure all subsequent graphics operations are performed as 2024 * efficiently and correctly as possible. 2025 * <p/> 2026 * When using Java2D to work with image types that are not well supported, 2027 * the results can be anything from exceptions bubbling up from the depths 2028 * of Java2D to images being completely corrupted and just returned as solid 2029 * black. 2030 * 2031 * @param src 2032 * The image to copy (if necessary) into an optimally typed 2033 * {@link BufferedImage}. 2034 * 2035 * @return a representation of the <code>src</code> image in an optimally 2036 * typed {@link BufferedImage}, otherwise <code>src</code> if it was 2037 * already of an optimal type. 2038 * 2039 * @throws IllegalArgumentException 2040 * if <code>src</code> is <code>null</code>. 2041 */ 2042 protected static BufferedImage copyToOptimalImage(BufferedImage src) 2043 throws IllegalArgumentException { 2044 if (src == null) 2045 throw new IllegalArgumentException("src cannot be null"); 2046 2047 // Calculate the type depending on the presence of alpha. 2048 int type = (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB 2049 : BufferedImage.TYPE_INT_ARGB); 2050 BufferedImage result = new BufferedImage(src.getWidth(), 2051 src.getHeight(), type); 2052 2053 // Render the src image into our new optimal source. 2054 Graphics g = result.getGraphics(); 2055 g.drawImage(src, 0, 0, null); 2056 g.dispose(); 2057 2058 return result; 2059 } 2060 2061 /** 2062 * Used to determine the scaling {@link Method} that is best suited for 2063 * scaling the image to the targeted dimensions. 2064 * <p/> 2065 * This method is intended to be used to select a specific scaling 2066 * {@link Method} when a {@link Method#AUTOMATIC} method is specified. This 2067 * method utilizes the {@link Scalr#THRESHOLD_QUALITY_BALANCED} and 2068 * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds when selecting which 2069 * method should be used by comparing the primary dimension (width or 2070 * height) against the threshold and seeing where the image falls. The 2071 * primary dimension is determined by looking at the orientation of the 2072 * image: landscape or square images use their width and portrait-oriented 2073 * images use their height. 2074 * 2075 * @param targetWidth 2076 * The target width for the scaled image. 2077 * @param targetHeight 2078 * The target height for the scaled image. 2079 * @param ratio 2080 * A height/width ratio used to determine the orientation of the 2081 * image so the primary dimension (width or height) can be 2082 * selected to test if it is greater than or less than a 2083 * particular threshold. 2084 * 2085 * @return the fastest {@link Method} suited for scaling the image to the 2086 * specified dimensions while maintaining a good-looking result. 2087 */ 2088 protected static Method determineScalingMethod(int targetWidth, 2089 int targetHeight, float ratio) { 2090 // Get the primary dimension based on the orientation of the image 2091 int length = (ratio <= 1 ? targetWidth : targetHeight); 2092 2093 // Default to speed 2094 Method result = Method.SPEED; 2095 2096 // Figure out which scalingMethod should be used 2097 if (length <= Scalr.THRESHOLD_QUALITY_BALANCED) 2098 result = Method.QUALITY; 2099 else if (length <= Scalr.THRESHOLD_BALANCED_SPEED) 2100 result = Method.BALANCED; 2101 2102 if (DEBUG) 2103 log(2, "AUTOMATIC scaling method selected: %s", result.name()); 2104 2105 return result; 2106 } 2107 2108 /** 2109 * Used to implement a straight-forward image-scaling operation using Java 2110 * 2D. 2111 * <p/> 2112 * This method uses the Oracle-encouraged method of 2113 * <code>Graphics2D.drawImage(...)</code> to scale the given image with the 2114 * given interpolation hint. 2115 * 2116 * @param src 2117 * The image that will be scaled. 2118 * @param targetWidth 2119 * The target width for the scaled image. 2120 * @param targetHeight 2121 * The target height for the scaled image. 2122 * @param interpolationHintValue 2123 * The {@link RenderingHints} interpolation value used to 2124 * indicate the method that {@link Graphics2D} should use when 2125 * scaling the image. 2126 * 2127 * @return the result of scaling the original <code>src</code> to the given 2128 * dimensions using the given interpolation method. 2129 */ 2130 protected static BufferedImage scaleImage(BufferedImage src, 2131 int targetWidth, int targetHeight, Object interpolationHintValue) { 2132 // Setup the rendering resources to match the source image's 2133 BufferedImage result = createOptimalImage(src, targetWidth, 2134 targetHeight); 2135 Graphics2D resultGraphics = result.createGraphics(); 2136 2137 // Scale the image to the new buffer using the specified rendering hint. 2138 resultGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 2139 interpolationHintValue); 2140 resultGraphics.drawImage(src, 0, 0, targetWidth, targetHeight, null); 2141 2142 // Just to be clean, explicitly dispose our temporary graphics object 2143 resultGraphics.dispose(); 2144 2145 // Return the scaled image to the caller. 2146 return result; 2147 } 2148 2149 /** 2150 * Used to implement Chris Campbell's incremental-scaling algorithm: <a 2151 * href="http://today.java.net/pub/a/today/2007/04/03/perils 2152 * -of-image-getscaledinstance 2153 * .html">http://today.java.net/pub/a/today/2007/04/03/perils 2154 * -of-image-getscaledinstance.html</a>. 2155 * <p/> 2156 * Modifications to the original algorithm are variable names and comments 2157 * added for clarity and the hard-coding of using BICUBIC interpolation as 2158 * well as the explicit "flush()" operation on the interim BufferedImage 2159 * instances to avoid resource leaking. 2160 * 2161 * @param src 2162 * The image that will be scaled. 2163 * @param targetWidth 2164 * The target width for the scaled image. 2165 * @param targetHeight 2166 * The target height for the scaled image. 2167 * @param scalingMethod 2168 * The scaling method specified by the user (or calculated by 2169 * imgscalr) to use for this incremental scaling operation. 2170 * @param interpolationHintValue 2171 * The {@link RenderingHints} interpolation value used to 2172 * indicate the method that {@link Graphics2D} should use when 2173 * scaling the image. 2174 * 2175 * @return an image scaled to the given dimensions using the given rendering 2176 * hint. 2177 */ 2178 protected static BufferedImage scaleImageIncrementally(BufferedImage src, 2179 int targetWidth, int targetHeight, Method scalingMethod, 2180 Object interpolationHintValue) { 2181 boolean hasReassignedSrc = false; 2182 int incrementCount = 0; 2183 int currentWidth = src.getWidth(); 2184 int currentHeight = src.getHeight(); 2185 2186 /* 2187 * The original QUALITY mode, representing Chris Campbell's algorithm, 2188 * is to step down by 1/2s every time when scaling the image 2189 * incrementally. Users pointed out that using this method to scale 2190 * images with noticeable straight lines left them really jagged in 2191 * smaller thumbnail format. 2192 * 2193 * After investigation it was discovered that scaling incrementally by 2194 * smaller increments was the ONLY way to make the thumbnail sized 2195 * images look less jagged and more accurate; almost matching the 2196 * accuracy of Mac's built in thumbnail generation which is the highest 2197 * quality resize I've come across (better than GIMP Lanczos3 and 2198 * Windows 7). 2199 * 2200 * A divisor of 7 was chose as using 5 still left some jaggedness in the 2201 * image while a divisor of 8 or higher made the resulting thumbnail too 2202 * soft; like our OP_ANTIALIAS convolve op had been forcibly applied to 2203 * the result even if the user didn't want it that soft. 2204 * 2205 * Using a divisor of 7 for the ULTRA_QUALITY seemed to be the sweet 2206 * spot. 2207 * 2208 * NOTE: Below when the actual fraction is used to calculate the small 2209 * portion to subtract from the current dimension, this is a 2210 * progressively smaller and smaller chunk. When the code was changed to 2211 * do a linear reduction of the image of equal steps for each 2212 * incremental resize (e.g. say 50px each time) the result was 2213 * significantly worse than the progressive approach used below; even 2214 * when a very high number of incremental steps (13) was tested. 2215 */ 2216 int fraction = (scalingMethod == Method.ULTRA_QUALITY ? 7 : 2); 2217 2218 do { 2219 int prevCurrentWidth = currentWidth; 2220 int prevCurrentHeight = currentHeight; 2221 2222 /* 2223 * If the current width is bigger than our target, cut it in half 2224 * and sample again. 2225 */ 2226 if (currentWidth > targetWidth) { 2227 currentWidth -= (currentWidth / fraction); 2228 2229 /* 2230 * If we cut the width too far it means we are on our last 2231 * iteration. Just set it to the target width and finish up. 2232 */ 2233 if (currentWidth < targetWidth) 2234 currentWidth = targetWidth; 2235 } 2236 2237 /* 2238 * If the current height is bigger than our target, cut it in half 2239 * and sample again. 2240 */ 2241 2242 if (currentHeight > targetHeight) { 2243 currentHeight -= (currentHeight / fraction); 2244 2245 /* 2246 * If we cut the height too far it means we are on our last 2247 * iteration. Just set it to the target height and finish up. 2248 */ 2249 2250 if (currentHeight < targetHeight) 2251 currentHeight = targetHeight; 2252 } 2253 2254 /* 2255 * Stop when we cannot incrementally step down anymore. 2256 * 2257 * This used to use a || condition, but that would cause problems 2258 * when using FIT_EXACT such that sometimes the width OR height 2259 * would not change between iterations, but the other dimension 2260 * would (e.g. resizing 500x500 to 500x250). 2261 * 2262 * Now changing this to an && condition requires that both 2263 * dimensions do not change between a resize iteration before we 2264 * consider ourselves done. 2265 */ 2266 if (prevCurrentWidth == currentWidth 2267 && prevCurrentHeight == currentHeight) 2268 break; 2269 2270 if (DEBUG) 2271 log(2, "Scaling from [%d x %d] to [%d x %d]", prevCurrentWidth, 2272 prevCurrentHeight, currentWidth, currentHeight); 2273 2274 // Render the incremental scaled image. 2275 BufferedImage incrementalImage = scaleImage(src, currentWidth, 2276 currentHeight, interpolationHintValue); 2277 2278 /* 2279 * Before re-assigning our interim (partially scaled) 2280 * incrementalImage to be the new src image before we iterate around 2281 * again to process it down further, we want to flush() the previous 2282 * src image IF (and only IF) it was one of our own temporary 2283 * BufferedImages created during this incremental down-sampling 2284 * cycle. If it wasn't one of ours, then it was the original 2285 * caller-supplied BufferedImage in which case we don't want to 2286 * flush() it and just leave it alone. 2287 */ 2288 if (hasReassignedSrc) 2289 src.flush(); 2290 2291 /* 2292 * Now treat our incremental partially scaled image as the src image 2293 * and cycle through our loop again to do another incremental 2294 * scaling of it (if necessary). 2295 */ 2296 src = incrementalImage; 2297 2298 /* 2299 * Keep track of us re-assigning the original caller-supplied source 2300 * image with one of our interim BufferedImages so we know when to 2301 * explicitly flush the interim "src" on the next cycle through. 2302 */ 2303 hasReassignedSrc = true; 2304 2305 // Track how many times we go through this cycle to scale the image. 2306 incrementCount++; 2307 } while (currentWidth != targetWidth || currentHeight != targetHeight); 2308 2309 if (DEBUG) 2310 log(2, "Incrementally Scaled Image in %d steps.", incrementCount); 2311 2312 /* 2313 * Once the loop has exited, the src image argument is now our scaled 2314 * result image that we want to return. 2315 */ 2316 return src; 2317 } 2318 }