public static BufferedImage apply(BufferedImage src, BufferedImageOp... ops)
			throws IllegalArgumentException, ImagingOpException {
	  long t = -1;
	  if (DEBUG)
	    t = System.currentTimeMillis();

		if (src == null)
			throw new IllegalArgumentException("src cannot be null");
		if (ops == null || ops.length == 0)
			throw new IllegalArgumentException("ops cannot be null or empty");

		int type = src.getType();

		/*
		 * Ensure the src image is in the best supported image type before we
		 * continue, otherwise it is possible our calls below to getBounds2D and
		 * certainly filter(...) may fail if not.
		 * 
		 * Java2D makes an attempt at applying most BufferedImageOps using
		 * hardware acceleration via the ImagingLib internal library.
		 * 
		 * Unfortunately may of the BufferedImageOp are written to simply fail
		 * with an ImagingOpException if the operation cannot be applied with no
		 * additional information about what went wrong or attempts at
		 * re-applying it in different ways.
		 * 
		 * This is assuming the failing BufferedImageOp even returns a null
		 * image after failing to apply; some simply return a corrupted/black
		 * image that result in no exception and it is up to the user to
		 * discover this.
		 * 
		 * In internal testing, EVERY failure I've ever seen was the result of
		 * the source image being in a poorly-supported BufferedImage Type like
		 * BGR or ABGR (even though it was loaded with ImageIO).
		 * 
		 * To avoid this nasty/stupid surprise with BufferedImageOps, we always
		 * ensure that the src image starts in an optimally supported format
		 * before we try and apply the filter.
		 */
		if (!(type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB))
			src = copyToOptimalImage(src);

		if (DEBUG)
			log(0, "Applying %d BufferedImageOps...", ops.length);

		boolean hasReassignedSrc = false;

		for (int i = 0; i < ops.length; i++) {
      long subT = -1;
      if (DEBUG)
        subT = System.currentTimeMillis();
			BufferedImageOp op = ops[i];

			// Skip null ops instead of throwing an exception.
			if (op == null)
				continue;

			if (DEBUG)
				log(1, "Applying BufferedImageOp [class=%s, toString=%s]...",
						op.getClass(), op.toString());

			/*
			 * Must use op.getBounds instead of src.getWidth and src.getHeight
			 * because we are trying to create an image big enough to hold the
			 * result of this operation (which may be to scale the image
			 * smaller), in that case the bounds reported by this op and the
			 * bounds reported by the source image will be different.
			 */
			Rectangle2D resultBounds = op.getBounds2D(src);

			// Watch out for flaky/misbehaving ops that fail to work right.
			if (resultBounds == null)
				throw new ImagingOpException(
						"BufferedImageOp ["
								+ op.toString()
								+ "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op.");

			/*
			 * We must manually create the target image; we cannot rely on the
			 * null-destination filter() method to create a valid destination
			 * for us thanks to this JDK bug that has been filed for almost a
			 * decade:
			 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606
			 */
			BufferedImage dest = createOptimalImage(src,
					(int) Math.round(resultBounds.getWidth()),
					(int) Math.round(resultBounds.getHeight()));

			// Perform the operation, update our result to return.
			BufferedImage result = op.filter(src, dest);

			/*
			 * Flush the 'src' image ONLY IF it is one of our interim temporary
			 * images being used when applying 2 or more operations back to
			 * back. We never want to flush the original image passed in.
			 */
			if (hasReassignedSrc)
				src.flush();

			/*
			 * Incase there are more operations to perform, update what we
			 * consider the 'src' reference to our last result so on the next
			 * iteration the next op is applied to this result and not back
			 * against the original src passed in.
			 */
			src = result;

			/*
			 * Keep track of when we re-assign 'src' to an interim temporary
			 * image, so we know when we can explicitly flush it and clean up
			 * references on future iterations.
			 */
			hasReassignedSrc = true;

			if (DEBUG)
				log(1,
						"Applied BufferedImageOp in %d ms, result [width=%d, height=%d]",
						System.currentTimeMillis() - subT, result.getWidth(),
						result.getHeight());
		}

		if (DEBUG)
			log(0, "All %d BufferedImageOps applied in %d ms", ops.length,
					System.currentTimeMillis() - t);

		return src;
	}
