├── .gitignore ├── CNAME ├── LICENSE ├── README ├── build.xml ├── index.html ├── pom.xml └── src ├── main └── java │ └── org │ └── imgscalr │ ├── AsyncScalr.java │ └── Scalr.java └── test ├── java └── org │ └── imgscalr │ ├── AbstractScalrTest.java │ ├── AllTests.java │ ├── AsyncScalrMultiThreadTest.java │ ├── AsyncScalrSingleThreadTest.java │ ├── ScalrApplyTest.java │ ├── ScalrCropTest.java │ ├── ScalrPadTest.java │ ├── ScalrResizeTest.java │ └── ScalrRotateTest.java └── resources ├── .gitignore └── org └── imgscalr ├── mr-t-thumbnail.jpg ├── mr-t.jpg ├── time-square-apply-1.png ├── time-square-apply-4.png ├── time-square-crop-wh.png ├── time-square-crop-xywh-ops.png ├── time-square-crop-xywh.png ├── time-square-pad-8-alpha-ops.png ├── time-square-pad-8-alpha.png ├── time-square-pad-8-red.png ├── time-square-pad-8.png ├── time-square-resize-320-fit-exact.png ├── time-square-resize-320-speed-fit-exact.png ├── time-square-resize-320-speed.png ├── time-square-resize-320.png ├── time-square-resize-640x480-speed.png ├── time-square-resize-640x480.png ├── time-square-resize-640x640-fit-exact.png ├── time-square-resize-640x640-speed-fit-exact-ops.png ├── time-square-resize-640x640-speed-fit-exact.png ├── time-square-rotate-180.png ├── time-square-rotate-270.png ├── time-square-rotate-90.png ├── time-square-rotate-horz-ops.png ├── time-square-rotate-horz.png ├── time-square-rotate-vert.png └── time-square.png /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /generated-comparisons 3 | /target 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | imgscalr.org -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | imgscalr - Java Image-Scaling Library 2 | 3 | Changelog 4 | --------- 5 | 4.2 6 | * Added support for a new Method.ULTRA_QUALITY scaling method. 7 | 8 | This new method uses 3.5x more incremental steps when scaling an image down 9 | than the QUALITY method, providing a much more accurate result. This is 10 | especially noticeable in thumbnails that have diagonal lines that get jagged 11 | during down-sizing with QUALITY or lower methods. 12 | 13 | The quality of the ULTRA_QUALITY scaling method is almost on par with the 14 | image resize functionality built into Mac OS X; that is to say it is better 15 | than GIMP's Lancsoz3 and Windows 7 built-in resize. 16 | 17 | https://github.com/rkalla/imgscalr/issues/61 18 | 19 | * Fixed subtle bug with incremental scaling and Mode.FIT_EXACT causing the 20 | incremental scaling to stop too soon resulting in the wrong-sized result 21 | image. 22 | 23 | The stop-condition for incremental scaling assumed that in every case the 24 | width AND height would be shrinking each iteration; when using 25 | Mode.FIT_EXACT this is not necessarily true as one dimension may not change 26 | at all or stop changing before another. 27 | 28 | https://github.com/rkalla/imgscalr/issues/65 29 | 30 | 4.1 31 | * Fixed NullPointerException that occurred when debugging was enabled 32 | https://github.com/rkalla/imgscalr/issues/60 33 | 34 | Required a patch-release due to the show-stopping nature of the bug. 35 | 36 | 4.0 37 | * [BREAKING] Package has changed from com.thebuzzmedia.imgscalr to 38 | org.imgscalr - I am sorry for the inconvenience of this, but this is 39 | necessary. There will be a family of imgscalr-based Java utilities coming 40 | out in the future (ExifTool is next) that will all be under this umbrella. 41 | 42 | * [BREAKING] Java 6 is now required for using imgscalr. 43 | 44 | The reason for this is because imgscalr includes specific types of 45 | ResizeOp and ColorConvertOps that actually segfault the latest Java 5 VM 46 | when applied, but run fine in Java 6 and 7. 47 | 48 | imgscalr cannot knowingly ship VM-segfaulting code that could would 49 | introduce a potentially devastating situation into client applications. 50 | 51 | This decision was not made lightly, but with Java 5 end-of-lifed and Java 6 52 | being out for 5 years, it seemed like a reasonable requirement. 53 | 54 | * [BREAKING] Rotation enum was totally redefined. All rotations were 55 | redefined in terms of 90,180,270 quadrant rotations as well as h/v FLIP. 56 | 57 | * [BREAKING] All resize(...) methods that accepted Rotation enums are 58 | removed. All graphic operations are now separate and discrete, but can be 59 | easily combined when multiple effects are wanted. 60 | 61 | * Added apply() support for applying an arbitrary list of BufferedImageOps 62 | SAFELY and efficiently working around all the bugs in the JDK pertaining 63 | to BufferedImageOps (also used internally when applying any optionally 64 | specified ops). 65 | 66 | * Added crop() support. 67 | 68 | * Added pad() support. 69 | 70 | * Added rotate() support. 71 | 72 | * All graphic operations (even new ones) were modified to allow the 73 | application of 1 or more BufferedImageOps to a final image result before 74 | returning it for convenience. 75 | 76 | * Support for all the new operations (apply, crop, pad, rotate) were all 77 | added to AsyncScalr so these operations can all be asynchronously performed 78 | as well. 79 | 80 | * Added support for horizontal and vertical flipping of the image via the 81 | Rotation enum and new rotate() method. 82 | 83 | * Added pre-defined OP_DARKER and OP_BRIGHTER operations that can be applied 84 | to any image to make them darker or brighter (respectively) by 10%. 85 | 86 | * Added Mode.FIT_EXACT to support (for the first time) scaling images 87 | forced into a specific given dimension instead of honoring the image's 88 | orientation and proportions automatically. 89 | 90 | * AsyncScalr's use of ExecutorService was rewritten; no more support for 91 | passing in custom ExecutorService implementations or modifying existing ones 92 | on the fly and having the class do something magic to them under the 93 | covers (that was bad) -- just extend the class and specify your own logic. 94 | 95 | * AsyncScalr can be easily customized now through a single method: 96 | 97 | - createService() 98 | OR 99 | - createService(ThreadFactory) 100 | 101 | * AsyncScalr provides two custom ThreadFactory implementations for subclasses 102 | to use if they want to customize the types of Threads generated and used 103 | internally for async scale operations. 104 | 105 | - DefaultThreadFactory creates default threads with all default settings. 106 | - ServerThreadFactory generates threads that are optimized to execute in 107 | a server environment (daemon threads w/ LOW_PRIORITY). 108 | 109 | * AsyncScalr.DEFAULT_THREAD_COUNT was removed and replaced with THREAD_COUNT 110 | that can be customized and set via system properties. 111 | 112 | * AsyncScalr.THREAD_COUNT's property name was separated into a String constant 113 | to make it easier to work with. 114 | 115 | * Simplified the resize() calls as a result of making all operations discrete; 116 | 8 duplicate methods accepting "rotation" arguments were removed. 117 | 118 | * Optimized the application of BufferedImageOps. 119 | 120 | * Fixed a bug in the application of BufferedImageOps which could have led 121 | to an ImagingOpException bubbling up from native Java2D or a corrupt (black) 122 | image for poorly supported image types. 123 | 124 | * Memory optimized the application of 2 or more BufferedImageOps (interim 125 | images are explicitly cleaned up just like in incremental scaling). 126 | 127 | * Optimized log() implementation to avoid StringBuilder creation and string 128 | concatenation. Should be significant run-time savings over time if you are 129 | running in an environment with debugging turned on. 130 | 131 | * Removed the identity-return functionality in each method to throw an 132 | exception instead of silently returning "src" unchanged. 133 | 134 | This was done intentionally to avoid users getting caught in the situation 135 | where they have code that automatically calls flush() on "src" after an 136 | imgscalr method has returned (assuming they NOW have a modified copy to work 137 | with). 138 | 139 | In the case of sending in invalid or null arguments, previously imgscalr 140 | would return "src" unchanged, which means the caller would be calling 141 | flush() on a perfectly good image they still needed and not a copy as was 142 | assumed by using imgscalr (And there would be no way to tell if imgscalr had 143 | created a copy or not without using an == check with EVERY returned image 144 | result). 145 | 146 | Instead, invalid or missing arguments passed to any imgscalr method are 147 | now considered an exception so the caller knows IMMEDIATELY when something 148 | is wrong and won't get magically different/unexpected behavior. 149 | 150 | * Exposed the potential for every method to fire an ImagingOpException if 151 | one of the BufferedImageOps fails to apply using the hardware-accelerated 152 | underlying Java2D code path. These exceptions were previously hidden in the 153 | guts of Java2D and could bubble up unexpectedly, now they are clearly defined 154 | directly on the imgscalr API so they can be cause and handled IF the caller 155 | wants or needs to do that when using custom BufferedImageOps. 156 | 157 | * Detailed notations about performance optimizations the caller can make to 158 | ensure their handling of images are as performant as possible were added to 159 | all the methods as a convenience. 160 | 161 | * Defined DEBUG system property name as a public constant that can be used 162 | to help avoid misspellings when trying to set debugging on. 163 | 164 | * Modified LOG_PREFIX so it can now be set via the "imgscalr.logPrefix" 165 | system property value now. 166 | 167 | * Rewrote imgscalr test suite to specifically test all discrete operations 168 | and all variations of the operations as well. 169 | 170 | * Added AllTests test suite so all tests can be easily run at one time to 171 | verify the release. 172 | 173 | * Rewrote Javadoc covering a lot of the return and exception conditions for 174 | all the methods to more clearly communicate what is happening inside the 175 | method and to the original images. 176 | 177 | 178 | 3.2 179 | * Added support for asynchronous & rate-limited scaling operations via the 180 | AsyncScalr class. 181 | 182 | The AsyncScalr class wraps the parent Scalr class and submits scale jobs to 183 | an internal ExecutorService. The executor service can be used to serialize 184 | and queue up scaling operations to avoid blowing the heap and overloading the 185 | underlying host on a busy, multi-user system (e.g. a web app running imgscalr). 186 | 187 | AsyncScalr by default uses a fixed-size ThreadPoolExecutor that can be modified 188 | at run time to any tuned level of threads the caller desires (default 2). The 189 | default settings are intended to be safe/efficient to use out of the box on 190 | most all systems. 191 | 192 | Additionally, AsyncScalr can be configured to use *any* ExecutorService 193 | implementation passed to it so callers have ultimate control over how the 194 | AsyncScalr processes jobs if they need/want it. 195 | 196 | Typically it is a good idea to roughly map # of Scaling Threads to the # of 197 | Cores on the server, especially on a server with plenty of memory and a large 198 | heap for the VM. 199 | 200 | If you are running inside of a smaller VM heap or lower-memory server (regardless 201 | of core count) you will want to limit the number of simultaneous scale operations 202 | so as not to saturate the heap during scaling when the images are read into 203 | internal BufferedImage instances in VM memory. 204 | 205 | * Added support for Rotation to the library. You can now specify the following 206 | rotations to be applied to your image: 207 | 208 | Rotation.NONE - No rotation. 209 | Rotation.CLOCKWISE - Clockwise (90 degrees to the right) rotation. 210 | Rotation.COUNTER_CLOCKWISE - Counter-clockwise (90 degrees to the left) rotation. 211 | Rotation.FLIP - Flip the image (180 degrees rotation). 212 | 213 | The rotation is performed as tightly and efficiently as possible, explicitly 214 | cleaning up temporary resources created during the operation. 215 | 216 | * API was simplified as duplicate methods without the vararg parameter were 217 | removed (these were effectively duplicates of the vararg methods make the 218 | API longer than it needed to be). 219 | 220 | * Corrected a multitude of incorrect Javadoc comments pertaining to @throws 221 | conditions. 222 | 223 | * Rewrote the method Javadoc. Manually reviewing uncovered too many copy-paste 224 | discrepancies that left out important information that would be helpful in 225 | a Javadoc popup in an IDE while using imgscalr. 226 | 227 | * All new code heavily commented. 228 | 229 | 3.1 230 | * You can now specify Mode.FIT_TO_WIDTH or Mode.FIT_TO_HEIGHT behaviors 231 | when resizing an image to get imgscalr to treat one dimension as the primary 232 | and recalculate the other dimension to best fit it, regardless of the image's 233 | orientation. Previously this was decided automatically for you by the 234 | orientation of the image. 235 | 236 | * resize methods now accept 0 or more BufferedImageOps as var-arg arguments. 237 | 238 | * Workaround for a 10-year-old JDK bug that causes RasterExceptions to get 239 | thrown from inside of Java2D when using BufferedImageOps was built directly 240 | into imgscalr so you don't have to worry about RasterExceptions. More 241 | info here: https://github.com/rkalla/imgscalr/issues/closed#issue/23 242 | 243 | * API was made more strict and an IAE is thrown if 'src' is null to any of 244 | the resize operations; a user reported that he spent a while debugging why 245 | "imgscalr wasn't working" only to find out it was silently returning due to 246 | a null source image. Would have been helpful if imgscalr had notified him of 247 | the issue immediately. 248 | 249 | 3.0 250 | * Big thanks to Magnus Kvalheim from http://www.movellas.com/ for help with 251 | this release! 252 | 253 | * Support for hardware-accelerated BufferedImageOp's was added to the library. 254 | You can now provide an optional BufferedImageOp to many of the methods in the 255 | imgscalr library and it will be applied to the resultant image before returning 256 | it. 257 | 258 | * Most common request was for imgscalr to apply an "anti-aliasing" filter to 259 | results before returning them; this was achieved by adding support for 260 | BufferedImageOps and providing a hand-tuned ConvolveOp to provide a good 261 | default that can be applied easily by folks that want the effect but don't 262 | want to learn all about BufferedImageOps and what "convolve" even means. 263 | 264 | * Speed/Balance/Quality THRESHOLD values were adjusted for more optimal results 265 | when relying on Method.AUTOMATIC to give good-looking results. 266 | 267 | * Javadoc was updated to clarify hardware acceleration behaviors. 268 | 269 | 2.1 270 | * Scaling of certain image types (and byte layouts) could result in very poor 271 | looking scaled images ("pixelated" look, discolored dithering, etc.). This was 272 | corrected by imgscalr forcibly scaling all source images into the most well-supported 273 | image types by Java2D, resulting in excellent scale result quality regardless 274 | of the Method specified. 275 | 276 | * The issue of scaling of poorly supported (by Java2D) image-types can lead 277 | to unexpectedly poor performance was also corrected as a side-effect of this 278 | because all source images are converted to the most commonly supported image 279 | type for Java2D. 280 | 281 | 2.0 282 | * API-break: resize(BufferedImage, Method, int, int, boolean, boolean) was removed and 283 | replaced by resize(BufferedImage, Method, int, int). 284 | 285 | * DEBUG system variable added; set 'imgscalr.debug' to true to trigger debugging output 286 | in the console. The boolean debug and elapsedTime arguments to the resize method 287 | have been removed. 288 | 289 | * New BALANCED method added. Provides a better result than SPEED faster than QUALITY. 290 | 291 | * Added 2 optimized thresholds (in pixels) that the API uses to select the best Method 292 | for scaling when the user specifies AUTOMATIC (or doesn't specify a method). This helps 293 | provide much better results out of the box by default and tightens up the performance of the 294 | API a bit more. 295 | 296 | * Image comparison generator utility (ComparisonGenerator test class) added. 297 | 298 | * Functional portions of API broken into static protected methods that can be 299 | easily overridden by implementors to customize the API without needing to rewrite 300 | the resize methods. 301 | 302 | * Consolidated 5 locations of duplicated rendering code into a single method (scaleImage). 303 | 304 | * Tightened up image scaling operation to do everything possible to avoid memory leaks (every native 305 | resource is disposed or released explicitly) 306 | 307 | * Detailed logging information integrated. If the 'imgscalr.debug' system property is 308 | true, the API outputs exactly what it's doing, what argument values it is processing and 309 | how long it is taking to do each scale operation. 310 | 311 | * When AUTOMATIC method is specified, the API is more intelligent about selecting 312 | SPEED, BALANCED or QUALITY based on the images primary dimension only (more accurate). 313 | 314 | * Copious amounts of Javadoc added to new methods, new code and existing code. 315 | 316 | Issues Resolved in 2.0: 317 | https://github.com/rkalla/imgscalr/issues/closed 318 | 319 | 1.2 320 | * Default proportional-scaling logic is more straight forward. If an image is 321 | landscape then width is the preferred dimension and the given height is ignored 322 | (and recalculated) and visa-versa if the image is portrait oriented. This gives 323 | much better "default behavior" results. 324 | 325 | * Added new convenience method resize(BufferedImage,int,int) 326 | 327 | * Modified build.xml to output Maven-friendly artifact names. 328 | 329 | Issues Resolved in 1.2: 330 | https://github.com/rkalla/imgscalr/issues/closed 331 | 332 | 1.1 333 | * Initial public release. 334 | 335 | 336 | License 337 | ------- 338 | This library is released under the Apache 2 License. See LICENSE. 339 | 340 | 341 | Description 342 | ----------- 343 | A class implementing performant (hardware accelerated), good-looking and 344 | intelligent image-scaling algorithms in pure Java 2D. This class implements the 345 | Java2D "best practices" when it comes to scaling images as well as Chris 346 | Campbell's incremental scaling algorithm proposed as the best method for 347 | down-sizes images for use as thumbnails (along with some additional minor 348 | optimizations). 349 | 350 | imgscalr also provides support for applying arbitrary BufferedImageOps against 351 | resultant images directly in the library. 352 | 353 | TIP: imgscalr provides a default "anti-aliasing" Op that will very lightly soften 354 | an image; this was a common request. Check Scalr.OP_ANTIALIAS 355 | 356 | TIP: All resizing operations maintain the original images proportions. 357 | 358 | TIP: You can ask imgscalr to fit an image to a specific width or height regardless 359 | of its orientation using a Mode argument. 360 | 361 | This class attempts to make scaling images in Java as simple as possible by providing 362 | a handful of approaches tuned for scaling as fast as possible or as best-looking 363 | as possible and the ability to let the algorithm choose for you to optionally create 364 | the best-looking scaled image as fast as possible without boring you with the details 365 | if you don't want them. 366 | 367 | 368 | Example 369 | ------- 370 | In the simplest use-case where an image needs to be scaled to proportionally fit 371 | a specific width (say 150px for a thumbnail) and the class is left to decide which 372 | method will look the best, the code would look like this: 373 | 374 | BufferedImage srcImage = ImageIO.read(...); // Load image 375 | BufferedImage scaledImage = Scalr.resize(srcImage, 150); // Scale image 376 | 377 | You could even flatten that out further if you simply wanted to scale the image 378 | and write out the scaled result immediately to a single line: 379 | 380 | ImageIO.write(Scalr.resize(ImageIO.read(...), 150)); 381 | 382 | 383 | Working with GIFs 384 | ----------------- 385 | Java's support for writing GIF is... terrible. In Java 5 is was patent-encumbered 386 | which made it mostly totally broken. In Java 6 the quantizer used to downsample 387 | colors to the most accurate 256 colors was fast but inaccurate, yielding 388 | poor-looking results. The handling of an alpha channel (transparency) while writing 389 | out GIF files (e.g. ImageIO.write(...)) was non-existent in Java 5 and in Java 6 390 | would remove the alpha channel completely and replace it with solid BLACK. 391 | 392 | In Java 7, support for writing out the alpha channel was added but unfortunately 393 | many of the remaining image operations (like ConvoleOp) still corrupt the 394 | resulting image when written out as a GIF. 395 | 396 | NOTE: Support for scaling animated GIFs don't work at all in any version. 397 | 398 | My recommendation for working with GIFs is as follows in order of preference: 399 | 400 | 1. Save the resulting BufferedImage from imgscalr as a PNG; it looks 401 | better as no quantizer needs to be used to cull down the color space and 402 | transparency is maintained. 403 | 404 | 2. If you mostly need GIF, check the resulting BufferedImage.getType() to see 405 | if it is TYPE_INT_RGB (no transparency) or TYPE_INT_ARGB (transparency); if the 406 | type is ARGB, then save the image as a PNG to maintain the alpha channel, if not, 407 | you can safely save it as a GIF. 408 | 409 | 3. If you MUST have GIF, upgrade your runtime to Java 7 and save your images as 410 | GIF. If you run Java 6, any GIF using transparency will have the transparent 411 | channel replaced with BLACK and in Java 5 I think the images will most all be 412 | corrupt/invalid. 413 | 414 | REMINDER: Even in Java 7, applying some BufferedImageOps (like ConvolveOp) to 415 | the scaled GIF before saving it totally corrupts it; so you would need to avoid 416 | that if you didn't want to save it as a PNG. If you decide to save as a PNG, you 417 | can apply any Ops you want. 418 | 419 | 420 | Troubleshooting 421 | --------------- 422 | Image-manipulation in Java can take more memory than the size of the source image 423 | because the image has to be "decoded" into raw ARGB bytes when loaded into the 424 | BufferedImage instance; fortunately on most platforms this is a hardware-accelerated 425 | operation by the video card. 426 | 427 | If you are running into OutOfMemoryExceptions when using this library (e.g. if you 428 | dealing with 10+ MB source images from an ultra-high-MP DSLR) try and up the 429 | heap size using the "-Xmx" command line argument to your Java process. 430 | 431 | An example of how to do this looks like: 432 | 433 | java -Xmx128m com.site.MyApp 434 | 435 | 436 | Reference 437 | --------- 438 | Chris Campbell Incremental Scaling - http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html 439 | 440 | 441 | Related Projects 442 | ---------------- 443 | ExifTool for Java - https://github.com/rkalla/exiftool 444 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 66 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 91 | 94 | 95 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redirecting to https://github.com/rkalla/imgscalr 4 | 5 | 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.sonatype.oss 5 | oss-parent 6 | 7 7 | 8 | 9 | 4.0.0 10 | org.imgscalr 11 | imgscalr-lib 12 | 4.3-SNAPSHOT 13 | jar 14 | 15 | imgscalr - A Java Image Scaling Library 16 | imgscalr is an simple and efficient best-practices image-scaling and manipulation library implemented in pure Java. 17 | http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/ 18 | 19 | 20 | ASF 2.0 21 | http://www.apache.org/licenses/LICENSE-2.0.txt 22 | repo 23 | 24 | 25 | 26 | 27 | scm:git:git@github.com:thebuzzmedia/imgscalr.git 28 | scm:git:git@github.com:thebuzzmedia/imgscalr.git 29 | git@github.com:thebuzzmedia/imgscalr.git 30 | 31 | 32 | 33 | GitHub 34 | https://github.com/thebuzzmedia/imgscalr/issues 35 | 36 | 37 | 38 | 39 | software@thebuzzmedia.com 40 | thebuzzmedia 41 | Riyad Kalla 42 | The Buzz Media, LLC 43 | https://github.com/thebuzzmedia 44 | http://www.thebuzzmedia.com/software/ 45 | 46 | 47 | 48 | 49 | The Buzz Media, LLC 50 | http://www.thebuzzmedia.com/software 51 | 52 | 53 | 54 | UTF-8 55 | 56 | 57 | 58 | 59 | junit 60 | junit 61 | 4.10 62 | jar 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-source-plugin 72 | 2.1.2 73 | 74 | 75 | attach-sources 76 | 77 | jar 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-javadoc-plugin 85 | 2.8 86 | 87 | 88 | attach-javadocs 89 | 90 | jar 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-assembly-plugin 98 | 2.2.1 99 | 100 | 101 | jar-with-dependencies 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/org/imgscalr/AsyncScalr.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.Color; 19 | import java.awt.image.BufferedImage; 20 | import java.awt.image.BufferedImageOp; 21 | import java.awt.image.ImagingOpException; 22 | import java.util.concurrent.Callable; 23 | import java.util.concurrent.ExecutorService; 24 | import java.util.concurrent.Executors; 25 | import java.util.concurrent.Future; 26 | import java.util.concurrent.ThreadFactory; 27 | import java.util.concurrent.ThreadPoolExecutor; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | 31 | import org.imgscalr.Scalr.Method; 32 | import org.imgscalr.Scalr.Mode; 33 | import org.imgscalr.Scalr.Rotation; 34 | 35 | /** 36 | * Class used to provide the asynchronous versions of all the methods defined in 37 | * {@link Scalr} for the purpose of efficiently handling large amounts of image 38 | * operations via a select number of processing threads asynchronously. 39 | *

