Integrating imgscalr into ColdFusion as a replacement for ImageScaleToFit()
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 );
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.