Menu

As simple as possible, as complex as necessary

Integrating imgscalr into ColdFusion as a replacement for ImageScaleToFit()

1 May 2013

Image processing is something many web content management systems have to support. User uploads whopping great photo from multi-megapixel camera/phone, system needs to scale it down to a sensible size (dimensions and bytes) for web delivery.

Since version 8, ColdFusion has offered a range of image functions and these have generally served me well, but other developers report better performance and fewer issues with third-party tools, such as cfx_openimage.

Although using the native functions is obviously simpler, one of CF's great virtues is that it allows you to integrate external libraries without too much fuss. Cfx_openimage is written in C++ but can be registered in the CF Administrator as a ColdFusion "Extension" (CFX) allowing it to be used just like any other CF tag.

Using java libraries is even easier since they can be dynamically loaded rather than having to register them in the server admin. CF10 supports this natively while previous versions can use Mark Mandel's JavaLoader.

Smaller but bigger

The other day I noticed that certain images being scaled down (i.e. to reduce the width and height proportionally), were more than doubling in file size - the opposite of what you'd expect. I tried tweaking the interpolation option on ImageScaleToFit() and various suggested hacks to remove EXIF data, all to no avail.

I thought of using cfx_openimage, but my instinct is to avoid increasing the complexity of my CF server install even by a small amount, so I instead downloaded imgscalr, a tiny java class focussed on the job at hand.

Loading imgscalr

Being still on CF9, the following code uses JavaLoader, which is assumed to be in the same folder as the downloaded imgscalr jar file, the executing script and the source image to be scaled.

script.cfm

imgscalrPath = ExpandPath( "imgscalr-lib-4.2.jar" );
loader = New javaloader.javaLoader( [ imgscalrPath ] );
Scalr = loader.create( "org.imgscalr.Scalr" );
WriteDump( Scalr );

partial screenshot of the imgscalr methods cfdump

Calling imgscalr's resize method

Dumping the object shows that it offers various methods, resize() being the one I'm interested in. Specifically I want to scale an image down to a width of 1400px (the original is 2100px), preferring performance over quality. According to the imgscalr docs the syntax for this in java would be:

BufferedImage smallerImage = Scalr.resize( sourceImage, Scalr.Method.SPEED,Scalr.Mode.FIT_TO_WIDTH,1400 );

However in CF this won't work without a bit of preparation. First we need to supply the original file as a "java.awt.image.BufferedImage", which is easily done using two CF functions:

imgscalrPath = ExpandPath( "imgscalr-lib-4.2.jar" );
loader = New javaloader.javaLoader( [ imgscalrPath ] );
Scalr = loader.create( "org.imgscalr.Scalr" );
sourceImage = ImageNew( "source.jpg" );
bufferedImage = ImageGetBufferedImage( sourceImage );

Next, we need to pass the Method and Mode arguments. As a non-Java programmer these baffled me for a time, but according to the docs they are "enums", or internal classes which need to be instantiated separately before being passed in.

imgscalrPath = ExpandPath( "imgscalr-lib-4.2.jar" );
loader = New javaloader.javaLoader( [ imgscalrPath ] );
Scalr = loader.create( "org.imgscalr.Scalr" );
mode = loader.create( "org.imgscalr.Scalr$Mode" );
method = loader.create( "org.imgscalr.Scalr$Method" );
sourceImage = ImageNew( "source.jpg" );
bufferedImage = ImageGetBufferedImage( sourceImage );

Finally, each of the imgscalr methods takes a set of options to apply to the BufferedImage, such as OP_ANTIALIAS or OP_BRIGHTER. You would pass these as a comma delimited array, for example:

smallerImage = Scalr.resize( bufferedImage,method.SPEED,mode.FIT_TO_WIDTH,1920,[Scalr.OP_ANTIALIAS,Scalr.OP_BRIGHTER] );

None of the options seemed appropriate for my case, so I simply passed an empty array. My final code was as follows:

imgscalrPath = ExpandPath( "imgscalr-lib-4.2.jar" );
loader = New javaloader.javaLoader( [ imgscalrPath ] );
Scalr = loader.create( "org.imgscalr.Scalr" );
mode = loader.create( "org.imgscalr.Scalr$Mode" );
method = loader.create( "org.imgscalr.Scalr$Method" );
sourceImage = ImageNew( "source.jpg" );
bufferedImage = ImageGetBufferedImage( sourceImage );
smallerImage = Scalr.resize( bufferedImage,method.SPEED,mode.FIT_TO_WIDTH,1400,[] );
ImageWrite( ImageNew( smallerImage ),"smaller.jpg" );

Notice in the last line that I am using ImageNew() to convert the BufferedImage back to a normal CF image object, which is then written to disk.

Fail. But I learned something

Sadly, none of this had any effect on the file size of my scaled image, which increased exactly as it had using ImageScaleToFit()!

Clearly the issue lies elsewhere (ideas welcome), but as an exercise in slightly expanding my CF/java integration comfort zone this was not a waste of time.

Posted on . Updated

Comments

  • Formatting comments: See this list of formatting tags you can use in your comments.
  • Want to paste code? Enclose within <pre><code> tags for syntax higlighting and better formatting and if possible use script. If your code includes "self-closing" tags, such as <cfargument>, you must add an explicit closing tag, otherwise it is likely to be mangled by the Disqus parser.
Back to the top