Accessibility
 
Home / Developer Center / ColdFusion MX Application Developer Center /

ColdFusion Article

Icon or Spacer Icon or Spacer Icon or Spacer
Matt Liotta
Matt Liotta
www.devilm.com
 

Dynamically Manipulating Images with ColdFusion MX and the Java Advanced Imaging API

One of the most requested ColdFusion extensions is the ability to manipulate images. Whether the request is as simple as just getting the rendered height and width of an image or as complex as creating a thumbnail in a different file format, the Java Advanced Imaging API (JAI) may hold the answer. This article explains how to use the JAI from within ColdFusion MX for generating thumbnail images, converting image file formats, cropping images, and creating borders for images. Additionally, I will show how to obtain image properties, such as the height and width of a rendered image.

 

Before explaining how to use the JAI, I will give a little bit of background. The JAI is a set of interfaces that provide image manipulation for Java. It is an optional package, not shipped with Java 1.3. Since JAI is only a set of interfaces, it also requires an implementation of those interfaces for you to use it. Sun Microsystems provides a free implementation of the JAI interfaces along with the JAI package that you can download. Sun Microsystems implements all of the interfaces, but may not provide all the functionality you are looking for. For instance, the Sun interface can read BMP, JPG, GIF, TIF, and PNG image formats; it can write BMP, JPG, TIF, PNG, but if you must write GIF files, then you’ll have to find another implementation (note that the PNG file format is an acceptable substitute for GIF file format).

To use JAI, you’ll learn how to create a Java class that accesses the JAI APIs and then create a ColdFusion component (CFC) that wraps the Java class. This tutorial requires that you are proficient with Java and know how to setup JAI with ColdFusion MX, such as adding JAI jars to the ColdFusion MX classpath.

Creating a shell class
First, create a shell class with the correct imports. This class will declare some private variables that methods later in this tutorial will use. The shell class is as follows.

import java.io.*;
import java.util.*;
import java.awt.image.renderable.*;
import javax.media.jai.*;
import com.sun.media.jai.codec.*;

public class ImageUtils
{
private RenderedOp image = null;
private RenderedOp result = null;
private int height = 0;
private int width = 0;
}

Creating a load method
After creating the shell class, create your first method. Since all of your image manipulation methods depend on having an image loaded into memory, you will create a load method, as shown below:

public void load(String file) throws IOException
{
FileSeekableStream fss = new FileSeekableStream(file);
image = JAI.create("stream", fss);
height = image.getHeight();
width = image.getWidth();
}

As you can see, the method takes a single parameter that specifies the file to load into memory. You must specify the complete file path for this parameter. First, the method creates a new FileSeekableStream instance using the path passed as a parameter. Then, it creates an image stream. For convenience, this tutorial uses the provided static JAI factory. The first parameter in the static JAI.create method is the type of object you create, while all of the other parameters rely on the object you want to create. In this case, create a stream by passing it the FileSeekableStream instance you just created. The JAI.create method returns a RenderedOp object. Now that you have your image in memory, you can get its height and width with the getHeight() and getWidth() methods of RenderedOp.

Writing the image to disk
No matter what type of image manipulation you wish to do, you must always write the resulting image to disk. To write an image to disk, you must know what type of encoding to use as well as the name of the file to create. The example below shows one option for writing the image to disk:

public void writeResult(String file, String type) throws IOException
{
FileOutputStream os = new FileOutputStream(file);
JAI.create("encode", result, os, type, null);
}

With only two lines of code, the writeResult method is quite simple. The method uses the static JAI.create method to encode your image by passing it a RenderedOp (the image), the FileOutputStream previously created, and the type of encoding to use. Then, it calls the encode method on your behalf, which writes the image to disk in the appropriate format. This method supports almost all of the popular image encodings, except for GIF formats. Check the JAI documentation for a list of supported encodings. For the most part, PNG is an acceptable substitute for GIF.