public static BufferedImage apply ( BufferedImage src , BufferedImageOp ... ops ) throws IllegalArgumentException , ImagingOpException { long t = - 1 ; if ( DEBUG ) t = System . currentTimeMillis ( ) ; if ( src == null ) throw new IllegalArgumentException ( "src cannot be null" ) ; if ( ops == null || ops . length == 0 ) throw new IllegalArgumentException ( "ops cannot be null or empty" ) ; int type = src . getType ( ) ; if ( ! ( type == BufferedImage . TYPE_INT_RGB || type == BufferedImage . TYPE_INT_ARGB ) ) src = copyToOptimalImage ( src ) ; if ( DEBUG ) log ( 0 , "Applying %d BufferedImageOps..." , ops . length ) ; boolean hasReassignedSrc = false ; for ( int i = 0 ; i < ops . length ; i ++ ) { long subT = - 1 ; if ( DEBUG ) subT = System . currentTimeMillis ( ) ; BufferedImageOp op = ops [ i ] ; if ( op == null ) continue ; if ( DEBUG ) log ( 1 , "Applying BufferedImageOp [class=%s, toString=%s]..." , op . getClass ( ) , op . toString ( ) ) ; Rectangle2D resultBounds = op . getBounds2D ( src ) ; if ( resultBounds == null ) throw new ImagingOpException ( "BufferedImageOp [" + op . toString ( ) + "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op." ) ; BufferedImage dest = createOptimalImage ( src , ( int ) Math . round ( resultBounds . getWidth ( ) ) , ( int ) Math . round ( resultBounds . getHeight ( ) ) ) ; BufferedImage result = op . filter ( src , dest ) ; if ( hasReassignedSrc ) src . flush ( ) ; src = result ; hasReassignedSrc = true ; if ( DEBUG ) log ( 1 , "Applied BufferedImageOp in %d ms, result [width=%d, height=%d]" , System . currentTimeMillis ( ) - subT , result . getWidth ( ) , result . getHeight ( ) ) ; } if ( DEBUG ) log ( 0 , "All %d BufferedImageOps applied in %d ms" , ops . length , System . currentTimeMillis ( ) - t ) ; return src ; }
Used to apply, in the order given, 1 or more {@link BufferedImageOp}s to
a given {@link BufferedImage} and return the result.
<p/>
<strong>Feature</strong>: This implementation works around <a
href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606">a
decade-old JDK bug</a> that can cause a {@link RasterFormatException}
when applying a perfectly valid {@link BufferedImageOp}s to images.
<p/>
<strong>Feature</strong>: This implementation also works around
{@link BufferedImageOp}s failing to apply and throwing
{@link ImagingOpException}s when run against a <code>src</code> image
type that is poorly supported. Unfortunately using {@link ImageIO} and
standard Java methods to load images provides no consistency in getting
images in well-supported formats. This method automatically accounts and
corrects for all those problems (if necessary).
<p/>
It is recommended you always use this method to apply any
{@link BufferedImageOp}s instead of relying on directly using the
{@link BufferedImageOp#filter(BufferedImage, BufferedImage)} method.
<p/>
<strong>Performance</strong>: Not all {@link BufferedImageOp}s are
hardware accelerated operations, but many of the most popular (like
{@link ConvolveOp}) are. For more information on if your image op is
hardware accelerated or not, check the source code of the underlying JDK
class that actually executes the Op code, <a href=
"http://www.docjar.com/html/api/sun/awt/image/ImagingLib.java.html"
>sun.awt.image.ImagingLib</a>.
<p/>
<strong>TIP</strong>: This operation leaves the original <code>src</code>
image unmodified. If the caller is done with the <code>src</code> image
after getting the result of this operation, remember to call
{@link BufferedImage#flush()} on the <code>src</code> to free up native
resources and make it easier for the GC to collect the unused image.

@param src
The image that will have the ops applied to it.
@param ops
<code>1</code> or more ops to apply to the image.

@return a new {@link BufferedImage} that represents the <code>src</code>
with all the given operations applied to it.

@throws IllegalArgumentException
if <code>src</code> is <code>null</code>.
@throws IllegalArgumentException
if <code>ops</code> is <code>null</code> or empty.
@throws ImagingOpException
if one of the given {@link BufferedImageOp}s fails to apply.
These exceptions bubble up from the inside of most of the
{@link BufferedImageOp} implementations and are explicitly
defined on the imgscalr API to make it easier for callers to
catch the exception (if they are passing along optional ops
to be applied). imgscalr takes detailed steps to avoid the
most common pitfalls that will cause {@link BufferedImageOp}s
to fail, even when using straight forward JDK-image
operations.