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.
|