As an added benefit, the writeResult method can convert image formats. This is because you can load an image into any acceptable format and then write it to any of its supported encodings. For example, you could load a BMP image and then write it as a JPG, thus converting the image format.

Creating a thumbnail
When creating thumbnails, you risk distorting the image if you don’t scale each dimension to the image’s aspect ratio. The thumbnail method below accepts a single number that represents the longest edge of the resulting image. The method scales the image according to its aspect ratio to the desired edge length.

public void thumbnail(float edgeLength)
{
boolean tall = (height > width);
float modifier = edgeLength / (float) (tall ? height : width);

ParameterBlock params = new ParameterBlock();
params.addSource(image);

params.add(modifier);//x scale factor
params.add(modifier);//y scale factor
params.add(0.0F);//x translate
params.add(0.0F);//y translate
params.add(new InterpolationNearest());//interpolation method

result = JAI.create("scale", params);
}

First, the method determines if the image is tall or wide by comparing whether the height is greater than the width. From there, the method has a modifier value based on the desired edge length, divided by the longest edge. Now that you have a modifier value, you must create a ParameterBlock to pass to the scale method. The image source is the first parameter, while you add parameters for the x and y scale factor. Notice the method uses the same x and y scale factor, which prevents image distortion. The rest of the parameters aren’t crucial for generating thumbnails, but are useful for scaling operations. If you are interested in scaling operations, the JAI documentation describes how to use these additional parameters. After creating the ParameterBlock, pass it to the static JAI.create method, which calls scale and returns the result.

Cropping an image
The crop method below crops the same amount for both the height and width of an image. Thus, the crop method below only takes a single parameter: the amount to crop.

public void crop(float edge)
{
ParameterBlock params = new ParameterBlock();
params.addSource(image);

params.add(edge);//x origin
params.add(edge);//y origin
params.add((float) width - edge);//width
params.add((float) height - edge);//height

result = JAI.create("crop", params);
}

Again, the method creates a ParameterBlock. In this method, the image source is the first parameter. From there, the method adds the x and y origins, which specify how to crop the image. Next, it adds the width and height of the crop. It determines the width and height by subtracting the origin from its edge. Since you crop the same amount for both the height and width, use the same values for the x and y values (the same as subtracting the same value from the width and height). Finally, pass the ParameterBlock to the static JAI.create method, which calls the crop method and returns the result.

Creating a border for an image
Much like the crop method, the border method uses the same size border for each side of the image. Besides the width of the border, the border method will also allow you to specify a border color. Thus, the border method takes two parameters.

public void border(int edge, double edgeColor)
{
ParameterBlock params = new ParameterBlock();
params.addSource(image);

params.add(edge);//left pad
params.add(edge);//right pad
params.add(edge);//top pad
params.add(edge);//bottom pad
double fill[] = {edgeColor};
params.add(new BorderExtenderConstant(fill));//type
params.add(edgeColor);//fill color

result = JAI.create("border", params);
}

Again, the method creates a ParameterBlock and sets the image source. Then, it adds a parameter for each side’s border width. Since the method creates the same border width for all sides, this is the same value. Next, add the border color through two parameters: BorderExtenderConstant and the color. You can modify the border method to use multiple colors for your borders, but that is out of the scope of this article. You can find out more about multi-colored borders in the JAI documentation. After creating ParameterBlock, pass it to the static JAI.create method, which calls the border method and returns the result.

Compiling your Java class
You can compile your complete Java class. Ensure that you include the JAI JARs (Java archive files) in your CLASSPATH before attempting to compile it. Once you compile your Java class, simply place it in the ColdFusion CLASSPATH, so that you can use it from your ColdFusion component (CFC).

Creating a CFC to manipulate images dynamically
Note: If you are new to CFCs, read the ColdFusion documentation in the online ColdFusion MX LiveDocs.

The CFC declares three variables: iu, loaded, and result. The code for the three declarations is:

