├── .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 |
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 | * get()
or get(long, TimeUnit)
immediately on
77 | * the returned {@link Future} from any of the methods below.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 | * 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 | * 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 | * 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 Futureservice
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 | * 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 | * 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 | *targetWidth
as the primary dimension and re-calculate the
106 | * targetHeight
regardless of what is passed in.targetHeight
as the
108 | * primary dimension and re-calculate the targetWidth
regardless of
109 | * what is passed in.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).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 | * 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 | *
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 | * 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:
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:
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 | * .0f | 268 | *.08f | 269 | *.0f | 270 | *
.08f | 273 | *.68f | 274 | *.08f | 275 | *
.0f | 278 | *.08f | 279 | *.0f | 280 | *
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