40 | * Given that image-scaling operations, especially when working with large 41 | * images, can be very hardware-intensive (both CPU and memory), in large-scale 42 | * deployments (e.g. a busy web application) it becomes increasingly important 43 | * that the scale operations performed by imgscalr be manageable so as not to 44 | * fire off too many simultaneous operations that the JVM's heap explodes and 45 | * runs out of memory or pegs the CPU on the host machine, staving all other 46 | * running processes. 47 | *

48 | * Up until now it was left to the caller to implement their own serialization 49 | * or limiting logic to handle these use-cases. Given imgscalr's popularity in 50 | * web applications it was determined that this requirement be common enough 51 | * that it should be integrated directly into the imgscalr library for everyone 52 | * to benefit from. 53 | *

54 | * Every method in this class wraps the matching methods in the {@link Scalr} 55 | * class in new {@link Callable} instances that are submitted to an internal 56 | * {@link ExecutorService} for execution at a later date. A {@link Future} is 57 | * returned to the caller representing the task that is either currently 58 | * performing the scale operation or will at a future date depending on where it 59 | * is in the {@link ExecutorService}'s queue. {@link Future#get()} or 60 | * {@link Future#get(long, TimeUnit)} can be used to block on the 61 | * Future, waiting for the scale operation to complete and return 62 | * the resultant {@link BufferedImage} to the caller. 63 | *

64 | * This design provides the following features: 65 | *

79 | *

Performance

80 | * When tuning this class for optimal performance, benchmarking your particular 81 | * hardware is the best approach. For some rough guidelines though, there are 82 | * two resources you want to watch closely: 83 | *
    84 | *
  1. JVM Heap Memory (Assume physical machine memory is always sufficiently 85 | * large)
  2. 86 | *
  3. # of CPU Cores
  4. 87 | *
88 | * You never want to allocate more scaling threads than you have CPU cores and 89 | * on a sufficiently busy host where some of the cores may be busy running a 90 | * database or a web server, you will want to allocate even less scaling 91 | * threads. 92 | *

93 | * So as a maximum you would never want more scaling threads than CPU cores in 94 | * any situation and less so on a busy server. 95 | *

96 | * If you allocate more threads than you have available CPU cores, your scaling 97 | * operations will slow down as the CPU will spend a considerable amount of time 98 | * context-switching between threads on the same core trying to finish all the 99 | * tasks in parallel. You might still be tempted to do this because of the I/O 100 | * delay some threads will encounter reading images off disk, but when you do 101 | * your own benchmarking you'll likely find (as I did) that the actual disk I/O 102 | * necessary to pull the image data off disk is a much smaller portion of the 103 | * execution time than the actual scaling operations. 104 | *

105 | * If you are executing on a storage medium that is unexpectedly slow and I/O is 106 | * a considerable portion of the scaling operation (e.g. S3 or EBS volumes), 107 | * feel free to try using more threads than CPU cores to see if that helps; but 108 | * in most normal cases, it will only slow down all other parallel scaling 109 | * operations. 110 | *

111 | * As for memory, every time an image is scaled it is decoded into a 112 | * {@link BufferedImage} and stored in the JVM Heap space (decoded image 113 | * instances are always larger than the source images on-disk). For larger 114 | * images, that can use up quite a bit of memory. You will need to benchmark 115 | * your particular use-cases on your hardware to get an idea of where the sweet 116 | * spot is for this; if you are operating within tight memory bounds, you may 117 | * want to limit simultaneous scaling operations to 1 or 2 regardless of the 118 | * number of cores just to avoid having too many {@link BufferedImage} instances 119 | * in JVM Heap space at the same time. 120 | *

121 | * These are rough metrics and behaviors to give you an idea of how best to tune 122 | * this class for your deployment, but nothing can replacement writing a small 123 | * Java class that scales a handful of images in a number of different ways and 124 | * testing that directly on your deployment hardware. 125 | *

Resource Overhead

126 | * The {@link ExecutorService} utilized by this class won't be initialized until 127 | * one of the operation methods are called, at which point the 128 | * service will be instantiated for the first time and operation 129 | * queued up. 130 | *

131 | * More specifically, if you have no need for asynchronous image processing 132 | * offered by this class, you don't need to worry about wasted resources or 133 | * hanging/idle threads as they will never be created if you never use this 134 | * class. 135 | *

Cleaning up Service Threads

136 | * By default the {@link Thread}s created by the internal 137 | * {@link ThreadPoolExecutor} do not run in daemon mode; which 138 | * means they will block the host VM from exiting until they are explicitly shut 139 | * down in a client application; in a server application the container will shut 140 | * down the pool forcibly. 141 | *