<cfobject type="java" name="iu" class="ImageUtils" action="create">
<cfset loaded = false>
<cfset result = false>

As you can see, the component names the Java class “ImageUtils” and uses the cfobject tag to create an instance of it. Since all of the CFC methods will use the ImageUtils class, this CFC creates the instance in the component instead of within an individual method. Two other variables, loaded and result, are simply boolean values that represent the CFC status.

Since the CFC only wraps the Java class, all of the methods within it are simple. This article lists each method and gives a brief explanation.

<cffunction name="load" access="public">
<cfargument name="filename" type="string" required="true">
<cfscript>
iu.load(arguments.filename);
loaded = true;
</cfscript>
</cffunction>

The load method specifies the file to load directly to ImageUtils and sets the boolean loaded variable to “true,” indicating that it has loaded an image.

<cffunction name="writeResult" access="public">
<cfargument name="filename" type="string" required="true">
<cfargument name="type" type="string" required="true">
<cfif result>
<cfscript>
if(result)
iu.writeResult(arguments.filename, arguments.type);
</cfscript>
</cfif>
</cffunction>

The writeResult method checks to see if the application created a result before calling the ImageUtils writeResult method.

<cffunction name="thumbnail" access="public">
<cfargument name="edgeLength" type="numeric" required="true">
<cfif loaded>
<cfscript>
iu.thumbnail(arguments.edgeLength);
result = true;
</cfscript>
</cfif>
</cffunction>

The thumbnail method checks to see if the CFC has loaded an image. It then calls the ImageUtils thumbnail method and sets the boolean result variable to “true,” indicating that the CFC has created a result.

<cffunction name="crop" access="public">
<cfargument name="edge" type="numeric" required="true">
<cfif loaded>
<cfscript>
iu.crop(arguments.edge);
result = true;
</cfscript>
</cfif>
</cffunction>

Just like the thumbnail method, the crop method checks to see if the CFC has loaded an image. It then calls the ImageUtils crop method and sets the boolean result variable to “true,” indicating that the CFC has created a result.

<cffunction name="border" access="public">
<cfargument name="edge" type="numeric" required="true">
<cfargument name="edgeColor" type="numeric" required="true">
<cfif loaded>
<cfscript>
iu.border(arguments.edgeLength, arguments.edgeColor);
result = true;
</cfscript>
</cfif>
</cffunction>

Finally, the border method checks to see if the CFC has loaded an image. It then calls the ImageUtils border method and sets the boolean result variable to “true,” indicating that the CFC has created a result.

After creating the Java class and associated wrapper CFC, you can now manipulate images from ColdFusion MX easily. Further, you can extend both the Java class and the CFC easily to support other JAI functionality. While creating a wrapper CFC may seem like adding a lot of work with little benefit, you could add further enhancements to the Java class that would be challenging to accomplish in CFML. Since Java is a typed language and ColdFusion is typeless, it is often useful to have a wrapper class that can act as an adapter.

 


About the author

Matt Liotta started his development career at the age of twelve by developing C applications for faculty at Emory University. He built his first web page soon after the release of Mosaic 1.0. Excited by early web applications, Matt saw the potential to replace legacy client server applications. At Emory University he built an enterprise calendaring system, the faculty poster project, a Y2K compliance tracking application, and a prototype for an electronic research administration system.

Since then he has worked with an early ASP, Cignify, to build their transaction processing system for payroll time data. He also built a code distribution system for Consumer Financial Networks, as well as the first online account management system for Grizzard Communications. In San Francisco, Matt consulted for companies such as Williams Sonoma and Yipes Communications. Soon after, he built gMoney’s Group Transaction System using an innovative XML messaging architecture that matches conceptually with the now popular web services paradigm. Later at TeamToolz, he designed a highly secure and scalable network architecture to support N-tier transport agnostic distributed applications. He then went on to implement a cutting-edge content management system for DevX. He is now President & CEO of Montara Software, which he founded recently.