001 package org.imgscalr;
002
003 import java.awt.Color;
004 import java.awt.image.BufferedImage;
005 import java.awt.image.BufferedImageOp;
006 import java.awt.image.ImagingOpException;
007 import java.util.concurrent.Callable;
008 import java.util.concurrent.ExecutorService;
009 import java.util.concurrent.Executors;
010 import java.util.concurrent.Future;
011 import java.util.concurrent.ThreadFactory;
012 import java.util.concurrent.ThreadPoolExecutor;
013 import java.util.concurrent.TimeUnit;
014 import java.util.concurrent.atomic.AtomicInteger;
015
016 import org.imgscalr.Scalr.Method;
017 import org.imgscalr.Scalr.Mode;
018 import org.imgscalr.Scalr.Rotation;
019
020 /**
021 * Class used to provide the asynchronous versions of all the methods defined in
022 * {@link Scalr} for the purpose of efficiently handling large amounts of image
023 * operations via a select number of processing threads asynchronously.
024 * <p/>
025 * Given that image-scaling operations, especially when working with large
026 * images, can be very hardware-intensive (both CPU and memory), in large-scale
027 * deployments (e.g. a busy web application) it becomes increasingly important
028 * that the scale operations performed by imgscalr be manageable so as not to
029 * fire off too many simultaneous operations that the JVM's heap explodes and
030 * runs out of memory or pegs the CPU on the host machine, staving all other
031 * running processes.
032 * <p/>
033 * Up until now it was left to the caller to implement their own serialization
034 * or limiting logic to handle these use-cases. Given imgscalr's popularity in
035 * web applications it was determined that this requirement be common enough
036 * that it should be integrated directly into the imgscalr library for everyone
037 * to benefit from.
038 * <p/>
039 * Every method in this class wraps the matching methods in the {@link Scalr}
040 * class in new {@link Callable} instances that are submitted to an internal
041 * {@link ExecutorService} for execution at a later date. A {@link Future} is
042 * returned to the caller representing the task that is either currently
043 * performing the scale operation or will at a future date depending on where it
044 * is in the {@link ExecutorService}'s queue. {@link Future#get()} or
045 * {@link Future#get(long, TimeUnit)} can be used to block on the
046 * <code>Future</code>, waiting for the scale operation to complete and return
047 * the resultant {@link BufferedImage} to the caller.
048 * <p/>
049 * This design provides the following features:
050 * <ul>
051 * <li>Non-blocking, asynchronous scale operations that can continue execution
052 * while waiting on the scaled result.</li>
053 * <li>Serialize all scale requests down into a maximum number of
054 * <em>simultaneous</em> scale operations with no additional/complex logic. The
055 * number of simultaneous scale operations is caller-configurable (see
056 * {@link #THREAD_COUNT}) so as best to optimize the host system (e.g. 1 scale
057 * thread per core).</li>
058 * <li>No need to worry about overloading the host system with too many scale
059 * operations, they will simply queue up in this class and execute in-order.</li>
060 * <li>Synchronous/blocking behavior can still be achieved (if desired) by
061 * calling <code>get()</code> or <code>get(long, TimeUnit)</code> immediately on
062 * the returned {@link Future} from any of the methods below.</li>
063 * </ul>
064 * <h3>Performance</h3>
065 * When tuning this class for optimal performance, benchmarking your particular
066 * hardware is the best approach. For some rough guidelines though, there are
067 * two resources you want to watch closely:
068 * <ol>
069 * <li>JVM Heap Memory (Assume physical machine memory is always sufficiently
070 * large)</li>
071 * <li># of CPU Cores</li>
072 * </ol>
073 * You never want to allocate more scaling threads than you have CPU cores and
074 * on a sufficiently busy host where some of the cores may be busy running a
075 * database or a web server, you will want to allocate even less scaling
076 * threads.
077 * <p/>
078 * So as a maximum you would never want more scaling threads than CPU cores in
079 * any situation and less so on a busy server.
080 * <p/>
081 * If you allocate more threads than you have available CPU cores, your scaling
082 * operations will slow down as the CPU will spend a considerable amount of time
083 * context-switching between threads on the same core trying to finish all the
084 * tasks in parallel. You might still be tempted to do this because of the I/O
085 * delay some threads will encounter reading images off disk, but when you do
086 * your own benchmarking you'll likely find (as I did) that the actual disk I/O
087 * necessary to pull the image data off disk is a much smaller portion of the
088 * execution time than the actual scaling operations.
089 * <p/>
090 * If you are executing on a storage medium that is unexpectedly slow and I/O is
091 * a considerable portion of the scaling operation (e.g. S3 or EBS volumes),
092 * feel free to try using more threads than CPU cores to see if that helps; but
093 * in most normal cases, it will only slow down all other parallel scaling
094 * operations.
095 * <p/>
096 * As for memory, every time an image is scaled it is decoded into a
097 * {@link BufferedImage} and stored in the JVM Heap space (decoded image
098 * instances are always larger than the source images on-disk). For larger
099 * images, that can use up quite a bit of memory. You will need to benchmark
100 * your particular use-cases on your hardware to get an idea of where the sweet
101 * spot is for this; if you are operating within tight memory bounds, you may
102 * want to limit simultaneous scaling operations to 1 or 2 regardless of the
103 * number of cores just to avoid having too many {@link BufferedImage} instances
104 * in JVM Heap space at the same time.
105 * <p/>
106 * These are rough metrics and behaviors to give you an idea of how best to tune
107 * this class for your deployment, but nothing can replacement writing a small
108 * Java class that scales a handful of images in a number of different ways and
109 * testing that directly on your deployment hardware.
110 * <h3>Resource Overhead</h3>
111 * The {@link ExecutorService} utilized by this class won't be initialized until
112 * one of the operation methods are called, at which point the
113 * <code>service</code> will be instantiated for the first time and operation
114 * queued up.
115 * <p/>
116 * More specifically, if you have no need for asynchronous image processing
117 * offered by this class, you don't need to worry about wasted resources or
118 * hanging/idle threads as they will never be created if you never use this
119 * class.
120 * <h3>Cleaning up Service Threads</h3>
121 * By default the {@link Thread}s created by the internal
122 * {@link ThreadPoolExecutor} do not run in <code>daemon</code> mode; which
123 * means they will block the host VM from exiting until they are explicitly shut
124 * down in a client application; in a server application the container will shut
125 * down the pool forcibly.
126 * <p/>
127 * If you have used the {@link AsyncScalr} class and are trying to shut down a
128 * client application, you will need to call {@link #getService()} then
129 * {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()}
130 * to have the threads terminated; you may also want to look at the
131 * {@link ExecutorService#awaitTermination(long, TimeUnit)} method if you'd like
132 * to more closely monitor the shutting down process (and finalization of
133 * pending scale operations).
134 * <h3>Reusing Shutdown AsyncScalr</h3>
135 * If you have previously called <code>shutdown</code> on the underlying service
136 * utilized by this class, subsequent calls to any of the operations this class
137 * provides will invoke the internal {@link #checkService()} method which will
138 * replace the terminated underlying {@link ExecutorService} with a new one via
139 * the {@link #createService()} method.
140 * <h3>Custom Implementations</h3>
141 * If a subclass wants to customize the {@link ExecutorService} or
142 * {@link ThreadFactory} used under the covers, this can be done by overriding
143 * the {@link #createService()} method which is invoked by this class anytime a
144 * new {@link ExecutorService} is needed.
145 * <p/>
146 * By default the {@link #createService()} method delegates to the
147 * {@link #createService(ThreadFactory)} method with a new instance of
148 * {@link DefaultThreadFactory}. Either of these methods can be overridden and
149 * customized easily if desired.
150 * <p/>
151 * <strong>TIP</strong>: A common customization to this class is to make the
152 * {@link Thread}s generated by the underlying factory more server-friendly, in
153 * which case the caller would want to use an instance of the
154 * {@link ServerThreadFactory} when creating the new {@link ExecutorService}.
155 * <p/>
156 * This can be done in one line by overriding {@link #createService()} and
157 * returning the result of:
158 * <code>return createService(new ServerThreadFactory());</code>
159 * <p/>
160 * By default this class uses an {@link ThreadPoolExecutor} internally to handle
161 * execution of queued image operations. If a different type of
162 * {@link ExecutorService} is desired, again, simply overriding the
163 * {@link #createService()} method of choice is the right way to do that.
164 *
165 * @author Riyad Kalla (software@thebuzzmedia.com)
166 * @since 3.2
167 */
168 @SuppressWarnings("javadoc")
169 public class AsyncScalr {
170 /**
171 * System property name used to set the number of threads the default
172 * underlying {@link ExecutorService} will use to process async image
173 * operations.
174 * <p/>
175 * Value is "<code>imgscalr.async.threadCount</code>".
176 */
177 public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount";
178
179 /**
180 * Number of threads the internal {@link ExecutorService} will use to
181 * simultaneously execute scale requests.
182 * <p/>
183 * This value can be changed by setting the
184 * <code>imgscalr.async.threadCount</code> system property (see
185 * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0.
186 * <p/>
187 * Default value is <code>2</code>.
188 */
189 public static final int THREAD_COUNT = Integer.getInteger(
190 THREAD_COUNT_PROPERTY_NAME, 2);
191
192 /**
193 * Initializer used to verify the THREAD_COUNT system property.
194 */
195 static {
196 if (THREAD_COUNT < 1)
197 throw new RuntimeException("System property '"
198 + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to "
199 + THREAD_COUNT + ", but THREAD_COUNT must be > 0.");
200 }
201
202 protected static ExecutorService service;
203
204 /**
205 * Used to get access to the internal {@link ExecutorService} used by this
206 * class to process scale operations.
207 * <p/>
208 * <strong>NOTE</strong>: You will need to explicitly shutdown any service
209 * currently set on this class before the host JVM exits.
210 * <p/>
211 * You can call {@link ExecutorService#shutdown()} to wait for all scaling
212 * operations to complete first or call
213 * {@link ExecutorService#shutdownNow()} to kill any in-process operations
214 * and purge all pending operations before exiting.
215 * <p/>
216 * Additionally you can use
217 * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a
218 * shutdown command to try and wait until the service has finished all
219 * tasks.
220 *
221 * @return the current {@link ExecutorService} used by this class to process
222 * scale operations.
223 */
224 public static ExecutorService getService() {
225 return service;
226 }
227
228 /**
229 * @see Scalr#apply(BufferedImage, BufferedImageOp...)
230 */
231 public static Future<BufferedImage> apply(final BufferedImage src,
232 final BufferedImageOp... ops) throws IllegalArgumentException,
233 ImagingOpException {
234 checkService();
235
236 return service.submit(new Callable<BufferedImage>() {
237 public BufferedImage call() throws Exception {
238 return Scalr.apply(src, ops);
239 }
240 });
241 }
242
243 /**
244 * @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...)
245 */
246 public static Future<BufferedImage> crop(final BufferedImage src,
247 final int width, final int height, final BufferedImageOp... ops)
248 throws IllegalArgumentException, ImagingOpException {
249 checkService();
250
251 return service.submit(new Callable<BufferedImage>() {
252 public BufferedImage call() throws Exception {
253 return Scalr.crop(src, width, height, ops);
254 }
255 });
256 }
257
258 /**
259 * @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...)
260 */
261 public static Future<BufferedImage> crop(final BufferedImage src,
262 final int x, final int y, final int width, final int height,
263 final BufferedImageOp... ops) throws IllegalArgumentException,
264 ImagingOpException {
265 checkService();
266
267 return service.submit(new Callable<BufferedImage>() {
268 public BufferedImage call() throws Exception {
269 return Scalr.crop(src, x, y, width, height, ops);
270 }
271 });
272 }
273
274 /**
275 * @see Scalr#pad(BufferedImage, int, BufferedImageOp...)
276 */
277 public static Future<BufferedImage> pad(final BufferedImage src,
278 final int padding, final BufferedImageOp... ops)
279 throws IllegalArgumentException, ImagingOpException {
280 checkService();
281
282 return service.submit(new Callable<BufferedImage>() {
283 public BufferedImage call() throws Exception {
284 return Scalr.pad(src, padding, ops);
285 }
286 });
287 }
288
289 /**
290 * @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...)
291 */
292 public static Future<BufferedImage> pad(final BufferedImage src,
293 final int padding, final Color color, final BufferedImageOp... ops)
294 throws IllegalArgumentException, ImagingOpException {
295 checkService();
296
297 return service.submit(new Callable<BufferedImage>() {
298 public BufferedImage call() throws Exception {
299 return Scalr.pad(src, padding, color, ops);
300 }
301 });
302 }
303
304 /**
305 * @see Scalr#resize(BufferedImage, int, BufferedImageOp...)
306 */
307 public static Future<BufferedImage> resize(final BufferedImage src,
308 final int targetSize, final BufferedImageOp... ops)
309 throws IllegalArgumentException, ImagingOpException {
310 checkService();
311
312 return service.submit(new Callable<BufferedImage>() {
313 public BufferedImage call() throws Exception {
314 return Scalr.resize(src, targetSize, ops);
315 }
316 });
317 }
318
319 /**
320 * @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...)
321 */
322 public static Future<BufferedImage> resize(final BufferedImage src,
323 final Method scalingMethod, final int targetSize,
324 final BufferedImageOp... ops) throws IllegalArgumentException,
325 ImagingOpException {
326 checkService();
327
328 return service.submit(new Callable<BufferedImage>() {
329 public BufferedImage call() throws Exception {
330 return Scalr.resize(src, scalingMethod, targetSize, ops);
331 }
332 });
333 }
334
335 /**
336 * @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...)
337 */
338 public static Future<BufferedImage> resize(final BufferedImage src,
339 final Mode resizeMode, final int targetSize,
340 final BufferedImageOp... ops) throws IllegalArgumentException,
341 ImagingOpException {
342 checkService();
343
344 return service.submit(new Callable<BufferedImage>() {
345 public BufferedImage call() throws Exception {
346 return Scalr.resize(src, resizeMode, targetSize, ops);
347 }
348 });
349 }
350
351 /**
352 * @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...)
353 */
354 public static Future<BufferedImage> resize(final BufferedImage src,
355 final Method scalingMethod, final Mode resizeMode,
356 final int targetSize, final BufferedImageOp... ops)
357 throws IllegalArgumentException, ImagingOpException {
358 checkService();
359
360 return service.submit(new Callable<BufferedImage>() {
361 public BufferedImage call() throws Exception {
362 return Scalr.resize(src, scalingMethod, resizeMode, targetSize,
363 ops);
364 }
365 });
366 }
367
368 /**
369 * @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...)
370 */
371 public static Future<BufferedImage> resize(final BufferedImage src,
372 final int targetWidth, final int targetHeight,
373 final BufferedImageOp... ops) throws IllegalArgumentException,
374 ImagingOpException {
375 checkService();
376
377 return service.submit(new Callable<BufferedImage>() {
378 public BufferedImage call() throws Exception {
379 return Scalr.resize(src, targetWidth, targetHeight, ops);
380 }
381 });
382 }
383
384 /**
385 * @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...)
386 */
387 public static Future<BufferedImage> resize(final BufferedImage src,
388 final Method scalingMethod, final int targetWidth,
389 final int targetHeight, final BufferedImageOp... ops) {
390 checkService();
391
392 return service.submit(new Callable<BufferedImage>() {
393 public BufferedImage call() throws Exception {
394 return Scalr.resize(src, scalingMethod, targetWidth,
395 targetHeight, ops);
396 }
397 });
398 }
399
400 /**
401 * @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...)
402 */
403 public static Future<BufferedImage> resize(final BufferedImage src,
404 final Mode resizeMode, final int targetWidth,
405 final int targetHeight, final BufferedImageOp... ops)
406 throws IllegalArgumentException, ImagingOpException {
407 checkService();
408
409 return service.submit(new Callable<BufferedImage>() {
410 public BufferedImage call() throws Exception {
411 return Scalr.resize(src, resizeMode, targetWidth, targetHeight,
412 ops);
413 }
414 });
415 }
416
417 /**
418 * @see Scalr#resize(BufferedImage, Method, Mode, int, int,
419 * BufferedImageOp...)
420 */
421 public static Future<BufferedImage> resize(final BufferedImage src,
422 final Method scalingMethod, final Mode resizeMode,
423 final int targetWidth, final int targetHeight,
424 final BufferedImageOp... ops) throws IllegalArgumentException,
425 ImagingOpException {
426 checkService();
427
428 return service.submit(new Callable<BufferedImage>() {
429 public BufferedImage call() throws Exception {
430 return Scalr.resize(src, scalingMethod, resizeMode,
431 targetWidth, targetHeight, ops);
432 }
433 });
434 }
435
436 /**
437 * @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...)
438 */
439 public static Future<BufferedImage> rotate(final BufferedImage src,
440 final Rotation rotation, final BufferedImageOp... ops)
441 throws IllegalArgumentException, ImagingOpException {
442 checkService();
443
444 return service.submit(new Callable<BufferedImage>() {
445 public BufferedImage call() throws Exception {
446 return Scalr.rotate(src, rotation, ops);
447 }
448 });
449 }
450
451 protected static ExecutorService createService() {
452 return createService(new DefaultThreadFactory());
453 }
454
455 protected static ExecutorService createService(ThreadFactory factory)
456 throws IllegalArgumentException {
457 if (factory == null)
458 throw new IllegalArgumentException("factory cannot be null");
459
460 return Executors.newFixedThreadPool(THREAD_COUNT, factory);
461 }
462
463 /**
464 * Used to verify that the underlying <code>service</code> points at an
465 * active {@link ExecutorService} instance that can be used by this class.
466 * <p/>
467 * If <code>service</code> is <code>null</code>, has been shutdown or
468 * terminated then this method will replace it with a new
469 * {@link ExecutorService} by calling the {@link #createService()} method
470 * and assigning the returned value to <code>service</code>.
471 * <p/>
472 * Any subclass that wants to customize the {@link ExecutorService} or
473 * {@link ThreadFactory} used internally by this class should override the
474 * {@link #createService()}.
475 */
476 protected static void checkService() {
477 if (service == null || service.isShutdown() || service.isTerminated()) {
478 /*
479 * If service was shutdown or terminated, assigning a new value will
480 * free the reference to the instance, allowing it to be GC'ed when
481 * it is done shutting down (assuming it hadn't already).
482 */
483 service = createService();
484 }
485 }
486
487 /**
488 * Default {@link ThreadFactory} used by the internal
489 * {@link ExecutorService} to creates execution {@link Thread}s for image
490 * scaling.
491 * <p/>
492 * More or less a copy of the hidden class backing the
493 * {@link Executors#defaultThreadFactory()} method, but exposed here to make
494 * it easier for implementors to extend and customize.
495 *
496 * @author Doug Lea
497 * @author Riyad Kalla (software@thebuzzmedia.com)
498 * @since 4.0
499 */
500 protected static class DefaultThreadFactory implements ThreadFactory {
501 protected static final AtomicInteger poolNumber = new AtomicInteger(1);
502
503 protected final ThreadGroup group;
504 protected final AtomicInteger threadNumber = new AtomicInteger(1);
505 protected final String namePrefix;
506
507 DefaultThreadFactory() {
508 SecurityManager manager = System.getSecurityManager();
509
510 /*
511 * Determine the group that threads created by this factory will be
512 * in.
513 */
514 group = (manager == null ? Thread.currentThread().getThreadGroup()
515 : manager.getThreadGroup());
516
517 /*
518 * Define a common name prefix for the threads created by this
519 * factory.
520 */
521 namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
522 }
523
524 /**
525 * Used to create a {@link Thread} capable of executing the given
526 * {@link Runnable}.
527 * <p/>
528 * Thread created by this factory are utilized by the parent
529 * {@link ExecutorService} when processing queued up scale operations.
530 */
531 public Thread newThread(Runnable r) {
532 /*
533 * Create a new thread in our specified group with a meaningful
534 * thread name so it is easy to identify.
535 */
536 Thread thread = new Thread(group, r, namePrefix
537 + threadNumber.getAndIncrement(), 0);
538
539 // Configure thread according to class or subclass
540 thread.setDaemon(false);
541 thread.setPriority(Thread.NORM_PRIORITY);
542
543 return thread;
544 }
545 }
546
547 /**
548 * An extension of the {@link DefaultThreadFactory} class that makes two
549 * changes to the execution {@link Thread}s it generations:
550 * <ol>
551 * <li>Threads are set to be daemon threads instead of user threads.</li>
552 * <li>Threads execute with a priority of {@link Thread#MIN_PRIORITY} to
553 * make them more compatible with server environment deployments.</li>
554 * </ol>
555 * This class is provided as a convenience for subclasses to use if they
556 * want this (common) customization to the {@link Thread}s used internally
557 * by {@link AsyncScalr} to process images, but don't want to have to write
558 * the implementation.
559 *
560 * @author Riyad Kalla (software@thebuzzmedia.com)
561 * @since 4.0
562 */
563 protected static class ServerThreadFactory extends DefaultThreadFactory {
564 /**
565 * Overridden to set <code>daemon</code> property to <code>true</code>
566 * and decrease the priority of the new thread to
567 * {@link Thread#MIN_PRIORITY} before returning it.
568 */
569 @Override
570 public Thread newThread(Runnable r) {
571 Thread thread = super.newThread(r);
572
573 thread.setDaemon(true);
574 thread.setPriority(Thread.MIN_PRIORITY);
575
576 return thread;
577 }
578 }
579 }