142 | * If you have used the {@link AsyncScalr} class and are trying to shut down a 143 | * client application, you will need to call {@link #getService()} then 144 | * {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()} 145 | * to have the threads terminated; you may also want to look at the 146 | * {@link ExecutorService#awaitTermination(long, TimeUnit)} method if you'd like 147 | * to more closely monitor the shutting down process (and finalization of 148 | * pending scale operations). 149 | *

Reusing Shutdown AsyncScalr

150 | * If you have previously called shutdown on the underlying service 151 | * utilized by this class, subsequent calls to any of the operations this class 152 | * provides will invoke the internal {@link #checkService()} method which will 153 | * replace the terminated underlying {@link ExecutorService} with a new one via 154 | * the {@link #createService()} method. 155 | *

Custom Implementations

156 | * If a subclass wants to customize the {@link ExecutorService} or 157 | * {@link ThreadFactory} used under the covers, this can be done by overriding 158 | * the {@link #createService()} method which is invoked by this class anytime a 159 | * new {@link ExecutorService} is needed. 160 | *

161 | * By default the {@link #createService()} method delegates to the 162 | * {@link #createService(ThreadFactory)} method with a new instance of 163 | * {@link DefaultThreadFactory}. Either of these methods can be overridden and 164 | * customized easily if desired. 165 | *

166 | * TIP: A common customization to this class is to make the 167 | * {@link Thread}s generated by the underlying factory more server-friendly, in 168 | * which case the caller would want to use an instance of the 169 | * {@link ServerThreadFactory} when creating the new {@link ExecutorService}. 170 | *

171 | * This can be done in one line by overriding {@link #createService()} and 172 | * returning the result of: 173 | * return createService(new ServerThreadFactory()); 174 | *

175 | * By default this class uses an {@link ThreadPoolExecutor} internally to handle 176 | * execution of queued image operations. If a different type of 177 | * {@link ExecutorService} is desired, again, simply overriding the 178 | * {@link #createService()} method of choice is the right way to do that. 179 | * 180 | * @author Riyad Kalla (software@thebuzzmedia.com) 181 | * @since 3.2 182 | */ 183 | @SuppressWarnings("javadoc") 184 | public class AsyncScalr { 185 | /** 186 | * System property name used to set the number of threads the default 187 | * underlying {@link ExecutorService} will use to process async image 188 | * operations. 189 | *

190 | * Value is "imgscalr.async.threadCount". 191 | */ 192 | public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount"; 193 | 194 | /** 195 | * Number of threads the internal {@link ExecutorService} will use to 196 | * simultaneously execute scale requests. 197 | *

198 | * This value can be changed by setting the 199 | * imgscalr.async.threadCount system property (see 200 | * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0. 201 | *

202 | * Default value is 2. 203 | */ 204 | public static final int THREAD_COUNT = Integer.getInteger( 205 | THREAD_COUNT_PROPERTY_NAME, 2); 206 | 207 | /** 208 | * Initializer used to verify the THREAD_COUNT system property. 209 | */ 210 | static { 211 | if (THREAD_COUNT < 1) 212 | throw new RuntimeException("System property '" 213 | + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to " 214 | + THREAD_COUNT + ", but THREAD_COUNT must be > 0."); 215 | } 216 | 217 | protected static ExecutorService service; 218 | 219 | /** 220 | * Used to get access to the internal {@link ExecutorService} used by this 221 | * class to process scale operations. 222 | *

223 | * NOTE: You will need to explicitly shutdown any service 224 | * currently set on this class before the host JVM exits. 225 | *

226 | * You can call {@link ExecutorService#shutdown()} to wait for all scaling 227 | * operations to complete first or call 228 | * {@link ExecutorService#shutdownNow()} to kill any in-process operations 229 | * and purge all pending operations before exiting. 230 | *

231 | * Additionally you can use 232 | * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a 233 | * shutdown command to try and wait until the service has finished all 234 | * tasks. 235 | * 236 | * @return the current {@link ExecutorService} used by this class to process 237 | * scale operations. 238 | */ 239 | public static ExecutorService getService() { 240 | return service; 241 | } 242 | 243 | /** 244 | * @see Scalr#apply(BufferedImage, BufferedImageOp...) 245 | */ 246 | public static Future apply(final BufferedImage src, 247 | final BufferedImageOp... ops) throws IllegalArgumentException, 248 | ImagingOpException { 249 | checkService(); 250 | 251 | return service.submit(new Callable() { 252 | public BufferedImage call() throws Exception { 253 | return Scalr.apply(src, ops); 254 | } 255 | }); 256 | } 257 | 258 | /** 259 | * @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...) 260 | */ 261 | public static Future crop(final BufferedImage src, 262 | final int width, final int height, final BufferedImageOp... ops) 263 | throws IllegalArgumentException, ImagingOpException { 264 | checkService(); 265 | 266 | return service.submit(new Callable() { 267 | public BufferedImage call() throws Exception { 268 | return Scalr.crop(src, width, height, ops); 269 | } 270 | }); 271 | } 272 | 273 | /** 274 | * @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...) 275 | */ 276 | public static Future crop(final BufferedImage src, 277 | final int x, final int y, final int width, final int height, 278 | final BufferedImageOp... ops) throws IllegalArgumentException, 279 | ImagingOpException { 280 | checkService(); 281 | 282 | return service.submit(new Callable() { 283 | public BufferedImage call() throws Exception { 284 | return Scalr.crop(src, x, y, width, height, ops); 285 | } 286 | }); 287 | } 288 | 289 | /** 290 | * @see Scalr#pad(BufferedImage, int, BufferedImageOp...) 291 | */ 292 | public static Future pad(final BufferedImage src, 293 | final int padding, final BufferedImageOp... ops) 294 | throws IllegalArgumentException, ImagingOpException { 295 | checkService(); 296 | 297 | return service.submit(new Callable() { 298 | public BufferedImage call() throws Exception { 299 | return Scalr.pad(src, padding, ops); 300 | } 301 | }); 302 | } 303 | 304 | /** 305 | * @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...) 306 | */ 307 | public static Future pad(final BufferedImage src, 308 | final int padding, final Color color, final BufferedImageOp... ops) 309 | throws IllegalArgumentException, ImagingOpException { 310 | checkService(); 311 | 312 | return service.submit(new Callable() { 313 | public BufferedImage call() throws Exception { 314 | return Scalr.pad(src, padding, color, ops); 315 | } 316 | }); 317 | } 318 | 319 | /** 320 | * @see Scalr#resize(BufferedImage, int, BufferedImageOp...) 321 | */ 322 | public static Future resize(final BufferedImage src, 323 | final int targetSize, final BufferedImageOp... ops) 324 | throws IllegalArgumentException, ImagingOpException { 325 | checkService(); 326 | 327 | return service.submit(new Callable() { 328 | public BufferedImage call() throws Exception { 329 | return Scalr.resize(src, targetSize, ops); 330 | } 331 | }); 332 | } 333 | 334 | /** 335 | * @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...) 336 | */ 337 | public static Future resize(final BufferedImage src, 338 | final Method scalingMethod, final int targetSize, 339 | final BufferedImageOp... ops) throws IllegalArgumentException, 340 | ImagingOpException { 341 | checkService(); 342 | 343 | return service.submit(new Callable() { 344 | public BufferedImage call() throws Exception { 345 | return Scalr.resize(src, scalingMethod, targetSize, ops); 346 | } 347 | }); 348 | } 349 | 350 | /** 351 | * @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...) 352 | */ 353 | public static Future resize(final BufferedImage src, 354 | final Mode resizeMode, final int targetSize, 355 | final BufferedImageOp... ops) throws IllegalArgumentException, 356 | ImagingOpException { 357 | checkService(); 358 | 359 | return service.submit(new Callable() { 360 | public BufferedImage call() throws Exception { 361 | return Scalr.resize(src, resizeMode, targetSize, ops); 362 | } 363 | }); 364 | } 365 | 366 | /** 367 | * @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...) 368 | */ 369 | public static Future resize(final BufferedImage src, 370 | final Method scalingMethod, final Mode resizeMode, 371 | final int targetSize, final BufferedImageOp... ops) 372 | throws IllegalArgumentException, ImagingOpException { 373 | checkService(); 374 | 375 | return service.submit(new Callable() { 376 | public BufferedImage call() throws Exception { 377 | return Scalr.resize(src, scalingMethod, resizeMode, targetSize, 378 | ops); 379 | } 380 | }); 381 | } 382 | 383 | /** 384 | * @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...) 385 | */ 386 | public static Future resize(final BufferedImage src, 387 | final int targetWidth, final int targetHeight, 388 | final BufferedImageOp... ops) throws IllegalArgumentException, 389 | ImagingOpException { 390 | checkService(); 391 | 392 | return service.submit(new Callable() { 393 | public BufferedImage call() throws Exception { 394 | return Scalr.resize(src, targetWidth, targetHeight, ops); 395 | } 396 | }); 397 | } 398 | 399 | /** 400 | * @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...) 401 | */ 402 | public static Future resize(final BufferedImage src, 403 | final Method scalingMethod, final int targetWidth, 404 | final int targetHeight, final BufferedImageOp... ops) { 405 | checkService(); 406 | 407 | return service.submit(new Callable() { 408 | public BufferedImage call() throws Exception { 409 | return Scalr.resize(src, scalingMethod, targetWidth, 410 | targetHeight, ops); 411 | } 412 | }); 413 | } 414 | 415 | /** 416 | * @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...) 417 | */ 418 | public static Future resize(final BufferedImage src, 419 | final Mode resizeMode, final int targetWidth, 420 | final int targetHeight, final BufferedImageOp... ops) 421 | throws IllegalArgumentException, ImagingOpException { 422 | checkService(); 423 | 424 | return service.submit(new Callable() { 425 | public BufferedImage call() throws Exception { 426 | return Scalr.resize(src, resizeMode, targetWidth, targetHeight, 427 | ops); 428 | } 429 | }); 430 | } 431 | 432 | /** 433 | * @see Scalr#resize(BufferedImage, Method, Mode, int, int, 434 | * BufferedImageOp...) 435 | */ 436 | public static Future resize(final BufferedImage src, 437 | final Method scalingMethod, final Mode resizeMode, 438 | final int targetWidth, final int targetHeight, 439 | final BufferedImageOp... ops) throws IllegalArgumentException, 440 | ImagingOpException { 441 | checkService(); 442 | 443 | return service.submit(new Callable() { 444 | public BufferedImage call() throws Exception { 445 | return Scalr.resize(src, scalingMethod, resizeMode, 446 | targetWidth, targetHeight, ops); 447 | } 448 | }); 449 | } 450 | 451 | /** 452 | * @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...) 453 | */ 454 | public static Future rotate(final BufferedImage src, 455 | final Rotation rotation, final BufferedImageOp... ops) 456 | throws IllegalArgumentException, ImagingOpException { 457 | checkService(); 458 | 459 | return service.submit(new Callable() { 460 | public BufferedImage call() throws Exception { 461 | return Scalr.rotate(src, rotation, ops); 462 | } 463 | }); 464 | } 465 | 466 | protected static ExecutorService createService() { 467 | return createService(new DefaultThreadFactory()); 468 | } 469 | 470 | protected static ExecutorService createService(ThreadFactory factory) 471 | throws IllegalArgumentException { 472 | if (factory == null) 473 | throw new IllegalArgumentException("factory cannot be null"); 474 | 475 | return Executors.newFixedThreadPool(THREAD_COUNT, factory); 476 | } 477 | 478 | /** 479 | * Used to verify that the underlying service points at an 480 | * active {@link ExecutorService} instance that can be used by this class. 481 | *

482 | * If service is null, has been shutdown or 483 | * terminated then this method will replace it with a new 484 | * {@link ExecutorService} by calling the {@link #createService()} method 485 | * and assigning the returned value to service. 486 | *

487 | * Any subclass that wants to customize the {@link ExecutorService} or 488 | * {@link ThreadFactory} used internally by this class should override the 489 | * {@link #createService()}. 490 | */ 491 | protected static void checkService() { 492 | if (service == null || service.isShutdown() || service.isTerminated()) { 493 | /* 494 | * If service was shutdown or terminated, assigning a new value will 495 | * free the reference to the instance, allowing it to be GC'ed when 496 | * it is done shutting down (assuming it hadn't already). 497 | */ 498 | service = createService(); 499 | } 500 | } 501 | 502 | /** 503 | * Default {@link ThreadFactory} used by the internal 504 | * {@link ExecutorService} to creates execution {@link Thread}s for image 505 | * scaling. 506 | *

507 | * More or less a copy of the hidden class backing the 508 | * {@link Executors#defaultThreadFactory()} method, but exposed here to make 509 | * it easier for implementors to extend and customize. 510 | * 511 | * @author Doug Lea 512 | * @author Riyad Kalla (software@thebuzzmedia.com) 513 | * @since 4.0 514 | */ 515 | protected static class DefaultThreadFactory implements ThreadFactory { 516 | protected static final AtomicInteger poolNumber = new AtomicInteger(1); 517 | 518 | protected final ThreadGroup group; 519 | protected final AtomicInteger threadNumber = new AtomicInteger(1); 520 | protected final String namePrefix; 521 | 522 | DefaultThreadFactory() { 523 | SecurityManager manager = System.getSecurityManager(); 524 | 525 | /* 526 | * Determine the group that threads created by this factory will be 527 | * in. 528 | */ 529 | group = (manager == null ? Thread.currentThread().getThreadGroup() 530 | : manager.getThreadGroup()); 531 | 532 | /* 533 | * Define a common name prefix for the threads created by this 534 | * factory. 535 | */ 536 | namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; 537 | } 538 | 539 | /** 540 | * Used to create a {@link Thread} capable of executing the given 541 | * {@link Runnable}. 542 | *

543 | * Thread created by this factory are utilized by the parent 544 | * {@link ExecutorService} when processing queued up scale operations. 545 | */ 546 | public Thread newThread(Runnable r) { 547 | /* 548 | * Create a new thread in our specified group with a meaningful 549 | * thread name so it is easy to identify. 550 | */ 551 | Thread thread = new Thread(group, r, namePrefix 552 | + threadNumber.getAndIncrement(), 0); 553 | 554 | // Configure thread according to class or subclass 555 | thread.setDaemon(false); 556 | thread.setPriority(Thread.NORM_PRIORITY); 557 | 558 | return thread; 559 | } 560 | } 561 | 562 | /** 563 | * An extension of the {@link DefaultThreadFactory} class that makes two 564 | * changes to the execution {@link Thread}s it generations: 565 | *

    566 | *
  1. Threads are set to be daemon threads instead of user threads.
  2. 567 | *
  3. Threads execute with a priority of {@link Thread#MIN_PRIORITY} to 568 | * make them more compatible with server environment deployments.
  4. 569 | *
570 | * This class is provided as a convenience for subclasses to use if they 571 | * want this (common) customization to the {@link Thread}s used internally 572 | * by {@link AsyncScalr} to process images, but don't want to have to write 573 | * the implementation. 574 | * 575 | * @author Riyad Kalla (software@thebuzzmedia.com) 576 | * @since 4.0 577 | */ 578 | protected static class ServerThreadFactory extends DefaultThreadFactory { 579 | /** 580 | * Overridden to set daemon property to true 581 | * and decrease the priority of the new thread to 582 | * {@link Thread#MIN_PRIORITY} before returning it. 583 | */ 584 | @Override 585 | public Thread newThread(Runnable r) { 586 | Thread thread = super.newThread(r); 587 | 588 | thread.setDaemon(true); 589 | thread.setPriority(Thread.MIN_PRIORITY); 590 | 591 | return thread; 592 | } 593 | } 594 | } 595 | -------------------------------------------------------------------------------- /src/main/java/org/imgscalr/Scalr.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.Color; 19 | import java.awt.Graphics; 20 | import java.awt.Graphics2D; 21 | import java.awt.Image; 22 | import java.awt.RenderingHints; 23 | import java.awt.Transparency; 24 | import java.awt.color.ColorSpace; 25 | import java.awt.geom.AffineTransform; 26 | import java.awt.geom.Rectangle2D; 27 | import java.awt.image.AreaAveragingScaleFilter; 28 | import java.awt.image.BufferedImage; 29 | import java.awt.image.BufferedImageOp; 30 | import java.awt.image.ColorConvertOp; 31 | import java.awt.image.ColorModel; 32 | import java.awt.image.ConvolveOp; 33 | import java.awt.image.ImagingOpException; 34 | import java.awt.image.IndexColorModel; 35 | import java.awt.image.Kernel; 36 | import java.awt.image.RasterFormatException; 37 | import java.awt.image.RescaleOp; 38 | 39 | import javax.imageio.ImageIO; 40 | 41 | /** 42 | * Class used to implement performant, high-quality and intelligent image 43 | * scaling and manipulation algorithms in native Java 2D. 44 | *

45 | * This class utilizes the Java2D "best practices" for image manipulation, 46 | * ensuring that all operations (even most user-provided {@link BufferedImageOp} 47 | * s) are hardware accelerated if provided by the platform and host-VM. 48 | *

49 | *

Image Quality

50 | * This class implements a few different methods for scaling an image, providing 51 | * either the best-looking result, the fastest result or a balanced result 52 | * between the two depending on the scaling hint provided (see {@link Method}). 53 | *

54 | * This class also implements an optimized version of the incremental scaling 55 | * algorithm presented by Chris Campbell in his Perils of 57 | * Image.getScaledInstance() article in order to give the best-looking image 58 | * resize results (e.g. generating thumbnails that aren't blurry or jagged). 59 | *

60 | * The results generated by imgscalr using this method, as compared to a single 61 | * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} scale operation look much 62 | * better, especially when using the {@link Method#ULTRA_QUALITY} method. 63 | *

64 | * Only when scaling using the {@link Method#AUTOMATIC} method will this class 65 | * look at the size of the image before selecting an approach to scaling the 66 | * image. If {@link Method#QUALITY} is specified, the best-looking algorithm 67 | * possible is always used. 68 | *

69 | * Minor modifications are made to Campbell's original implementation in the 70 | * form of: 71 | *

    72 | *
  1. Instead of accepting a user-supplied interpolation method, 73 | * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} interpolation is always 74 | * used. This was done after A/B comparison testing with large images 75 | * down-scaled to thumbnail sizes showed noticeable "blurring" when BILINEAR 76 | * interpolation was used. Given that Campbell's algorithm is only used in 77 | * QUALITY mode when down-scaling, it was determined that the user's expectation 78 | * of a much less blurry picture would require that BICUBIC be the default 79 | * interpolation in order to meet the QUALITY expectation.
  2. 80 | *
  3. After each iteration of the do-while loop that incrementally scales the 81 | * source image down, an explicit effort is made to call 82 | * {@link BufferedImage#flush()} on the interim temporary {@link BufferedImage} 83 | * instances created by the algorithm in an attempt to ensure a more complete GC 84 | * cycle by the VM when cleaning up the temporary instances (this is in addition 85 | * to disposing of the temporary {@link Graphics2D} references as well).
  4. 86 | *
  5. Extensive comments have been added to increase readability of the code.
  6. 87 | *
  7. Variable names have been expanded to increase readability of the code.
  8. 88 | *
89 | *

90 | * NOTE: This class does not call {@link BufferedImage#flush()} 91 | * on any of the source images passed in by calling code; it is up to 92 | * the original caller to dispose of their source images when they are no longer 93 | * needed so the VM can most efficiently GC them. 94 | *

Image Proportions

95 | * All scaling operations implemented by this class maintain the proportions of 96 | * the original image unless a mode of {@link Mode#FIT_EXACT} is specified; in 97 | * which case the orientation and proportion of the source image is ignored and 98 | * the image is stretched (if necessary) to fit the exact dimensions given. 99 | *

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 | *

    104 | *
  1. If the image is LANDSCAPE-oriented or SQUARE, treat the 105 | * targetWidth as the primary dimension and re-calculate the 106 | * targetHeight regardless of what is passed in.
  2. 107 | *
  3. If image is PORTRAIT-oriented, treat the targetHeight as the 108 | * primary dimension and re-calculate the targetWidth regardless of 109 | * what is passed in.
  4. 110 | *
  5. If a {@link Mode} value of {@link Mode#FIT_TO_WIDTH} or 111 | * {@link Mode#FIT_TO_HEIGHT} is passed in to the resize 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).
  6. 118 | *
119 | *

Optimized Image Handling

120 | * Java2D provides support for a number of different image types defined as 121 | * BufferedImage.TYPE_* variables, unfortunately not all image 122 | * types are supported equally in the Java2D rendering pipeline. 123 | *

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 of the same type 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 | *

131 | * imgscalr specifically accounts for and automatically hands 132 | * ALL of these pain points for you internally by shuffling all 133 | * images into one of two types: 134 | *

    135 | *
  1. {@link BufferedImage#TYPE_INT_RGB}
  2. 136 | *
  3. {@link BufferedImage#TYPE_INT_ARGB}
  4. 137 | *
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 here. 143 | *

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 | *

GIF Transparency

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 | *

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 | *

166 | * Workaround: 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 | *

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 | *

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 | *

178 | * More can be read about the issue here and here. 182 | *

Thread Safety

183 | * The {@link Scalr} class is thread-safe (as all the methods 184 | * are static); 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 | *

Logging

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 System.out via the printf 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 | *

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 | *

207 | * Value is "imgscalr.debug". 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 | *

214 | * Value is "imgscalr.logPrefix". 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 | * "imgscalr.debug" system property to true. This 221 | * value will be false if the "imgscalr.debug" 222 | * system property is undefined or set to false. 223 | *

224 | * This property can be set on startup with:
225 | * 226 | * -Dimgscalr.debug=true 227 | * 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 | *

231 | * Default value is false. 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 | *

240 | * This property can be set on startup with:
241 | * 242 | * -Dimgscalr.logPrefix=<YOUR PREFIX HERE> 243 | * 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 | *

247 | * Default value is "[imgscalr] " (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 | *

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 | *

264 | * This ConvolveOp uses a 3x3 kernel with the values: 265 | * 266 | * 267 | * 268 | * 269 | * 270 | * 271 | * 272 | * 273 | * 274 | * 275 | * 276 | * 277 | * 278 | * 279 | * 280 | * 281 | *
.0f.08f.0f
.08f.68f.08f
.0f.08f.0f
282 | *

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 | *

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 | *

Performance

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, sun.awt.image.ImagingLib. 303 | *

Known Issues

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 | *

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 | *

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 | *

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 | *

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 QUALITY, 371 | * BALANCED or SPEED scaling algorithms. 372 | *

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 | *

441 | * It is recommended that you use one of the other FIT_TO 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 largest image that fit within the bounding box, 450 | * without cropping or distortion, retaining the original proportions. 451 | */ 452 | BEST_FIT_BOTH, 453 | /** 454 | * Used to indicate that the scaling implementation should calculate 455 | * dimensions for the resultant image that best-fit within the given 456 | * width, regardless of the orientation of the image. 457 | */ 458 | FIT_TO_WIDTH, 459 | /** 460 | * Used to indicate that the scaling implementation should calculate 461 | * dimensions for the resultant image that best-fit within the given 462 | * height, regardless of the orientation of the image. 463 | */ 464 | FIT_TO_HEIGHT; 465 | } 466 | 467 | /** 468 | * Used to define the different types of rotations that can be applied to an 469 | * image during a resize operation. 470 | * 471 | * @author Riyad Kalla (software@thebuzzmedia.com) 472 | * @since 3.2 473 | */ 474 | public static enum Rotation { 475 | /** 476 | * 90-degree, clockwise rotation (to the right). This is equivalent to a 477 | * quarter-turn of the image to the right; moving the picture on to its 478 | * right side. 479 | */ 480 | CW_90, 481 | /** 482 | * 180-degree, clockwise rotation (to the right). This is equivalent to 483 | * 1 half-turn of the image to the right; rotating the picture around 484 | * until it is upside down from the original position. 485 | */ 486 | CW_180, 487 | /** 488 | * 270-degree, clockwise rotation (to the right). This is equivalent to 489 | * a quarter-turn of the image to the left; moving the picture on to its 490 | * left side. 491 | */ 492 | CW_270, 493 | /** 494 | * Flip the image horizontally by reflecting it around the y axis. 495 | *

496 | * This is not a standard rotation around a center point, but instead 497 | * creates the mirrored reflection of the image horizontally. 498 | *

499 | * More specifically, the vertical orientation of the image stays the 500 | * same (the top stays on top, and the bottom on bottom), but the right 501 | * and left sides flip. This is different than a standard rotation where 502 | * the top and bottom would also have been flipped. 503 | */ 504 | FLIP_HORZ, 505 | /** 506 | * Flip the image vertically by reflecting it around the x axis. 507 | *

508 | * This is not a standard rotation around a center point, but instead 509 | * creates the mirrored reflection of the image vertically. 510 | *

511 | * More specifically, the horizontal orientation of the image stays the 512 | * same (the left stays on the left and the right stays on the right), 513 | * but the top and bottom sides flip. This is different than a standard 514 | * rotation where the left and right would also have been flipped. 515 | */ 516 | FLIP_VERT; 517 | } 518 | 519 | /** 520 | * Threshold (in pixels) at which point the scaling operation using the 521 | * {@link Method#AUTOMATIC} method will decide if a {@link Method#BALANCED} 522 | * method will be used (if smaller than or equal to threshold) or a 523 | * {@link Method#SPEED} method will be used (if larger than threshold). 524 | *

525 | * The bigger the image is being scaled to, the less noticeable degradations 526 | * in the image becomes and the faster algorithms can be selected. 527 | *

528 | * The value of this threshold (1600) was chosen after visual, by-hand, A/B 529 | * testing between different types of images scaled with this library; both 530 | * photographs and screenshots. It was determined that images below this 531 | * size need to use a {@link Method#BALANCED} scale method to look decent in 532 | * most all cases while using the faster {@link Method#SPEED} method for 533 | * images bigger than this threshold showed no noticeable degradation over a 534 | * BALANCED scale. 535 | */ 536 | public static final int THRESHOLD_BALANCED_SPEED = 1600; 537 | 538 | /** 539 | * Threshold (in pixels) at which point the scaling operation using the 540 | * {@link Method#AUTOMATIC} method will decide if a {@link Method#QUALITY} 541 | * method will be used (if smaller than or equal to threshold) or a 542 | * {@link Method#BALANCED} method will be used (if larger than threshold). 543 | *

544 | * The bigger the image is being scaled to, the less noticeable degradations 545 | * in the image becomes and the faster algorithms can be selected. 546 | *

547 | * The value of this threshold (800) was chosen after visual, by-hand, A/B 548 | * testing between different types of images scaled with this library; both 549 | * photographs and screenshots. It was determined that images below this 550 | * size need to use a {@link Method#QUALITY} scale method to look decent in 551 | * most all cases while using the faster {@link Method#BALANCED} method for 552 | * images bigger than this threshold showed no noticeable degradation over a 553 | * QUALITY scale. 554 | */ 555 | public static final int THRESHOLD_QUALITY_BALANCED = 800; 556 | 557 | /** 558 | * Used to apply, in the order given, 1 or more {@link BufferedImageOp}s to 559 | * a given {@link BufferedImage} and return the result. 560 | *

561 | * Feature: This implementation works around a 563 | * decade-old JDK bug that can cause a {@link RasterFormatException} 564 | * when applying a perfectly valid {@link BufferedImageOp}s to images. 565 | *

566 | * Feature: This implementation also works around 567 | * {@link BufferedImageOp}s failing to apply and throwing 568 | * {@link ImagingOpException}s when run against a src image 569 | * type that is poorly supported. Unfortunately using {@link ImageIO} and 570 | * standard Java methods to load images provides no consistency in getting 571 | * images in well-supported formats. This method automatically accounts and 572 | * corrects for all those problems (if necessary). 573 | *

574 | * It is recommended you always use this method to apply any 575 | * {@link BufferedImageOp}s instead of relying on directly using the 576 | * {@link BufferedImageOp#filter(BufferedImage, BufferedImage)} method. 577 | *

578 | * Performance: Not all {@link BufferedImageOp}s are 579 | * hardware accelerated operations, but many of the most popular (like 580 | * {@link ConvolveOp}) are. For more information on if your image op is 581 | * hardware accelerated or not, check the source code of the underlying JDK 582 | * class that actually executes the Op code, sun.awt.image.ImagingLib. 585 | *

586 | * TIP: This operation leaves the original src 587 | * image unmodified. If the caller is done with the src image 588 | * after getting the result of this operation, remember to call 589 | * {@link BufferedImage#flush()} on the src to free up native 590 | * resources and make it easier for the GC to collect the unused image. 591 | * 592 | * @param src 593 | * The image that will have the ops applied to it. 594 | * @param ops 595 | * 1 or more ops to apply to the image. 596 | * 597 | * @return a new {@link BufferedImage} that represents the src 598 | * with all the given operations applied to it. 599 | * 600 | * @throws IllegalArgumentException 601 | * if src is null. 602 | * @throws IllegalArgumentException 603 | * if ops is null or empty. 604 | * @throws ImagingOpException 605 | * if one of the given {@link BufferedImageOp}s fails to apply. 606 | * These exceptions bubble up from the inside of most of the 607 | * {@link BufferedImageOp} implementations and are explicitly 608 | * defined on the imgscalr API to make it easier for callers to 609 | * catch the exception (if they are passing along optional ops 610 | * to be applied). imgscalr takes detailed steps to avoid the 611 | * most common pitfalls that will cause {@link BufferedImageOp}s 612 | * to fail, even when using straight forward JDK-image 613 | * operations. 614 | */ 615 | public static BufferedImage apply(BufferedImage src, BufferedImageOp... ops) 616 | throws IllegalArgumentException, ImagingOpException { 617 | long t = -1; 618 | if (DEBUG) 619 | t = System.currentTimeMillis(); 620 | 621 | if (src == null) 622 | throw new IllegalArgumentException("src cannot be null"); 623 | if (ops == null || ops.length == 0) 624 | throw new IllegalArgumentException("ops cannot be null or empty"); 625 | 626 | int type = src.getType(); 627 | 628 | /* 629 | * Ensure the src image is in the best supported image type before we 630 | * continue, otherwise it is possible our calls below to getBounds2D and 631 | * certainly filter(...) may fail if not. 632 | * 633 | * Java2D makes an attempt at applying most BufferedImageOps using 634 | * hardware acceleration via the ImagingLib internal library. 635 | * 636 | * Unfortunately may of the BufferedImageOp are written to simply fail 637 | * with an ImagingOpException if the operation cannot be applied with no 638 | * additional information about what went wrong or attempts at 639 | * re-applying it in different ways. 640 | * 641 | * This is assuming the failing BufferedImageOp even returns a null 642 | * image after failing to apply; some simply return a corrupted/black 643 | * image that result in no exception and it is up to the user to 644 | * discover this. 645 | * 646 | * In internal testing, EVERY failure I've ever seen was the result of 647 | * the source image being in a poorly-supported BufferedImage Type like 648 | * BGR or ABGR (even though it was loaded with ImageIO). 649 | * 650 | * To avoid this nasty/stupid surprise with BufferedImageOps, we always 651 | * ensure that the src image starts in an optimally supported format 652 | * before we try and apply the filter. 653 | */ 654 | if (!(type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB)) 655 | src = copyToOptimalImage(src); 656 | 657 | if (DEBUG) 658 | log(0, "Applying %d BufferedImageOps...", ops.length); 659 | 660 | boolean hasReassignedSrc = false; 661 | 662 | for (int i = 0; i < ops.length; i++) { 663 | long subT = -1; 664 | if (DEBUG) 665 | subT = System.currentTimeMillis(); 666 | BufferedImageOp op = ops[i]; 667 | 668 | // Skip null ops instead of throwing an exception. 669 | if (op == null) 670 | continue; 671 | 672 | if (DEBUG) 673 | log(1, "Applying BufferedImageOp [class=%s, toString=%s]...", 674 | op.getClass(), op.toString()); 675 | 676 | /* 677 | * Must use op.getBounds instead of src.getWidth and src.getHeight 678 | * because we are trying to create an image big enough to hold the 679 | * result of this operation (which may be to scale the image 680 | * smaller), in that case the bounds reported by this op and the 681 | * bounds reported by the source image will be different. 682 | */ 683 | Rectangle2D resultBounds = op.getBounds2D(src); 684 | 685 | // Watch out for flaky/misbehaving ops that fail to work right. 686 | if (resultBounds == null) 687 | throw new ImagingOpException( 688 | "BufferedImageOp [" 689 | + op.toString() 690 | + "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op."); 691 | 692 | /* 693 | * We must manually create the target image; we cannot rely on the 694 | * null-destination filter() method to create a valid destination 695 | * for us thanks to this JDK bug that has been filed for almost a 696 | * decade: 697 | * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606 698 | */ 699 | BufferedImage dest = createOptimalImage(src, 700 | (int) Math.round(resultBounds.getWidth()), 701 | (int) Math.round(resultBounds.getHeight())); 702 | 703 | // Perform the operation, update our result to return. 704 | BufferedImage result = op.filter(src, dest); 705 | 706 | /* 707 | * Flush the 'src' image ONLY IF it is one of our interim temporary 708 | * images being used when applying 2 or more operations back to 709 | * back. We never want to flush the original image passed in. 710 | */ 711 | if (hasReassignedSrc) 712 | src.flush(); 713 | 714 | /* 715 | * Incase there are more operations to perform, update what we 716 | * consider the 'src' reference to our last result so on the next 717 | * iteration the next op is applied to this result and not back 718 | * against the original src passed in. 719 | */ 720 | src = result; 721 | 722 | /* 723 | * Keep track of when we re-assign 'src' to an interim temporary 724 | * image, so we know when we can explicitly flush it and clean up 725 | * references on future iterations. 726 | */ 727 | hasReassignedSrc = true; 728 | 729 | if (DEBUG) 730 | log(1, 731 | "Applied BufferedImageOp in %d ms, result [width=%d, height=%d]", 732 | System.currentTimeMillis() - subT, result.getWidth(), 733 | result.getHeight()); 734 | } 735 | 736 | if (DEBUG) 737 | log(0, "All %d BufferedImageOps applied in %d ms", ops.length, 738 | System.currentTimeMillis() - t); 739 | 740 | return src; 741 | } 742 | 743 | /** 744 | * Used to crop the given src image from the top-left corner 745 | * and applying any optional {@link BufferedImageOp}s to the result before 746 | * returning it. 747 | *

748 | * TIP: This operation leaves the original src 749 | * image unmodified. If the caller is done with the src image 750 | * after getting the result of this operation, remember to call 751 | * {@link BufferedImage#flush()} on the src to free up native 752 | * resources and make it easier for the GC to collect the unused image. 753 | * 754 | * @param src 755 | * The image to crop. 756 | * @param width 757 | * The width of the bounding cropping box. 758 | * @param height 759 | * The height of the bounding cropping box. 760 | * @param ops 761 | * 0 or more ops to apply to the image. If 762 | * null or empty then src is return 763 | * unmodified. 764 | * 765 | * @return a new {@link BufferedImage} representing the cropped region of 766 | * the src image with any optional operations applied 767 | * to it. 768 | * 769 | * @throws IllegalArgumentException 770 | * if src is null. 771 | * @throws IllegalArgumentException 772 | * if any coordinates of the bounding crop box is invalid within 773 | * the bounds of the src image (e.g. negative or 774 | * too big). 775 | * @throws ImagingOpException 776 | * if one of the given {@link BufferedImageOp}s fails to apply. 777 | * These exceptions bubble up from the inside of most of the 778 | * {@link BufferedImageOp} implementations and are explicitly 779 | * defined on the imgscalr API to make it easier for callers to 780 | * catch the exception (if they are passing along optional ops 781 | * to be applied). imgscalr takes detailed steps to avoid the 782 | * most common pitfalls that will cause {@link BufferedImageOp}s 783 | * to fail, even when using straight forward JDK-image 784 | * operations. 785 | */ 786 | public static BufferedImage crop(BufferedImage src, int width, int height, 787 | BufferedImageOp... ops) throws IllegalArgumentException, 788 | ImagingOpException { 789 | return crop(src, 0, 0, width, height, ops); 790 | } 791 | 792 | /** 793 | * Used to crop the given src image and apply any optional 794 | * {@link BufferedImageOp}s to it before returning the result. 795 | *

796 | * TIP: This operation leaves the original src 797 | * image unmodified. If the caller is done with the src image 798 | * after getting the result of this operation, remember to call 799 | * {@link BufferedImage#flush()} on the src to free up native 800 | * resources and make it easier for the GC to collect the unused image. 801 | * 802 | * @param src 803 | * The image to crop. 804 | * @param x 805 | * The x-coordinate of the top-left corner of the bounding box 806 | * used for cropping. 807 | * @param y 808 | * The y-coordinate of the top-left corner of the bounding box 809 | * used for cropping. 810 | * @param width 811 | * The width of the bounding cropping box. 812 | * @param height 813 | * The height of the bounding cropping box. 814 | * @param ops 815 | * 0 or more ops to apply to the image. If 816 | * null or empty then src is return 817 | * unmodified. 818 | * 819 | * @return a new {@link BufferedImage} representing the cropped region of 820 | * the src image with any optional operations applied 821 | * to it. 822 | * 823 | * @throws IllegalArgumentException 824 | * if src is null. 825 | * @throws IllegalArgumentException 826 | * if any coordinates of the bounding crop box is invalid within 827 | * the bounds of the src image (e.g. negative or 828 | * too big). 829 | * @throws ImagingOpException 830 | * if one of the given {@link BufferedImageOp}s fails to apply. 831 | * These exceptions bubble up from the inside of most of the 832 | * {@link BufferedImageOp} implementations and are explicitly 833 | * defined on the imgscalr API to make it easier for callers to 834 | * catch the exception (if they are passing along optional ops 835 | * to be applied). imgscalr takes detailed steps to avoid the 836 | * most common pitfalls that will cause {@link BufferedImageOp}s 837 | * to fail, even when using straight forward JDK-image 838 | * operations. 839 | */ 840 | public static BufferedImage crop(BufferedImage src, int x, int y, 841 | int width, int height, BufferedImageOp... ops) 842 | throws IllegalArgumentException, ImagingOpException { 843 | long t = -1; 844 | if (DEBUG) 845 | t = System.currentTimeMillis(); 846 | 847 | if (src == null) 848 | throw new IllegalArgumentException("src cannot be null"); 849 | if (x < 0 || y < 0 || width < 0 || height < 0) 850 | throw new IllegalArgumentException("Invalid crop bounds: x [" + x 851 | + "], y [" + y + "], width [" + width + "] and height [" 852 | + height + "] must all be >= 0"); 853 | 854 | int srcWidth = src.getWidth(); 855 | int srcHeight = src.getHeight(); 856 | 857 | if ((x + width) > srcWidth) 858 | throw new IllegalArgumentException( 859 | "Invalid crop bounds: x + width [" + (x + width) 860 | + "] must be <= src.getWidth() [" + srcWidth + "]"); 861 | if ((y + height) > srcHeight) 862 | throw new IllegalArgumentException( 863 | "Invalid crop bounds: y + height [" + (y + height) 864 | + "] must be <= src.getHeight() [" + srcHeight 865 | + "]"); 866 | 867 | if (DEBUG) 868 | log(0, 869 | "Cropping Image [width=%d, height=%d] to [x=%d, y=%d, width=%d, height=%d]...", 870 | srcWidth, srcHeight, x, y, width, height); 871 | 872 | // Create a target image of an optimal type to render into. 873 | BufferedImage result = createOptimalImage(src, width, height); 874 | Graphics g = result.getGraphics(); 875 | 876 | /* 877 | * Render the region specified by our crop bounds from the src image 878 | * directly into our result image (which is the exact size of the crop 879 | * region). 880 | */ 881 | g.drawImage(src, 0, 0, width, height, x, y, (x + width), (y + height), 882 | null); 883 | g.dispose(); 884 | 885 | if (DEBUG) 886 | log(0, "Cropped Image in %d ms", System.currentTimeMillis() - t); 887 | 888 | // Apply any optional operations (if specified). 889 | if (ops != null && ops.length > 0) 890 | result = apply(result, ops); 891 | 892 | return result; 893 | } 894 | 895 | /** 896 | * Used to apply padding around the edges of an image using 897 | * {@link Color#BLACK} to fill the extra padded space and then return the 898 | * result. 899 | *

900 | * The amount of padding specified is applied to all sides; 901 | * more specifically, a padding of 2 would add 2 902 | * extra pixels of space (filled by the given color) on the 903 | * top, bottom, left and right sides of the resulting image causing the 904 | * result to be 4 pixels wider and 4 pixels taller than the src 905 | * image. 906 | *

907 | * TIP: This operation leaves the original src 908 | * image unmodified. If the caller is done with the src image 909 | * after getting the result of this operation, remember to call 910 | * {@link BufferedImage#flush()} on the src to free up native 911 | * resources and make it easier for the GC to collect the unused image. 912 | * 913 | * @param src 914 | * The image the padding will be added to. 915 | * @param padding 916 | * The number of pixels of padding to add to each side in the 917 | * resulting image. If this value is 0 then 918 | * src is returned unmodified. 919 | * @param ops 920 | * 0 or more ops to apply to the image. If 921 | * null or empty then src is return 922 | * unmodified. 923 | * 924 | * @return a new {@link BufferedImage} representing src with 925 | * the given padding applied to it. 926 | * 927 | * @throws IllegalArgumentException 928 | * if src is null. 929 | * @throws IllegalArgumentException 930 | * if padding is < 1. 931 | * @throws ImagingOpException 932 | * if one of the given {@link BufferedImageOp}s fails to apply. 933 | * These exceptions bubble up from the inside of most of the 934 | * {@link BufferedImageOp} implementations and are explicitly 935 | * defined on the imgscalr API to make it easier for callers to 936 | * catch the exception (if they are passing along optional ops 937 | * to be applied). imgscalr takes detailed steps to avoid the 938 | * most common pitfalls that will cause {@link BufferedImageOp}s 939 | * to fail, even when using straight forward JDK-image 940 | * operations. 941 | */ 942 | public static BufferedImage pad(BufferedImage src, int padding, 943 | BufferedImageOp... ops) throws IllegalArgumentException, 944 | ImagingOpException { 945 | return pad(src, padding, Color.BLACK); 946 | } 947 | 948 | /** 949 | * Used to apply padding around the edges of an image using the given color 950 | * to fill the extra padded space and then return the result. {@link Color}s 951 | * using an alpha channel (i.e. transparency) are supported. 952 | *

953 | * The amount of padding specified is applied to all sides; 954 | * more specifically, a padding of 2 would add 2 955 | * extra pixels of space (filled by the given color) on the 956 | * top, bottom, left and right sides of the resulting image causing the 957 | * result to be 4 pixels wider and 4 pixels taller than the src 958 | * image. 959 | *

960 | * TIP: This operation leaves the original src 961 | * image unmodified. If the caller is done with the src image 962 | * after getting the result of this operation, remember to call 963 | * {@link BufferedImage#flush()} on the src to free up native 964 | * resources and make it easier for the GC to collect the unused image. 965 | * 966 | * @param src 967 | * The image the padding will be added to. 968 | * @param padding 969 | * The number of pixels of padding to add to each side in the 970 | * resulting image. If this value is 0 then 971 | * src is returned unmodified. 972 | * @param color 973 | * The color to fill the padded space with. {@link Color}s using 974 | * an alpha channel (i.e. transparency) are supported. 975 | * @param ops 976 | * 0 or more ops to apply to the image. If 977 | * null or empty then src is return 978 | * unmodified. 979 | * 980 | * @return a new {@link BufferedImage} representing src with 981 | * the given padding applied to it. 982 | * 983 | * @throws IllegalArgumentException 984 | * if src is null. 985 | * @throws IllegalArgumentException 986 | * if padding is < 1. 987 | * @throws IllegalArgumentException 988 | * if color is null. 989 | * @throws ImagingOpException 990 | * if one of the given {@link BufferedImageOp}s fails to apply. 991 | * These exceptions bubble up from the inside of most of the 992 | * {@link BufferedImageOp} implementations and are explicitly 993 | * defined on the imgscalr API to make it easier for callers to 994 | * catch the exception (if they are passing along optional ops 995 | * to be applied). imgscalr takes detailed steps to avoid the 996 | * most common pitfalls that will cause {@link BufferedImageOp}s 997 | * to fail, even when using straight forward JDK-image 998 | * operations. 999 | */ 1000 | public static BufferedImage pad(BufferedImage src, int padding, 1001 | Color color, BufferedImageOp... ops) 1002 | throws IllegalArgumentException, ImagingOpException { 1003 | long t = -1; 1004 | if (DEBUG) 1005 | t = System.currentTimeMillis(); 1006 | 1007 | if (src == null) 1008 | throw new IllegalArgumentException("src cannot be null"); 1009 | if (padding < 1) 1010 | throw new IllegalArgumentException("padding [" + padding 1011 | + "] must be > 0"); 1012 | if (color == null) 1013 | throw new IllegalArgumentException("color cannot be null"); 1014 | 1015 | int srcWidth = src.getWidth(); 1016 | int srcHeight = src.getHeight(); 1017 | 1018 | /* 1019 | * Double the padding to account for all sides of the image. More 1020 | * specifically, if padding is "1" we add 2 pixels to width and 2 to 1021 | * height, so we have 1 new pixel of padding all the way around our 1022 | * image. 1023 | */ 1024 | int sizeDiff = (padding * 2); 1025 | int newWidth = srcWidth + sizeDiff; 1026 | int newHeight = srcHeight + sizeDiff; 1027 | 1028 | if (DEBUG) 1029 | log(0, 1030 | "Padding Image from [originalWidth=%d, originalHeight=%d, padding=%d] to [newWidth=%d, newHeight=%d]...", 1031 | srcWidth, srcHeight, padding, newWidth, newHeight); 1032 | 1033 | boolean colorHasAlpha = (color.getAlpha() != 255); 1034 | boolean imageHasAlpha = (src.getTransparency() != BufferedImage.OPAQUE); 1035 | 1036 | BufferedImage result; 1037 | 1038 | /* 1039 | * We need to make sure our resulting image that we render into contains 1040 | * alpha if either our original image OR the padding color we are using 1041 | * contain it. 1042 | */ 1043 | if (colorHasAlpha || imageHasAlpha) { 1044 | if (DEBUG) 1045 | log(1, 1046 | "Transparency FOUND in source image or color, using ARGB image type..."); 1047 | 1048 | result = new BufferedImage(newWidth, newHeight, 1049 | BufferedImage.TYPE_INT_ARGB); 1050 | } else { 1051 | if (DEBUG) 1052 | log(1, 1053 | "Transparency NOT FOUND in source image or color, using RGB image type..."); 1054 | 1055 | result = new BufferedImage(newWidth, newHeight, 1056 | BufferedImage.TYPE_INT_RGB); 1057 | } 1058 | 1059 | Graphics g = result.getGraphics(); 1060 | 1061 | // Draw the border of the image in the color specified. 1062 | g.setColor(color); 1063 | g.fillRect(0, 0, newWidth, padding); 1064 | g.fillRect(0, padding, padding, newHeight); 1065 | g.fillRect(padding, newHeight - padding, newWidth, newHeight); 1066 | g.fillRect(newWidth - padding, padding, newWidth, newHeight - padding); 1067 | 1068 | // Draw the image into the center of the new padded image. 1069 | g.drawImage(src, padding, padding, null); 1070 | g.dispose(); 1071 | 1072 | if (DEBUG) 1073 | log(0, "Padding Applied in %d ms", System.currentTimeMillis() - t); 1074 | 1075 | // Apply any optional operations (if specified). 1076 | if (ops != null && ops.length > 0) 1077 | result = apply(result, ops); 1078 | 1079 | return result; 1080 | } 1081 | 1082 | /** 1083 | * Resize a given image (maintaining its original proportion) to a width and 1084 | * height no bigger than targetSize and apply the given 1085 | * {@link BufferedImageOp}s (if any) to the result before returning it. 1086 | *

1087 | * A scaling method of {@link Method#AUTOMATIC} and mode of 1088 | * {@link Mode#AUTOMATIC} are used. 1089 | *

1090 | * TIP: This operation leaves the original src 1091 | * image unmodified. If the caller is done with the src image 1092 | * after getting the result of this operation, remember to call 1093 | * {@link BufferedImage#flush()} on the src to free up native 1094 | * resources and make it easier for the GC to collect the unused image. 1095 | * 1096 | * @param src 1097 | * The image that will be scaled. 1098 | * @param targetSize 1099 | * The target width and height (square) that you wish the image 1100 | * to fit within. 1101 | * @param ops 1102 | * 0 or more optional image operations (e.g. 1103 | * sharpen, blur, etc.) that can be applied to the final result 1104 | * before returning the image. 1105 | * 1106 | * @return a new {@link BufferedImage} representing the scaled 1107 | * src image. 1108 | * 1109 | * @throws IllegalArgumentException 1110 | * if src is null. 1111 | * @throws IllegalArgumentException 1112 | * if targetSize is < 0. 1113 | * @throws ImagingOpException 1114 | * if one of the given {@link BufferedImageOp}s fails to apply. 1115 | * These exceptions bubble up from the inside of most of the 1116 | * {@link BufferedImageOp} implementations and are explicitly 1117 | * defined on the imgscalr API to make it easier for callers to 1118 | * catch the exception (if they are passing along optional ops 1119 | * to be applied). imgscalr takes detailed steps to avoid the 1120 | * most common pitfalls that will cause {@link BufferedImageOp}s 1121 | * to fail, even when using straight forward JDK-image 1122 | * operations. 1123 | */ 1124 | public static BufferedImage resize(BufferedImage src, int targetSize, 1125 | BufferedImageOp... ops) throws IllegalArgumentException, 1126 | ImagingOpException { 1127 | return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetSize, 1128 | targetSize, ops); 1129 | } 1130 | 1131 | /** 1132 | * Resize a given image (maintaining its original proportion) to a width and 1133 | * height no bigger than targetSize using the given scaling 1134 | * method and apply the given {@link BufferedImageOp}s (if any) to the 1135 | * result before returning it. 1136 | *

1137 | * A mode of {@link Mode#AUTOMATIC} is used. 1138 | *

1139 | * TIP: This operation leaves the original src 1140 | * image unmodified. If the caller is done with the src image 1141 | * after getting the result of this operation, remember to call 1142 | * {@link BufferedImage#flush()} on the src to free up native 1143 | * resources and make it easier for the GC to collect the unused image. 1144 | * 1145 | * @param src 1146 | * The image that will be scaled. 1147 | * @param scalingMethod 1148 | * The method used for scaling the image; preferring speed to 1149 | * quality or a balance of both. 1150 | * @param targetSize 1151 | * The target width and height (square) that you wish the image 1152 | * to fit within. 1153 | * @param ops 1154 | * 0 or more optional image operations (e.g. 1155 | * sharpen, blur, etc.) that can be applied to the final result 1156 | * before returning the image. 1157 | * 1158 | * @return a new {@link BufferedImage} representing the scaled 1159 | * src image. 1160 | * 1161 | * @throws IllegalArgumentException 1162 | * if src is null. 1163 | * @throws IllegalArgumentException 1164 | * if scalingMethod is null. 1165 | * @throws IllegalArgumentException 1166 | * if targetSize is < 0. 1167 | * @throws ImagingOpException 1168 | * if one of the given {@link BufferedImageOp}s fails to apply. 1169 | * These exceptions bubble up from the inside of most of the 1170 | * {@link BufferedImageOp} implementations and are explicitly 1171 | * defined on the imgscalr API to make it easier for callers to 1172 | * catch the exception (if they are passing along optional ops 1173 | * to be applied). imgscalr takes detailed steps to avoid the 1174 | * most common pitfalls that will cause {@link BufferedImageOp}s 1175 | * to fail, even when using straight forward JDK-image 1176 | * operations. 1177 | * 1178 | * @see Method 1179 | */ 1180 | public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1181 | int targetSize, BufferedImageOp... ops) 1182 | throws IllegalArgumentException, ImagingOpException { 1183 | return resize(src, scalingMethod, Mode.AUTOMATIC, targetSize, 1184 | targetSize, ops); 1185 | } 1186 | 1187 | /** 1188 | * Resize a given image (maintaining its original proportion) to a width and 1189 | * height no bigger than targetSize (or fitting the image to 1190 | * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode} 1191 | * specified) and apply the given {@link BufferedImageOp}s (if any) to the 1192 | * result before returning it. 1193 | *

1194 | * A scaling method of {@link Method#AUTOMATIC} is used. 1195 | *

1196 | * TIP: This operation leaves the original src 1197 | * image unmodified. If the caller is done with the src image 1198 | * after getting the result of this operation, remember to call 1199 | * {@link BufferedImage#flush()} on the src to free up native 1200 | * resources and make it easier for the GC to collect the unused image. 1201 | * 1202 | * @param src 1203 | * The image that will be scaled. 1204 | * @param resizeMode 1205 | * Used to indicate how imgscalr should calculate the final 1206 | * target size for the image, either fitting the image to the 1207 | * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1208 | * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1209 | * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1210 | * proportional dimensions for the scaled image based on its 1211 | * orientation (landscape, square or portrait). Unless you have 1212 | * very specific size requirements, most of the time you just 1213 | * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1214 | * @param targetSize 1215 | * The target width and height (square) that you wish the image 1216 | * to fit within. 1217 | * @param ops 1218 | * 0 or more optional image operations (e.g. 1219 | * sharpen, blur, etc.) that can be applied to the final result 1220 | * before returning the image. 1221 | * 1222 | * @return a new {@link BufferedImage} representing the scaled 1223 | * src image. 1224 | * 1225 | * @throws IllegalArgumentException 1226 | * if src is null. 1227 | * @throws IllegalArgumentException 1228 | * if resizeMode is null. 1229 | * @throws IllegalArgumentException 1230 | * if targetSize is < 0. 1231 | * @throws ImagingOpException 1232 | * if one of the given {@link BufferedImageOp}s fails to apply. 1233 | * These exceptions bubble up from the inside of most of the 1234 | * {@link BufferedImageOp} implementations and are explicitly 1235 | * defined on the imgscalr API to make it easier for callers to 1236 | * catch the exception (if they are passing along optional ops 1237 | * to be applied). imgscalr takes detailed steps to avoid the 1238 | * most common pitfalls that will cause {@link BufferedImageOp}s 1239 | * to fail, even when using straight forward JDK-image 1240 | * operations. 1241 | * 1242 | * @see Mode 1243 | */ 1244 | public static BufferedImage resize(BufferedImage src, Mode resizeMode, 1245 | int targetSize, BufferedImageOp... ops) 1246 | throws IllegalArgumentException, ImagingOpException { 1247 | return resize(src, Method.AUTOMATIC, resizeMode, targetSize, 1248 | targetSize, ops); 1249 | } 1250 | 1251 | /** 1252 | * Resize a given image (maintaining its original proportion) to a width and 1253 | * height no bigger than targetSize (or fitting the image to 1254 | * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode} 1255 | * specified) using the given scaling method and apply the given 1256 | * {@link BufferedImageOp}s (if any) to the result before returning it. 1257 | *

1258 | * TIP: This operation leaves the original src 1259 | * image unmodified. If the caller is done with the src image 1260 | * after getting the result of this operation, remember to call 1261 | * {@link BufferedImage#flush()} on the src to free up native 1262 | * resources and make it easier for the GC to collect the unused image. 1263 | * 1264 | * @param src 1265 | * The image that will be scaled. 1266 | * @param scalingMethod 1267 | * The method used for scaling the image; preferring speed to 1268 | * quality or a balance of both. 1269 | * @param resizeMode 1270 | * Used to indicate how imgscalr should calculate the final 1271 | * target size for the image, either fitting the image to the 1272 | * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1273 | * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1274 | * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1275 | * proportional dimensions for the scaled image based on its 1276 | * orientation (landscape, square or portrait). Unless you have 1277 | * very specific size requirements, most of the time you just 1278 | * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1279 | * @param targetSize 1280 | * The target width and height (square) that you wish the image 1281 | * to fit within. 1282 | * @param ops 1283 | * 0 or more optional image operations (e.g. 1284 | * sharpen, blur, etc.) that can be applied to the final result 1285 | * before returning the image. 1286 | * 1287 | * @return a new {@link BufferedImage} representing the scaled 1288 | * src image. 1289 | * 1290 | * @throws IllegalArgumentException 1291 | * if src is null. 1292 | * @throws IllegalArgumentException 1293 | * if scalingMethod is null. 1294 | * @throws IllegalArgumentException 1295 | * if resizeMode is null. 1296 | * @throws IllegalArgumentException 1297 | * if targetSize is < 0. 1298 | * @throws ImagingOpException 1299 | * if one of the given {@link BufferedImageOp}s fails to apply. 1300 | * These exceptions bubble up from the inside of most of the 1301 | * {@link BufferedImageOp} implementations and are explicitly 1302 | * defined on the imgscalr API to make it easier for callers to 1303 | * catch the exception (if they are passing along optional ops 1304 | * to be applied). imgscalr takes detailed steps to avoid the 1305 | * most common pitfalls that will cause {@link BufferedImageOp}s 1306 | * to fail, even when using straight forward JDK-image 1307 | * operations. 1308 | * 1309 | * @see Method 1310 | * @see Mode 1311 | */ 1312 | public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1313 | Mode resizeMode, int targetSize, BufferedImageOp... ops) 1314 | throws IllegalArgumentException, ImagingOpException { 1315 | return resize(src, scalingMethod, resizeMode, targetSize, targetSize, 1316 | ops); 1317 | } 1318 | 1319 | /** 1320 | * Resize a given image (maintaining its original proportion) to the target 1321 | * width and height and apply the given {@link BufferedImageOp}s (if any) to 1322 | * the result before returning it. 1323 | *

1324 | * A scaling method of {@link Method#AUTOMATIC} and mode of 1325 | * {@link Mode#AUTOMATIC} are used. 1326 | *

1327 | * TIP: See the class description to understand how this 1328 | * class handles recalculation of the targetWidth or 1329 | * targetHeight depending on the image's orientation in order 1330 | * to maintain the original proportion. 1331 | *

1332 | * TIP: This operation leaves the original src 1333 | * image unmodified. If the caller is done with the src image 1334 | * after getting the result of this operation, remember to call 1335 | * {@link BufferedImage#flush()} on the src to free up native 1336 | * resources and make it easier for the GC to collect the unused image. 1337 | * 1338 | * @param src 1339 | * The image that will be scaled. 1340 | * @param targetWidth 1341 | * The target width that you wish the image to have. 1342 | * @param targetHeight 1343 | * The target height that you wish the image to have. 1344 | * @param ops 1345 | * 0 or more optional image operations (e.g. 1346 | * sharpen, blur, etc.) that can be applied to the final result 1347 | * before returning the image. 1348 | * 1349 | * @return a new {@link BufferedImage} representing the scaled 1350 | * src image. 1351 | * 1352 | * @throws IllegalArgumentException 1353 | * if src is null. 1354 | * @throws IllegalArgumentException 1355 | * if targetWidth is < 0 or if 1356 | * targetHeight is < 0. 1357 | * @throws ImagingOpException 1358 | * if one of the given {@link BufferedImageOp}s fails to apply. 1359 | * These exceptions bubble up from the inside of most of the 1360 | * {@link BufferedImageOp} implementations and are explicitly 1361 | * defined on the imgscalr API to make it easier for callers to 1362 | * catch the exception (if they are passing along optional ops 1363 | * to be applied). imgscalr takes detailed steps to avoid the 1364 | * most common pitfalls that will cause {@link BufferedImageOp}s 1365 | * to fail, even when using straight forward JDK-image 1366 | * operations. 1367 | */ 1368 | public static BufferedImage resize(BufferedImage src, int targetWidth, 1369 | int targetHeight, BufferedImageOp... ops) 1370 | throws IllegalArgumentException, ImagingOpException { 1371 | return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetWidth, 1372 | targetHeight, ops); 1373 | } 1374 | 1375 | /** 1376 | * Resize a given image (maintaining its original proportion) to the target 1377 | * width and height using the given scaling method and apply the given 1378 | * {@link BufferedImageOp}s (if any) to the result before returning it. 1379 | *

1380 | * A mode of {@link Mode#AUTOMATIC} is used. 1381 | *

1382 | * TIP: See the class description to understand how this 1383 | * class handles recalculation of the targetWidth or 1384 | * targetHeight depending on the image's orientation in order 1385 | * to maintain the original proportion. 1386 | *

1387 | * TIP: This operation leaves the original src 1388 | * image unmodified. If the caller is done with the src image 1389 | * after getting the result of this operation, remember to call 1390 | * {@link BufferedImage#flush()} on the src to free up native 1391 | * resources and make it easier for the GC to collect the unused image. 1392 | * 1393 | * @param src 1394 | * The image that will be scaled. 1395 | * @param scalingMethod 1396 | * The method used for scaling the image; preferring speed to 1397 | * quality or a balance of both. 1398 | * @param targetWidth 1399 | * The target width that you wish the image to have. 1400 | * @param targetHeight 1401 | * The target height that you wish the image to have. 1402 | * @param ops 1403 | * 0 or more optional image operations (e.g. 1404 | * sharpen, blur, etc.) that can be applied to the final result 1405 | * before returning the image. 1406 | * 1407 | * @return a new {@link BufferedImage} representing the scaled 1408 | * src image. 1409 | * 1410 | * @throws IllegalArgumentException 1411 | * if src is null. 1412 | * @throws IllegalArgumentException 1413 | * if scalingMethod is null. 1414 | * @throws IllegalArgumentException 1415 | * if targetWidth is < 0 or if 1416 | * targetHeight is < 0. 1417 | * @throws ImagingOpException 1418 | * if one of the given {@link BufferedImageOp}s fails to apply. 1419 | * These exceptions bubble up from the inside of most of the 1420 | * {@link BufferedImageOp} implementations and are explicitly 1421 | * defined on the imgscalr API to make it easier for callers to 1422 | * catch the exception (if they are passing along optional ops 1423 | * to be applied). imgscalr takes detailed steps to avoid the 1424 | * most common pitfalls that will cause {@link BufferedImageOp}s 1425 | * to fail, even when using straight forward JDK-image 1426 | * operations. 1427 | * 1428 | * @see Method 1429 | */ 1430 | public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1431 | int targetWidth, int targetHeight, BufferedImageOp... ops) { 1432 | return resize(src, scalingMethod, Mode.AUTOMATIC, targetWidth, 1433 | targetHeight, ops); 1434 | } 1435 | 1436 | /** 1437 | * Resize a given image (maintaining its original proportion) to the target 1438 | * width and height (or fitting the image to the given WIDTH or HEIGHT 1439 | * explicitly, depending on the {@link Mode} specified) and apply the given 1440 | * {@link BufferedImageOp}s (if any) to the result before returning it. 1441 | *

1442 | * A scaling method of {@link Method#AUTOMATIC} is used. 1443 | *

1444 | * TIP: See the class description to understand how this 1445 | * class handles recalculation of the targetWidth or 1446 | * targetHeight depending on the image's orientation in order 1447 | * to maintain the original proportion. 1448 | *

1449 | * TIP: This operation leaves the original src 1450 | * image unmodified. If the caller is done with the src image 1451 | * after getting the result of this operation, remember to call 1452 | * {@link BufferedImage#flush()} on the src to free up native 1453 | * resources and make it easier for the GC to collect the unused image. 1454 | * 1455 | * @param src 1456 | * The image that will be scaled. 1457 | * @param resizeMode 1458 | * Used to indicate how imgscalr should calculate the final 1459 | * target size for the image, either fitting the image to the 1460 | * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1461 | * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1462 | * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1463 | * proportional dimensions for the scaled image based on its 1464 | * orientation (landscape, square or portrait). Unless you have 1465 | * very specific size requirements, most of the time you just 1466 | * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1467 | * @param targetWidth 1468 | * The target width that you wish the image to have. 1469 | * @param targetHeight 1470 | * The target height that you wish the image to have. 1471 | * @param ops 1472 | * 0 or more optional image operations (e.g. 1473 | * sharpen, blur, etc.) that can be applied to the final result 1474 | * before returning the image. 1475 | * 1476 | * @return a new {@link BufferedImage} representing the scaled 1477 | * src image. 1478 | * 1479 | * @throws IllegalArgumentException 1480 | * if src is null. 1481 | * @throws IllegalArgumentException 1482 | * if resizeMode is null. 1483 | * @throws IllegalArgumentException 1484 | * if targetWidth is < 0 or if 1485 | * targetHeight is < 0. 1486 | * @throws ImagingOpException 1487 | * if one of the given {@link BufferedImageOp}s fails to apply. 1488 | * These exceptions bubble up from the inside of most of the 1489 | * {@link BufferedImageOp} implementations and are explicitly 1490 | * defined on the imgscalr API to make it easier for callers to 1491 | * catch the exception (if they are passing along optional ops 1492 | * to be applied). imgscalr takes detailed steps to avoid the 1493 | * most common pitfalls that will cause {@link BufferedImageOp}s 1494 | * to fail, even when using straight forward JDK-image 1495 | * operations. 1496 | * 1497 | * @see Mode 1498 | */ 1499 | public static BufferedImage resize(BufferedImage src, Mode resizeMode, 1500 | int targetWidth, int targetHeight, BufferedImageOp... ops) 1501 | throws IllegalArgumentException, ImagingOpException { 1502 | return resize(src, Method.AUTOMATIC, resizeMode, targetWidth, 1503 | targetHeight, ops); 1504 | } 1505 | 1506 | /** 1507 | * Resize a given image (maintaining its original proportion) to the target 1508 | * width and height (or fitting the image to the given WIDTH or HEIGHT 1509 | * explicitly, depending on the {@link Mode} specified) using the given 1510 | * scaling method and apply the given {@link BufferedImageOp}s (if any) to 1511 | * the result before returning it. 1512 | *

1513 | * TIP: See the class description to understand how this 1514 | * class handles recalculation of the targetWidth or 1515 | * targetHeight depending on the image's orientation in order 1516 | * to maintain the original proportion. 1517 | *

1518 | * TIP: This operation leaves the original src 1519 | * image unmodified. If the caller is done with the src image 1520 | * after getting the result of this operation, remember to call 1521 | * {@link BufferedImage#flush()} on the src to free up native 1522 | * resources and make it easier for the GC to collect the unused image. 1523 | * 1524 | * @param src 1525 | * The image that will be scaled. 1526 | * @param scalingMethod 1527 | * The method used for scaling the image; preferring speed to 1528 | * quality or a balance of both. 1529 | * @param resizeMode 1530 | * Used to indicate how imgscalr should calculate the final 1531 | * target size for the image, either fitting the image to the 1532 | * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image 1533 | * to the given height ({@link Mode#FIT_TO_HEIGHT}). If 1534 | * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate 1535 | * proportional dimensions for the scaled image based on its 1536 | * orientation (landscape, square or portrait). Unless you have 1537 | * very specific size requirements, most of the time you just 1538 | * want to use {@link Mode#AUTOMATIC} to "do the right thing". 1539 | * @param targetWidth 1540 | * The target width that you wish the image to have. 1541 | * @param targetHeight 1542 | * The target height that you wish the image to have. 1543 | * @param ops 1544 | * 0 or more optional image operations (e.g. 1545 | * sharpen, blur, etc.) that can be applied to the final result 1546 | * before returning the image. 1547 | * 1548 | * @return a new {@link BufferedImage} representing the scaled 1549 | * src image. 1550 | * 1551 | * @throws IllegalArgumentException 1552 | * if src is null. 1553 | * @throws IllegalArgumentException 1554 | * if scalingMethod is null. 1555 | * @throws IllegalArgumentException 1556 | * if resizeMode is null. 1557 | * @throws IllegalArgumentException 1558 | * if targetWidth is < 0 or if 1559 | * targetHeight is < 0. 1560 | * @throws ImagingOpException 1561 | * if one of the given {@link BufferedImageOp}s fails to apply. 1562 | * These exceptions bubble up from the inside of most of the 1563 | * {@link BufferedImageOp} implementations and are explicitly 1564 | * defined on the imgscalr API to make it easier for callers to 1565 | * catch the exception (if they are passing along optional ops 1566 | * to be applied). imgscalr takes detailed steps to avoid the 1567 | * most common pitfalls that will cause {@link BufferedImageOp}s 1568 | * to fail, even when using straight forward JDK-image 1569 | * operations. 1570 | * 1571 | * @see Method 1572 | * @see Mode 1573 | */ 1574 | public static BufferedImage resize(BufferedImage src, Method scalingMethod, 1575 | Mode resizeMode, int targetWidth, int targetHeight, 1576 | BufferedImageOp... ops) throws IllegalArgumentException, 1577 | ImagingOpException { 1578 | long t = -1; 1579 | if (DEBUG) 1580 | t = System.currentTimeMillis(); 1581 | 1582 | if (src == null) 1583 | throw new IllegalArgumentException("src cannot be null"); 1584 | if (targetWidth < 0) 1585 | throw new IllegalArgumentException("targetWidth must be >= 0"); 1586 | if (targetHeight < 0) 1587 | throw new IllegalArgumentException("targetHeight must be >= 0"); 1588 | if (scalingMethod == null) 1589 | throw new IllegalArgumentException( 1590 | "scalingMethod cannot be null. A good default value is Method.AUTOMATIC."); 1591 | if (resizeMode == null) 1592 | throw new IllegalArgumentException( 1593 | "resizeMode cannot be null. A good default value is Mode.AUTOMATIC."); 1594 | 1595 | BufferedImage result = null; 1596 | 1597 | int currentWidth = src.getWidth(); 1598 | int currentHeight = src.getHeight(); 1599 | 1600 | // <= 1 is a square or landscape-oriented image, > 1 is a portrait. 1601 | float ratio = ((float) currentHeight / (float) currentWidth); 1602 | 1603 | if (DEBUG) 1604 | log(0, 1605 | "Resizing Image [size=%dx%d, resizeMode=%s, orientation=%s, ratio(H/W)=%f] to [targetSize=%dx%d]", 1606 | currentWidth, currentHeight, resizeMode, 1607 | (ratio <= 1 ? "Landscape/Square" : "Portrait"), ratio, 1608 | targetWidth, targetHeight); 1609 | 1610 | /* 1611 | * First determine if ANY size calculation needs to be done, in the case 1612 | * of FIT_EXACT, ignore image proportions and orientation and just use 1613 | * what the user sent in, otherwise the proportion of the picture must 1614 | * be honored. 1615 | * 1616 | * The way that is done is to figure out if the image is in a 1617 | * LANDSCAPE/SQUARE or PORTRAIT orientation and depending on its 1618 | * orientation, use the primary dimension (width for LANDSCAPE/SQUARE 1619 | * and height for PORTRAIT) to recalculate the alternative (height and 1620 | * width respectively) value that adheres to the existing ratio. 1621 | * 1622 | * This helps make life easier for the caller as they don't need to 1623 | * pre-compute proportional dimensions before calling the API, they can 1624 | * just specify the dimensions they would like the image to roughly fit 1625 | * within and it will do the right thing without mangling the result. 1626 | */ 1627 | if (resizeMode == Mode.FIT_EXACT) { 1628 | if (DEBUG) 1629 | log(1, 1630 | "Resize Mode FIT_EXACT used, no width/height checking or re-calculation will be done."); 1631 | } else if (resizeMode == Mode.BEST_FIT_BOTH) { 1632 | float requestedHeightScaling = ((float) targetHeight / (float) currentHeight); 1633 | float requestedWidthScaling = ((float) targetWidth / (float) currentWidth); 1634 | float actualScaling = Math.min(requestedHeightScaling, requestedWidthScaling); 1635 | 1636 | targetHeight = Math.round((float) currentHeight * actualScaling); 1637 | targetWidth = Math.round((float) currentWidth * actualScaling); 1638 | 1639 | if (targetHeight == currentHeight && targetWidth == currentWidth) 1640 | return src; 1641 | 1642 | if (DEBUG) 1643 | log(1, "Auto-Corrected width and height based on scalingRatio %d.", actualScaling); 1644 | } else { 1645 | if ((ratio <= 1 && resizeMode == Mode.AUTOMATIC) 1646 | || (resizeMode == Mode.FIT_TO_WIDTH)) { 1647 | // First make sure we need to do any work in the first place 1648 | if (targetWidth == src.getWidth()) 1649 | return src; 1650 | 1651 | // Save for detailed logging (this is cheap). 1652 | int originalTargetHeight = targetHeight; 1653 | 1654 | /* 1655 | * Landscape or Square Orientation: Ignore the given height and 1656 | * re-calculate a proportionally correct value based on the 1657 | * targetWidth. 1658 | */ 1659 | targetHeight = (int)Math.ceil((float) targetWidth * ratio); 1660 | 1661 | if (DEBUG && originalTargetHeight != targetHeight) 1662 | log(1, 1663 | "Auto-Corrected targetHeight [from=%d to=%d] to honor image proportions.", 1664 | originalTargetHeight, targetHeight); 1665 | } else { 1666 | // First make sure we need to do any work in the first place 1667 | if (targetHeight == src.getHeight()) 1668 | return src; 1669 | 1670 | // Save for detailed logging (this is cheap). 1671 | int originalTargetWidth = targetWidth; 1672 | 1673 | /* 1674 | * Portrait Orientation: Ignore the given width and re-calculate 1675 | * a proportionally correct value based on the targetHeight. 1676 | */ 1677 | targetWidth = Math.round((float) targetHeight / ratio); 1678 | 1679 | if (DEBUG && originalTargetWidth != targetWidth) 1680 | log(1, 1681 | "Auto-Corrected targetWidth [from=%d to=%d] to honor image proportions.", 1682 | originalTargetWidth, targetWidth); 1683 | } 1684 | } 1685 | 1686 | // If AUTOMATIC was specified, determine the real scaling method. 1687 | if (scalingMethod == Scalr.Method.AUTOMATIC) 1688 | scalingMethod = determineScalingMethod(targetWidth, targetHeight, 1689 | ratio); 1690 | 1691 | if (DEBUG) 1692 | log(1, "Using Scaling Method: %s", scalingMethod); 1693 | 1694 | // Now we scale the image 1695 | if (scalingMethod == Scalr.Method.SPEED) { 1696 | result = scaleImage(src, targetWidth, targetHeight, 1697 | RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 1698 | } else if (scalingMethod == Scalr.Method.BALANCED) { 1699 | result = scaleImage(src, targetWidth, targetHeight, 1700 | RenderingHints.VALUE_INTERPOLATION_BILINEAR); 1701 | } else if (scalingMethod == Scalr.Method.QUALITY 1702 | || scalingMethod == Scalr.Method.ULTRA_QUALITY) { 1703 | /* 1704 | * If we are scaling up (in either width or height - since we know 1705 | * the image will stay proportional we just check if either are 1706 | * being scaled up), directly using a single BICUBIC will give us 1707 | * better results then using Chris Campbell's incremental scaling 1708 | * operation (and take a lot less time). 1709 | * 1710 | * If we are scaling down, we must use the incremental scaling 1711 | * algorithm for the best result. 1712 | */ 1713 | if (targetWidth > currentWidth || targetHeight > currentHeight) { 1714 | if (DEBUG) 1715 | log(1, 1716 | "QUALITY scale-up, a single BICUBIC scale operation will be used..."); 1717 | 1718 | /* 1719 | * BILINEAR and BICUBIC look similar the smaller the scale jump 1720 | * upwards is, if the scale is larger BICUBIC looks sharper and 1721 | * less fuzzy. But most importantly we have to use BICUBIC to 1722 | * match the contract of the QUALITY rendering scalingMethod. 1723 | * This note is just here for anyone reading the code and 1724 | * wondering how they can speed their own calls up. 1725 | */ 1726 | result = scaleImage(src, targetWidth, targetHeight, 1727 | RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1728 | } else { 1729 | if (DEBUG) 1730 | log(1, 1731 | "QUALITY scale-down, incremental scaling will be used..."); 1732 | 1733 | /* 1734 | * Originally we wanted to use BILINEAR interpolation here 1735 | * because it takes 1/3rd the time that the BICUBIC 1736 | * interpolation does, however, when scaling large images down 1737 | * to most sizes bigger than a thumbnail we witnessed noticeable 1738 | * "softening" in the resultant image with BILINEAR that would 1739 | * be unexpectedly annoying to a user expecting a "QUALITY" 1740 | * scale of their original image. Instead BICUBIC was chosen to 1741 | * honor the contract of a QUALITY scale of the original image. 1742 | */ 1743 | result = scaleImageIncrementally(src, targetWidth, 1744 | targetHeight, scalingMethod, 1745 | RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1746 | } 1747 | } 1748 | 1749 | if (DEBUG) 1750 | log(0, "Resized Image in %d ms", System.currentTimeMillis() - t); 1751 | 1752 | // Apply any optional operations (if specified). 1753 | if (ops != null && ops.length > 0) 1754 | result = apply(result, ops); 1755 | 1756 | return result; 1757 | } 1758 | 1759 | /** 1760 | * Used to apply a {@link Rotation} and then 0 or more 1761 | * {@link BufferedImageOp}s to a given image and return the result. 1762 | *

1763 | * TIP: This operation leaves the original src 1764 | * image unmodified. If the caller is done with the src image 1765 | * after getting the result of this operation, remember to call 1766 | * {@link BufferedImage#flush()} on the src to free up native 1767 | * resources and make it easier for the GC to collect the unused image. 1768 | * 1769 | * @param src 1770 | * The image that will have the rotation applied to it. 1771 | * @param rotation 1772 | * The rotation that will be applied to the image. 1773 | * @param ops 1774 | * Zero or more optional image operations (e.g. sharpen, blur, 1775 | * etc.) that can be applied to the final result before returning 1776 | * the image. 1777 | * 1778 | * @return a new {@link BufferedImage} representing src rotated 1779 | * by the given amount and any optional ops applied to it. 1780 | * 1781 | * @throws IllegalArgumentException 1782 | * if src is null. 1783 | * @throws IllegalArgumentException 1784 | * if rotation is null. 1785 | * @throws ImagingOpException 1786 | * if one of the given {@link BufferedImageOp}s fails to apply. 1787 | * These exceptions bubble up from the inside of most of the 1788 | * {@link BufferedImageOp} implementations and are explicitly 1789 | * defined on the imgscalr API to make it easier for callers to 1790 | * catch the exception (if they are passing along optional ops 1791 | * to be applied). imgscalr takes detailed steps to avoid the 1792 | * most common pitfalls that will cause {@link BufferedImageOp}s 1793 | * to fail, even when using straight forward JDK-image 1794 | * operations. 1795 | * 1796 | * @see Rotation 1797 | */ 1798 | public static BufferedImage rotate(BufferedImage src, Rotation rotation, 1799 | BufferedImageOp... ops) throws IllegalArgumentException, 1800 | ImagingOpException { 1801 | long t = -1; 1802 | if (DEBUG) 1803 | t = System.currentTimeMillis(); 1804 | 1805 | if (src == null) 1806 | throw new IllegalArgumentException("src cannot be null"); 1807 | if (rotation == null) 1808 | throw new IllegalArgumentException("rotation cannot be null"); 1809 | 1810 | if (DEBUG) 1811 | log(0, "Rotating Image [%s]...", rotation); 1812 | 1813 | /* 1814 | * Setup the default width/height values from our image. 1815 | * 1816 | * In the case of a 90 or 270 (-90) degree rotation, these two values 1817 | * flip-flop and we will correct those cases down below in the switch 1818 | * statement. 1819 | */ 1820 | int newWidth = src.getWidth(); 1821 | int newHeight = src.getHeight(); 1822 | 1823 | /* 1824 | * We create a transform per operation request as (oddly enough) it ends 1825 | * up being faster for the VM to create, use and destroy these instances 1826 | * than it is to re-use a single AffineTransform per-thread via the 1827 | * AffineTransform.setTo(...) methods which was my first choice (less 1828 | * object creation); after benchmarking this explicit case and looking 1829 | * at just how much code gets run inside of setTo() I opted for a new AT 1830 | * for every rotation. 1831 | * 1832 | * Besides the performance win, trying to safely reuse AffineTransforms 1833 | * via setTo(...) would have required ThreadLocal instances to avoid 1834 | * race conditions where two or more resize threads are manipulating the 1835 | * same transform before applying it. 1836 | * 1837 | * Misusing ThreadLocals are one of the #1 reasons for memory leaks in 1838 | * server applications and since we have no nice way to hook into the 1839 | * init/destroy Servlet cycle or any other initialization cycle for this 1840 | * library to automatically call ThreadLocal.remove() to avoid the 1841 | * memory leak, it would have made using this library *safely* on the 1842 | * server side much harder. 1843 | * 1844 | * So we opt for creating individual transforms per rotation op and let 1845 | * the VM clean them up in a GC. I only clarify all this reasoning here 1846 | * for anyone else reading this code and being tempted to reuse the AT 1847 | * instances of performance gains; there aren't any AND you get a lot of 1848 | * pain along with it. 1849 | */ 1850 | AffineTransform tx = new AffineTransform(); 1851 | 1852 | switch (rotation) { 1853 | case CW_90: 1854 | /* 1855 | * A 90 or -90 degree rotation will cause the height and width to 1856 | * flip-flop from the original image to the rotated one. 1857 | */ 1858 | newWidth = src.getHeight(); 1859 | newHeight = src.getWidth(); 1860 | 1861 | // Reminder: newWidth == result.getHeight() at this point 1862 | tx.translate(newWidth, 0); 1863 | tx.quadrantRotate(1); 1864 | 1865 | break; 1866 | 1867 | case CW_270: 1868 | /* 1869 | * A 90 or -90 degree rotation will cause the height and width to 1870 | * flip-flop from the original image to the rotated one. 1871 | */ 1872 | newWidth = src.getHeight(); 1873 | newHeight = src.getWidth(); 1874 | 1875 | // Reminder: newHeight == result.getWidth() at this point 1876 | tx.translate(0, newHeight); 1877 | tx.quadrantRotate(3); 1878 | break; 1879 | 1880 | case CW_180: 1881 | tx.translate(newWidth, newHeight); 1882 | tx.quadrantRotate(2); 1883 | break; 1884 | 1885 | case FLIP_HORZ: 1886 | tx.translate(newWidth, 0); 1887 | tx.scale(-1.0, 1.0); 1888 | break; 1889 | 1890 | case FLIP_VERT: 1891 | tx.translate(0, newHeight); 1892 | tx.scale(1.0, -1.0); 1893 | break; 1894 | } 1895 | 1896 | // Create our target image we will render the rotated result to. 1897 | BufferedImage result = createOptimalImage(src, newWidth, newHeight); 1898 | Graphics2D g2d = (Graphics2D) result.createGraphics(); 1899 | 1900 | /* 1901 | * Render the resultant image to our new rotatedImage buffer, applying 1902 | * the AffineTransform that we calculated above during rendering so the 1903 | * pixels from the old position are transposed to the new positions in 1904 | * the resulting image correctly. 1905 | */ 1906 | g2d.drawImage(src, tx, null); 1907 | g2d.dispose(); 1908 | 1909 | if (DEBUG) 1910 | log(0, "Rotation Applied in %d ms, result [width=%d, height=%d]", 1911 | System.currentTimeMillis() - t, result.getWidth(), 1912 | result.getHeight()); 1913 | 1914 | // Apply any optional operations (if specified). 1915 | if (ops != null && ops.length > 0) 1916 | result = apply(result, ops); 1917 | 1918 | return result; 1919 | } 1920 | 1921 | /** 1922 | * Used to write out a useful and well-formatted log message by any piece of 1923 | * code inside of the imgscalr library. 1924 | *

1925 | * If a message cannot be logged (logging is disabled) then this method 1926 | * returns immediately. 1927 | *

1928 | * NOTE: Because Java will auto-box primitive arguments 1929 | * into Objects when building out the params array, care should 1930 | * be taken not to call this method with primitive values unless 1931 | * {@link Scalr#DEBUG} is true; otherwise the VM will be 1932 | * spending time performing unnecessary auto-boxing calculations. 1933 | * 1934 | * @param depth 1935 | * The indentation level of the log message. 1936 | * @param message 1937 | * The log message in format string syntax that will be logged. 1940 | * @param params 1941 | * The parameters that will be swapped into all the place holders 1942 | * in the original messages before being logged. 1943 | * 1944 | * @see Scalr#LOG_PREFIX 1945 | * @see Scalr#LOG_PREFIX_PROPERTY_NAME 1946 | */ 1947 | protected static void log(int depth, String message, Object... params) { 1948 | if (Scalr.DEBUG) { 1949 | System.out.print(Scalr.LOG_PREFIX); 1950 | 1951 | for (int i = 0; i < depth; i++) 1952 | System.out.print("\t"); 1953 | 1954 | System.out.printf(message, params); 1955 | System.out.println(); 1956 | } 1957 | } 1958 | 1959 | /** 1960 | * Used to create a {@link BufferedImage} with the most optimal RGB TYPE ( 1961 | * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB} 1962 | * ) capable of being rendered into from the given src. The 1963 | * width and height of both images will be identical. 1964 | *

1965 | * This does not perform a copy of the image data from src into 1966 | * the result image; see {@link #copyToOptimalImage(BufferedImage)} for 1967 | * that. 1968 | *

1969 | * We force all rendering results into one of these two types, avoiding the 1970 | * case where a source image is of an unsupported (or poorly supported) 1971 | * format by Java2D causing the rendering result to end up looking terrible 1972 | * (common with GIFs) or be totally corrupt (e.g. solid black image). 1973 | *

1974 | * Originally reported by Magnus Kvalheim from Movellas when scaling certain 1975 | * GIF and PNG images. 1976 | * 1977 | * @param src 1978 | * The source image that will be analyzed to determine the most 1979 | * optimal image type it can be rendered into. 1980 | * 1981 | * @return a new {@link BufferedImage} representing the most optimal target 1982 | * image type that src can be rendered into. 1983 | * 1984 | * @see How 1986 | * Java2D handles poorly supported image types 1987 | * @see Thanks 1989 | * to Morten Nobel for implementation hint 1990 | */ 1991 | protected static BufferedImage createOptimalImage(BufferedImage src) { 1992 | return createOptimalImage(src, src.getWidth(), src.getHeight()); 1993 | } 1994 | 1995 | /** 1996 | * Used to create a {@link BufferedImage} with the given dimensions and the 1997 | * most optimal RGB TYPE ( {@link BufferedImage#TYPE_INT_RGB} or 1998 | * {@link BufferedImage#TYPE_INT_ARGB} ) capable of being rendered into from 1999 | * the given src. 2000 | *

2001 | * This does not perform a copy of the image data from src into 2002 | * the result image; see {@link #copyToOptimalImage(BufferedImage)} for 2003 | * that. 2004 | *

2005 | * We force all rendering results into one of these two types, avoiding the 2006 | * case where a source image is of an unsupported (or poorly supported) 2007 | * format by Java2D causing the rendering result to end up looking terrible 2008 | * (common with GIFs) or be totally corrupt (e.g. solid black image). 2009 | *

2010 | * Originally reported by Magnus Kvalheim from Movellas when scaling certain 2011 | * GIF and PNG images. 2012 | * 2013 | * @param src 2014 | * The source image that will be analyzed to determine the most 2015 | * optimal image type it can be rendered into. 2016 | * @param width 2017 | * The width of the newly created resulting image. 2018 | * @param height 2019 | * The height of the newly created resulting image. 2020 | * 2021 | * @return a new {@link BufferedImage} representing the most optimal target 2022 | * image type that src can be rendered into. 2023 | * 2024 | * @throws IllegalArgumentException 2025 | * if width or height are < 0. 2026 | * 2027 | * @see How 2029 | * Java2D handles poorly supported image types 2030 | * @see Thanks 2032 | * to Morten Nobel for implementation hint 2033 | */ 2034 | protected static BufferedImage createOptimalImage(BufferedImage src, 2035 | int width, int height) throws IllegalArgumentException { 2036 | if (width <= 0 || height <= 0) 2037 | throw new IllegalArgumentException("width [" + width 2038 | + "] and height [" + height + "] must be > 0"); 2039 | 2040 | return new BufferedImage( 2041 | width, 2042 | height, 2043 | (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB 2044 | : BufferedImage.TYPE_INT_ARGB)); 2045 | } 2046 | 2047 | /** 2048 | * Used to copy a {@link BufferedImage} from a non-optimal type into a new 2049 | * {@link BufferedImage} instance of an optimal type (RGB or ARGB). If 2050 | * src is already of an optimal type, then it is returned 2051 | * unmodified. 2052 | *

2053 | * This method is meant to be used by any calling code (imgscalr's or 2054 | * otherwise) to convert any inbound image from a poorly supported image 2055 | * type into the 2 most well-supported image types in Java2D ( 2056 | * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB} 2057 | * ) in order to ensure all subsequent graphics operations are performed as 2058 | * efficiently and correctly as possible. 2059 | *

2060 | * When using Java2D to work with image types that are not well supported, 2061 | * the results can be anything from exceptions bubbling up from the depths 2062 | * of Java2D to images being completely corrupted and just returned as solid 2063 | * black. 2064 | * 2065 | * @param src 2066 | * The image to copy (if necessary) into an optimally typed 2067 | * {@link BufferedImage}. 2068 | * 2069 | * @return a representation of the src image in an optimally 2070 | * typed {@link BufferedImage}, otherwise src if it was 2071 | * already of an optimal type. 2072 | * 2073 | * @throws IllegalArgumentException 2074 | * if src is null. 2075 | */ 2076 | protected static BufferedImage copyToOptimalImage(BufferedImage src) 2077 | throws IllegalArgumentException { 2078 | if (src == null) 2079 | throw new IllegalArgumentException("src cannot be null"); 2080 | 2081 | // Calculate the type depending on the presence of alpha. 2082 | int type = (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB 2083 | : BufferedImage.TYPE_INT_ARGB); 2084 | BufferedImage result = new BufferedImage(src.getWidth(), 2085 | src.getHeight(), type); 2086 | 2087 | // Render the src image into our new optimal source. 2088 | Graphics g = result.getGraphics(); 2089 | g.drawImage(src, 0, 0, null); 2090 | g.dispose(); 2091 | 2092 | return result; 2093 | } 2094 | 2095 | /** 2096 | * Used to determine the scaling {@link Method} that is best suited for 2097 | * scaling the image to the targeted dimensions. 2098 | *

2099 | * This method is intended to be used to select a specific scaling 2100 | * {@link Method} when a {@link Method#AUTOMATIC} method is specified. This 2101 | * method utilizes the {@link Scalr#THRESHOLD_QUALITY_BALANCED} and 2102 | * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds when selecting which 2103 | * method should be used by comparing the primary dimension (width or 2104 | * height) against the threshold and seeing where the image falls. The 2105 | * primary dimension is determined by looking at the orientation of the 2106 | * image: landscape or square images use their width and portrait-oriented 2107 | * images use their height. 2108 | * 2109 | * @param targetWidth 2110 | * The target width for the scaled image. 2111 | * @param targetHeight 2112 | * The target height for the scaled image. 2113 | * @param ratio 2114 | * A height/width ratio used to determine the orientation of the 2115 | * image so the primary dimension (width or height) can be 2116 | * selected to test if it is greater than or less than a 2117 | * particular threshold. 2118 | * 2119 | * @return the fastest {@link Method} suited for scaling the image to the 2120 | * specified dimensions while maintaining a good-looking result. 2121 | */ 2122 | protected static Method determineScalingMethod(int targetWidth, 2123 | int targetHeight, float ratio) { 2124 | // Get the primary dimension based on the orientation of the image 2125 | int length = (ratio <= 1 ? targetWidth : targetHeight); 2126 | 2127 | // Default to speed 2128 | Method result = Method.SPEED; 2129 | 2130 | // Figure out which scalingMethod should be used 2131 | if (length <= Scalr.THRESHOLD_QUALITY_BALANCED) 2132 | result = Method.QUALITY; 2133 | else if (length <= Scalr.THRESHOLD_BALANCED_SPEED) 2134 | result = Method.BALANCED; 2135 | 2136 | if (DEBUG) 2137 | log(2, "AUTOMATIC scaling method selected: %s", result.name()); 2138 | 2139 | return result; 2140 | } 2141 | 2142 | /** 2143 | * Used to implement a straight-forward image-scaling operation using Java 2144 | * 2D. 2145 | *

2146 | * This method uses the Oracle-encouraged method of 2147 | * Graphics2D.drawImage(...) to scale the given image with the 2148 | * given interpolation hint. 2149 | * 2150 | * @param src 2151 | * The image that will be scaled. 2152 | * @param targetWidth 2153 | * The target width for the scaled image. 2154 | * @param targetHeight 2155 | * The target height for the scaled image. 2156 | * @param interpolationHintValue 2157 | * The {@link RenderingHints} interpolation value used to 2158 | * indicate the method that {@link Graphics2D} should use when 2159 | * scaling the image. 2160 | * 2161 | * @return the result of scaling the original src to the given 2162 | * dimensions using the given interpolation method. 2163 | */ 2164 | protected static BufferedImage scaleImage(BufferedImage src, 2165 | int targetWidth, int targetHeight, Object interpolationHintValue) { 2166 | // Setup the rendering resources to match the source image's 2167 | BufferedImage result = createOptimalImage(src, targetWidth, 2168 | targetHeight); 2169 | Graphics2D resultGraphics = result.createGraphics(); 2170 | 2171 | // Scale the image to the new buffer using the specified rendering hint. 2172 | resultGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 2173 | interpolationHintValue); 2174 | resultGraphics.drawImage(src, 0, 0, targetWidth, targetHeight, null); 2175 | 2176 | // Just to be clean, explicitly dispose our temporary graphics object 2177 | resultGraphics.dispose(); 2178 | 2179 | // Return the scaled image to the caller. 2180 | return result; 2181 | } 2182 | 2183 | /** 2184 | * Used to implement Chris Campbell's incremental-scaling algorithm: http://today.java.net/pub/a/today/2007/04/03/perils 2188 | * -of-image-getscaledinstance.html. 2189 | *

2190 | * Modifications to the original algorithm are variable names and comments 2191 | * added for clarity and the hard-coding of using BICUBIC interpolation as 2192 | * well as the explicit "flush()" operation on the interim BufferedImage 2193 | * instances to avoid resource leaking. 2194 | * 2195 | * @param src 2196 | * The image that will be scaled. 2197 | * @param targetWidth 2198 | * The target width for the scaled image. 2199 | * @param targetHeight 2200 | * The target height for the scaled image. 2201 | * @param scalingMethod 2202 | * The scaling method specified by the user (or calculated by 2203 | * imgscalr) to use for this incremental scaling operation. 2204 | * @param interpolationHintValue 2205 | * The {@link RenderingHints} interpolation value used to 2206 | * indicate the method that {@link Graphics2D} should use when 2207 | * scaling the image. 2208 | * 2209 | * @return an image scaled to the given dimensions using the given rendering 2210 | * hint. 2211 | */ 2212 | protected static BufferedImage scaleImageIncrementally(BufferedImage src, 2213 | int targetWidth, int targetHeight, Method scalingMethod, 2214 | Object interpolationHintValue) { 2215 | boolean hasReassignedSrc = false; 2216 | int incrementCount = 0; 2217 | int currentWidth = src.getWidth(); 2218 | int currentHeight = src.getHeight(); 2219 | 2220 | /* 2221 | * The original QUALITY mode, representing Chris Campbell's algorithm, 2222 | * is to step down by 1/2s every time when scaling the image 2223 | * incrementally. Users pointed out that using this method to scale 2224 | * images with noticeable straight lines left them really jagged in 2225 | * smaller thumbnail format. 2226 | * 2227 | * After investigation it was discovered that scaling incrementally by 2228 | * smaller increments was the ONLY way to make the thumbnail sized 2229 | * images look less jagged and more accurate; almost matching the 2230 | * accuracy of Mac's built in thumbnail generation which is the highest 2231 | * quality resize I've come across (better than GIMP Lanczos3 and 2232 | * Windows 7). 2233 | * 2234 | * A divisor of 7 was chose as using 5 still left some jaggedness in the 2235 | * image while a divisor of 8 or higher made the resulting thumbnail too 2236 | * soft; like our OP_ANTIALIAS convolve op had been forcibly applied to 2237 | * the result even if the user didn't want it that soft. 2238 | * 2239 | * Using a divisor of 7 for the ULTRA_QUALITY seemed to be the sweet 2240 | * spot. 2241 | * 2242 | * NOTE: Below when the actual fraction is used to calculate the small 2243 | * portion to subtract from the current dimension, this is a 2244 | * progressively smaller and smaller chunk. When the code was changed to 2245 | * do a linear reduction of the image of equal steps for each 2246 | * incremental resize (e.g. say 50px each time) the result was 2247 | * significantly worse than the progressive approach used below; even 2248 | * when a very high number of incremental steps (13) was tested. 2249 | */ 2250 | int fraction = (scalingMethod == Method.ULTRA_QUALITY ? 7 : 2); 2251 | 2252 | do { 2253 | int prevCurrentWidth = currentWidth; 2254 | int prevCurrentHeight = currentHeight; 2255 | 2256 | /* 2257 | * If the current width is bigger than our target, cut it in half 2258 | * and sample again. 2259 | */ 2260 | if (currentWidth > targetWidth) { 2261 | currentWidth -= (currentWidth / fraction); 2262 | 2263 | /* 2264 | * If we cut the width too far it means we are on our last 2265 | * iteration. Just set it to the target width and finish up. 2266 | */ 2267 | if (currentWidth < targetWidth) 2268 | currentWidth = targetWidth; 2269 | } 2270 | 2271 | /* 2272 | * If the current height is bigger than our target, cut it in half 2273 | * and sample again. 2274 | */ 2275 | 2276 | if (currentHeight > targetHeight) { 2277 | currentHeight -= (currentHeight / fraction); 2278 | 2279 | /* 2280 | * If we cut the height too far it means we are on our last 2281 | * iteration. Just set it to the target height and finish up. 2282 | */ 2283 | 2284 | if (currentHeight < targetHeight) 2285 | currentHeight = targetHeight; 2286 | } 2287 | 2288 | /* 2289 | * Stop when we cannot incrementally step down anymore. 2290 | * 2291 | * This used to use a || condition, but that would cause problems 2292 | * when using FIT_EXACT such that sometimes the width OR height 2293 | * would not change between iterations, but the other dimension 2294 | * would (e.g. resizing 500x500 to 500x250). 2295 | * 2296 | * Now changing this to an && condition requires that both 2297 | * dimensions do not change between a resize iteration before we 2298 | * consider ourselves done. 2299 | */ 2300 | if (prevCurrentWidth == currentWidth 2301 | && prevCurrentHeight == currentHeight) 2302 | break; 2303 | 2304 | if (DEBUG) 2305 | log(2, "Scaling from [%d x %d] to [%d x %d]", prevCurrentWidth, 2306 | prevCurrentHeight, currentWidth, currentHeight); 2307 | 2308 | // Render the incremental scaled image. 2309 | BufferedImage incrementalImage = scaleImage(src, currentWidth, 2310 | currentHeight, interpolationHintValue); 2311 | 2312 | /* 2313 | * Before re-assigning our interim (partially scaled) 2314 | * incrementalImage to be the new src image before we iterate around 2315 | * again to process it down further, we want to flush() the previous 2316 | * src image IF (and only IF) it was one of our own temporary 2317 | * BufferedImages created during this incremental down-sampling 2318 | * cycle. If it wasn't one of ours, then it was the original 2319 | * caller-supplied BufferedImage in which case we don't want to 2320 | * flush() it and just leave it alone. 2321 | */ 2322 | if (hasReassignedSrc) 2323 | src.flush(); 2324 | 2325 | /* 2326 | * Now treat our incremental partially scaled image as the src image 2327 | * and cycle through our loop again to do another incremental 2328 | * scaling of it (if necessary). 2329 | */ 2330 | src = incrementalImage; 2331 | 2332 | /* 2333 | * Keep track of us re-assigning the original caller-supplied source 2334 | * image with one of our interim BufferedImages so we know when to 2335 | * explicitly flush the interim "src" on the next cycle through. 2336 | */ 2337 | hasReassignedSrc = true; 2338 | 2339 | // Track how many times we go through this cycle to scale the image. 2340 | incrementCount++; 2341 | } while (currentWidth != targetWidth || currentHeight != targetHeight); 2342 | 2343 | if (DEBUG) 2344 | log(2, "Incrementally Scaled Image in %d steps.", incrementCount); 2345 | 2346 | /* 2347 | * Once the loop has exited, the src image argument is now our scaled 2348 | * result image that we want to return. 2349 | */ 2350 | return src; 2351 | } 2352 | } 2353 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/AbstractScalrTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.image.BufferedImage; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | 22 | import javax.imageio.ImageIO; 23 | 24 | import org.junit.AfterClass; 25 | import org.junit.Assert; 26 | import org.junit.BeforeClass; 27 | 28 | public abstract class AbstractScalrTest { 29 | protected static BufferedImage src; 30 | 31 | @BeforeClass 32 | public static void setup() throws IOException { 33 | src = load("time-square.png"); 34 | } 35 | 36 | @AfterClass 37 | public static void tearDown() { 38 | src.flush(); 39 | } 40 | 41 | protected static BufferedImage load(String name) { 42 | BufferedImage i = null; 43 | 44 | try { 45 | i = ImageIO.read(AbstractScalrTest.class.getResource(name)); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | return i; 51 | } 52 | 53 | protected static void save(BufferedImage image, String name) { 54 | try { 55 | ImageIO.write(image, "PNG", new FileOutputStream(name)); 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | protected static void assertEquals(BufferedImage orig, BufferedImage tmp) 62 | throws AssertionError { 63 | // Ensure neither image is null. 64 | Assert.assertNotNull(orig); 65 | Assert.assertNotNull(tmp); 66 | 67 | // Ensure dimensions are equal. 68 | Assert.assertEquals(orig.getWidth(), tmp.getWidth()); 69 | Assert.assertEquals(orig.getHeight(), tmp.getHeight()); 70 | 71 | int w = orig.getWidth(); 72 | int h = orig.getHeight(); 73 | 74 | // Ensure every RGB pixel value is the same. 75 | for (int i = 0; i < w; i++) { 76 | for (int j = 0; j < h; j++) { 77 | Assert.assertEquals(orig.getRGB(i, j), tmp.getRGB(i, j)); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/AllTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import org.junit.runner.RunWith; 19 | import org.junit.runners.Suite; 20 | import org.junit.runners.Suite.SuiteClasses; 21 | 22 | @RunWith(Suite.class) 23 | @SuiteClasses({ ScalrApplyTest.class, ScalrCropTest.class, ScalrPadTest.class, 24 | ScalrResizeTest.class, ScalrRotateTest.class }) 25 | public class AllTests { 26 | // no-op 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/AsyncScalrMultiThreadTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.image.BufferedImage; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | 25 | /** 26 | * The purpose of this test is to execute simultaneous scale operations on a 27 | * very small picture as quickly as possible to try and cause a dead-lock. 28 | * 29 | * @author Riyad Kalla (software@thebuzzmedia.com) 30 | */ 31 | public class AsyncScalrMultiThreadTest extends AbstractScalrTest { 32 | private static int ITERS = 100000; 33 | private static BufferedImage ORIG; 34 | 35 | static { 36 | System.setProperty(AsyncScalr.THREAD_COUNT_PROPERTY_NAME, "1"); 37 | ORIG = load("mr-t.jpg"); 38 | } 39 | 40 | @Test 41 | public void test() throws InterruptedException { 42 | List threadList = new ArrayList(ITERS); 43 | 44 | for (int i = 0; i < ITERS; i++) { 45 | if (i % 100 == 0) 46 | System.out.println("Scale Iteration " + i); 47 | 48 | try { 49 | Thread t = new ScaleThread(); 50 | t.start(); 51 | threadList.add(t); 52 | } catch (OutOfMemoryError error) { 53 | System.out.println("Cannot create any more threads, last created was " + i); 54 | ITERS = i; 55 | break; 56 | } 57 | } 58 | 59 | // Now wait for all the threads to finish 60 | for (int i = 0; i < ITERS; i++) { 61 | if (i % 100 == 0) 62 | System.out.println("Thread Finished " + i); 63 | 64 | threadList.get(i).join(); 65 | } 66 | 67 | // Make sure we finish with no exceptions. 68 | Assert.assertTrue(true); 69 | } 70 | 71 | public class ScaleThread extends Thread { 72 | @Override 73 | public void run() { 74 | try { 75 | AsyncScalr.resize(ORIG, 125).get(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/AsyncScalrSingleThreadTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.image.BufferedImage; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | /** 24 | * The purpose of this test is to execute simultaneous scale operations on a 25 | * very small picture as quickly as possible to try and cause a dead-lock. 26 | * 27 | * @author Riyad Kalla (software@thebuzzmedia.com) 28 | */ 29 | public class AsyncScalrSingleThreadTest extends AbstractScalrTest { 30 | private static int ITERS = 100000; 31 | private static BufferedImage ORIG; 32 | 33 | static { 34 | System.setProperty(AsyncScalr.THREAD_COUNT_PROPERTY_NAME, "1"); 35 | ORIG = load("mr-t.jpg"); 36 | } 37 | 38 | @Test 39 | public void test() throws InterruptedException { 40 | for (int i = 0; i < ITERS; i++) { 41 | if (i % 100 == 0) 42 | System.out.println("Scale Iteration " + i); 43 | 44 | Thread t = new ScaleThread(); 45 | t.start(); 46 | 47 | /* 48 | * We are testing single-threaded scales so join on the new thread 49 | * until done and keep testing. 50 | */ 51 | t.join(); 52 | } 53 | 54 | // Make sure we finish with no exceptions. 55 | Assert.assertTrue(true); 56 | } 57 | 58 | public class ScaleThread extends Thread { 59 | @Override 60 | public void run() { 61 | try { 62 | AsyncScalr.resize(ORIG, 125).get(); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/ScalrApplyTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import static org.imgscalr.Scalr.OP_ANTIALIAS; 19 | import static org.imgscalr.Scalr.OP_BRIGHTER; 20 | import static org.imgscalr.Scalr.OP_DARKER; 21 | import static org.imgscalr.Scalr.OP_GRAYSCALE; 22 | import static org.imgscalr.Scalr.apply; 23 | 24 | import java.awt.image.BufferedImageOp; 25 | 26 | import org.junit.Assert; 27 | import org.junit.Test; 28 | 29 | public class ScalrApplyTest extends AbstractScalrTest { 30 | @Test 31 | public void testApplyEX() { 32 | try { 33 | apply(src, (BufferedImageOp[]) null); 34 | Assert.assertTrue(false); 35 | } catch (Exception e) { 36 | Assert.assertTrue(true); 37 | } 38 | } 39 | 40 | @Test 41 | public void testApply1() { 42 | assertEquals(load("time-square-apply-1.png"), apply(src, OP_ANTIALIAS)); 43 | } 44 | 45 | @Test 46 | public void testApply4() { 47 | assertEquals( 48 | load("time-square-apply-4.png"), 49 | apply(src, Scalr.OP_ANTIALIAS, OP_BRIGHTER, OP_DARKER, 50 | OP_GRAYSCALE)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/ScalrCropTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import static org.imgscalr.Scalr.crop; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | public class ScalrCropTest extends AbstractScalrTest { 24 | @Test 25 | public void testCropEX() { 26 | try { 27 | crop(src, 3200, 2400); 28 | Assert.assertTrue(false); 29 | } catch (Exception e) { 30 | Assert.assertTrue(true); 31 | } 32 | 33 | try { 34 | crop(src, -8, -10, 100, 100); 35 | Assert.assertTrue(false); 36 | } catch (Exception e) { 37 | Assert.assertTrue(true); 38 | } 39 | 40 | try { 41 | crop(src, -100, -200, -4, -4); 42 | Assert.assertTrue(false); 43 | } catch (Exception e) { 44 | Assert.assertTrue(true); 45 | } 46 | } 47 | 48 | @Test 49 | public void testCropWH() { 50 | assertEquals(load("time-square-crop-wh.png"), crop(src, 320, 240)); 51 | } 52 | 53 | @Test 54 | public void testCropXYWH() { 55 | assertEquals(load("time-square-crop-xywh.png"), 56 | crop(src, 100, 100, 320, 240)); 57 | } 58 | 59 | @Test 60 | public void testCropXYWHOps() { 61 | assertEquals(load("time-square-crop-xywh-ops.png"), 62 | crop(src, 100, 100, 320, 240, Scalr.OP_GRAYSCALE)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/ScalrPadTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.Color; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | public class ScalrPadTest extends AbstractScalrTest { 24 | // An absurdly bright color to easily/visually check for alpha channel. 25 | static int pad = 8; 26 | static Color alpha = new Color(255, 50, 255, 0); 27 | 28 | @Test 29 | public void testPadEX() { 30 | try { 31 | Scalr.pad(src, -1); 32 | Assert.assertTrue(false); 33 | } catch (IllegalArgumentException ex) { 34 | Assert.assertTrue(true); 35 | } 36 | 37 | try { 38 | Scalr.pad(src, 0); 39 | Assert.assertTrue(false); 40 | } catch (IllegalArgumentException ex) { 41 | Assert.assertTrue(true); 42 | } 43 | } 44 | 45 | @Test 46 | public void testPad() { 47 | assertEquals(load("time-square-pad-8.png"), Scalr.pad(src, pad)); 48 | } 49 | 50 | @Test 51 | public void testPadColor() { 52 | assertEquals(load("time-square-pad-8-red.png"), 53 | Scalr.pad(src, pad, Color.RED)); 54 | } 55 | 56 | @Test 57 | public void testPadAlpha() { 58 | assertEquals(load("time-square-pad-8-alpha.png"), 59 | Scalr.pad(src, pad, alpha)); 60 | } 61 | 62 | @Test 63 | public void testPadAlphaOps() { 64 | assertEquals(load("time-square-pad-8-alpha-ops.png"), 65 | Scalr.pad(src, pad, alpha, Scalr.OP_GRAYSCALE)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/ScalrResizeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import java.awt.image.BufferedImage; 19 | 20 | import junit.framework.Assert; 21 | 22 | import org.imgscalr.Scalr.Method; 23 | import org.imgscalr.Scalr.Mode; 24 | import org.junit.Test; 25 | 26 | public class ScalrResizeTest extends AbstractScalrTest { 27 | @Test 28 | public void testResizeEX() { 29 | try { 30 | Scalr.resize(src, -1); 31 | Assert.assertTrue(false); 32 | } catch (IllegalArgumentException e) { 33 | Assert.assertTrue(true); 34 | } 35 | 36 | try { 37 | Scalr.resize(src, 240, -1); 38 | Assert.assertTrue(false); 39 | } catch (IllegalArgumentException e) { 40 | Assert.assertTrue(true); 41 | } 42 | 43 | try { 44 | Scalr.resize(src, (Method) null, 240); 45 | Assert.assertTrue(false); 46 | } catch (IllegalArgumentException e) { 47 | Assert.assertTrue(true); 48 | } 49 | 50 | try { 51 | Scalr.resize(src, (Mode) null, 240); 52 | Assert.assertTrue(false); 53 | } catch (IllegalArgumentException e) { 54 | Assert.assertTrue(true); 55 | } 56 | 57 | try { 58 | Scalr.resize(src, (Method) null, 240, 240); 59 | Assert.assertTrue(false); 60 | } catch (IllegalArgumentException e) { 61 | Assert.assertTrue(true); 62 | } 63 | 64 | try { 65 | Scalr.resize(src, (Mode) null, 240, 240); 66 | Assert.assertTrue(false); 67 | } catch (IllegalArgumentException e) { 68 | Assert.assertTrue(true); 69 | } 70 | 71 | try { 72 | Scalr.resize(src, null, null, 240); 73 | Assert.assertTrue(false); 74 | } catch (IllegalArgumentException e) { 75 | Assert.assertTrue(true); 76 | } 77 | 78 | try { 79 | Scalr.resize(src, null, null, 240, 240); 80 | Assert.assertTrue(false); 81 | } catch (IllegalArgumentException e) { 82 | Assert.assertTrue(true); 83 | } 84 | } 85 | 86 | @Test 87 | public void testResizeSize() { 88 | assertEquals(load("time-square-resize-320.png"), Scalr.resize(src, 320)); 89 | } 90 | 91 | @Test 92 | public void testResizeWH() { 93 | assertEquals(load("time-square-resize-640x480.png"), 94 | Scalr.resize(src, 640, 480)); 95 | } 96 | 97 | @Test 98 | public void testResizeSizeSpeed() { 99 | assertEquals(load("time-square-resize-320-speed.png"), 100 | Scalr.resize(src, Method.SPEED, 320)); 101 | } 102 | 103 | @Test 104 | public void testResizeWHSpeed() { 105 | assertEquals(load("time-square-resize-640x480-speed.png"), 106 | Scalr.resize(src, Method.SPEED, 640, 480)); 107 | } 108 | 109 | @Test 110 | public void testResizeSizeExact() { 111 | System.setProperty(Scalr.DEBUG_PROPERTY_NAME, "true"); 112 | assertEquals(load("time-square-resize-320-fit-exact.png"), 113 | Scalr.resize(src, Mode.FIT_EXACT, 320)); 114 | } 115 | 116 | @Test 117 | public void testResizeWHExact() { 118 | assertEquals(load("time-square-resize-640x640-fit-exact.png"), 119 | Scalr.resize(src, Mode.FIT_EXACT, 640, 640)); 120 | } 121 | 122 | @Test 123 | public void testResizeSizeSpeedExact() { 124 | assertEquals(load("time-square-resize-320-speed-fit-exact.png"), 125 | Scalr.resize(src, Method.SPEED, Mode.FIT_EXACT, 320)); 126 | } 127 | 128 | @Test 129 | public void testResizeWHSpeedExact() { 130 | assertEquals(load("time-square-resize-640x640-speed-fit-exact.png"), 131 | Scalr.resize(src, Method.SPEED, Mode.FIT_EXACT, 640, 640)); 132 | } 133 | 134 | @Test 135 | public void testResizeWHSpeedExactOps() { 136 | assertEquals( 137 | load("time-square-resize-640x640-speed-fit-exact-ops.png"), 138 | Scalr.resize(src, Method.SPEED, Mode.FIT_EXACT, 640, 640, 139 | Scalr.OP_GRAYSCALE)); 140 | } 141 | 142 | @Test 143 | public void testResizeUltraQuality() { 144 | System.setProperty(Scalr.DEBUG_PROPERTY_NAME, "true"); 145 | BufferedImage i = new BufferedImage(32, 32, BufferedImage.TYPE_INT_RGB); 146 | Scalr.resize(i, Method.ULTRA_QUALITY, 1); 147 | 148 | // This test is really about having scaling to tiny sizes not looping 149 | // forever because of the fractional step-down calculation bottoming 150 | // out. 151 | Assert.assertTrue(true); 152 | } 153 | 154 | @Test 155 | public void testResizeFitExact() { 156 | BufferedImage i = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); 157 | BufferedImage i2 = Scalr.resize(i, Mode.FIT_EXACT, 500, 250); 158 | 159 | Assert.assertEquals(i2.getWidth(), 500); 160 | Assert.assertEquals(i2.getHeight(), 250); 161 | } 162 | 163 | @Test 164 | public void testResizeAutoVsFitBoth() { 165 | // FitBoth will not allow the minor axis to grow beyond the specified box. The four commented 166 | // tests show how this interacts 167 | 168 | // For landscape images, AUTO will let targetWidth decide scaling, even if targetHeight is violated 169 | BufferedImage landscape = new BufferedImage(500, 250, BufferedImage.TYPE_INT_RGB); 170 | testResizeAutoVsBoth(landscape, 500, 250, 500, 250, 500, 250); 171 | testResizeAutoVsBoth(landscape, 500, 500, 500, 250, 500, 250); 172 | 173 | testResizeAutoVsBoth(landscape, 800, 300, 800, 400, 600, 300); // FitBoth restricts y to 300, and adjusts x 174 | testResizeAutoVsBoth(landscape, 800, 400, 800, 400, 800, 400); 175 | testResizeAutoVsBoth(landscape, 800, 500, 800, 400, 800, 400); 176 | 177 | testResizeAutoVsBoth(landscape, 250, 150, 250, 125, 250, 125); 178 | testResizeAutoVsBoth(landscape, 250, 125, 250, 125, 250, 125); 179 | testResizeAutoVsBoth(landscape, 250, 100, 250, 125, 200, 100); // FitBoth imposes smaller y, and adjusts x 180 | 181 | // For portrait images, AUTO will let targetHeight decide scaling, even if targetWidth is violated 182 | BufferedImage portrait = new BufferedImage(250, 500, BufferedImage.TYPE_INT_RGB); 183 | testResizeAutoVsBoth(portrait, 250, 500, 250, 500, 250, 500); 184 | testResizeAutoVsBoth(portrait, 500, 500, 250, 500, 250, 500); 185 | 186 | testResizeAutoVsBoth(portrait, 300, 800, 400, 800, 300, 600); // FitBoth restricts x to 800, and adjusts y 187 | testResizeAutoVsBoth(portrait, 400, 800, 400, 800, 400, 800); 188 | testResizeAutoVsBoth(portrait, 500, 800, 400, 800, 400, 800); 189 | 190 | testResizeAutoVsBoth(portrait, 150, 250, 125, 250, 125, 250); 191 | testResizeAutoVsBoth(portrait, 125, 250, 125, 250, 125, 250); 192 | testResizeAutoVsBoth(portrait, 100, 250, 125, 250, 100, 200); // FitBoth imposes smaller xj, and adjusts y 193 | 194 | // Squares are treated as a landscape 195 | BufferedImage square = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); 196 | testResizeAutoVsBoth(square, 500, 500, 500, 500, 500, 500); 197 | testResizeAutoVsBoth(square, 800, 800, 800, 800, 800, 800); 198 | testResizeAutoVsBoth(square, 400, 400, 400, 400, 400, 400); 199 | testResizeAutoVsBoth(square, 800, 600, 800, 800, 600, 600); // FixBoth restricts both dimensions 200 | 201 | } 202 | 203 | // resize to (w,h) using AUTO and FIT_BOTH modes, then compare auto (w,h) and fitBoth (w,h) 204 | private void testResizeAutoVsBoth (BufferedImage i, int targetWidth, int targetHeight, int autoWidth, int autoHeight, int fitBothWidth, int fitBothHeight) { 205 | BufferedImage auto = Scalr.resize(i, Mode.AUTOMATIC, targetWidth, targetHeight); 206 | BufferedImage fitBoth = Scalr.resize(i, Mode.BEST_FIT_BOTH, targetWidth, targetHeight); 207 | 208 | Assert.assertEquals (autoWidth, auto.getWidth()); 209 | Assert.assertEquals(autoHeight, auto.getHeight()); 210 | 211 | Assert.assertEquals(fitBothWidth, fitBoth.getWidth()); 212 | Assert.assertEquals(fitBothHeight, fitBoth.getHeight()); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/test/java/org/imgscalr/ScalrRotateTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Riyad Kalla 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.imgscalr; 17 | 18 | import org.imgscalr.Scalr.Rotation; 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | 22 | public class ScalrRotateTest extends AbstractScalrTest { 23 | @Test 24 | public void testRotateEX() { 25 | try { 26 | Scalr.rotate(src, null); 27 | Assert.assertTrue(false); 28 | } catch (IllegalArgumentException e) { 29 | Assert.assertTrue(true); 30 | } 31 | } 32 | 33 | @Test 34 | public void testRotate90() { 35 | assertEquals(load("time-square-rotate-90.png"), 36 | Scalr.rotate(load("time-square.png"), Rotation.CW_90)); 37 | } 38 | 39 | @Test 40 | public void testRotate180() { 41 | assertEquals(load("time-square-rotate-180.png"), 42 | Scalr.rotate(load("time-square.png"), Rotation.CW_180)); 43 | } 44 | 45 | @Test 46 | public void testRotate270() { 47 | assertEquals(load("time-square-rotate-270.png"), 48 | Scalr.rotate(load("time-square.png"), Rotation.CW_270)); 49 | } 50 | 51 | @Test 52 | public void testRotateFlipH() { 53 | assertEquals(load("time-square-rotate-horz.png"), 54 | Scalr.rotate(load("time-square.png"), Rotation.FLIP_HORZ)); 55 | } 56 | 57 | @Test 58 | public void testRotateFlipV() { 59 | assertEquals(load("time-square-rotate-vert.png"), 60 | Scalr.rotate(load("time-square.png"), Rotation.FLIP_VERT)); 61 | } 62 | 63 | @Test 64 | public void testRotateFlipHOps() { 65 | assertEquals(load("time-square-rotate-horz-ops.png"), 66 | Scalr.rotate(load("time-square.png"), Rotation.FLIP_HORZ, 67 | Scalr.OP_GRAYSCALE)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/.gitignore: -------------------------------------------------------------------------------- 1 | /L-huge-newspaper-dock.jpg 2 | /L-huge-flower.jpg 3 | -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/mr-t-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/mr-t-thumbnail.jpg -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/mr-t.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/mr-t.jpg -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-apply-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-apply-1.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-apply-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-apply-4.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-crop-wh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-crop-wh.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-crop-xywh-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-crop-xywh-ops.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-crop-xywh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-crop-xywh.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-pad-8-alpha-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-pad-8-alpha-ops.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-pad-8-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-pad-8-alpha.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-pad-8-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-pad-8-red.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-pad-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-pad-8.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-320-fit-exact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-320-fit-exact.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-320-speed-fit-exact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-320-speed-fit-exact.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-320-speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-320-speed.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-320.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-640x480-speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-640x480-speed.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-640x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-640x480.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-640x640-fit-exact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-640x640-fit-exact.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-640x640-speed-fit-exact-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-640x640-speed-fit-exact-ops.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-resize-640x640-speed-fit-exact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-resize-640x640-speed-fit-exact.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-180.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-270.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-90.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-horz-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-horz-ops.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-horz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-horz.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square-rotate-vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square-rotate-vert.png -------------------------------------------------------------------------------- /src/test/resources/org/imgscalr/time-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkalla/imgscalr/770ee89941aa95bb08fc02d0a6cc4fa96b758eb7/src/test/resources/org/imgscalr/time-square.png --------------------------------------------------------------------------------