Skip to page content or Skip to Accesskey List.

Work

Main Page Content

Image Manipulation With Cfmx And Jai

Rated 3.75 (Ratings: 5)

Want more?

 
Picture of DevilM

Matt Liotta

Member info

User since: 11 Mar 2002

Articles written: 6

One of the most requested extensions to ColdFusion is the ability to do image

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

format, the Java Advanced Imaging API (JAI) may well hold the answer. In this

article I will explain how to make use of JAI from CFMX to do the following

four operations, thumbnail generation, format conversion, cropping, and border

creation. Additionally, I will show how to obtain image properties such as

the height and width of the rendered image.

Before getting started using JAI, it is worth learning a little bit about

JAI. JAI is a set of interfaces that provide image manipulation for Java.

JAI is an optional package that does not ship with Java 1.3. Since JAI is

just a set of interfaces, an implementation of those interfaces is also required

in order to make use of it. Sun provides a free implementation of the JAI

interfaces along with the JAI package that you can download here.

Sun does implement all of the interfaces, but may not provide all the functionality

you are looking for. For example, the Sun interface can read BMP, JPG, GIF,

TIF, and PNG image formats, while it can write DMP, JPG, TIF, PNG. If you

need the ability to write GIF files than you will have to find another implementation

(PNG is the generally accepted substitute for GIF).

To easily make use of JAI I am going to create a Java class that accesses the JAI APIs and then a CFC that wraps my Java class. The first step is to create a shell class with the correct imports. I am also going to declare some private variables I�ll make use of later in the methods. 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;

}

After creating the shell class I am ready for my first method. Since all my image manipulation methods will depend on having an image loaded into memory, I will create a load method. Below is my method for loading the image into memory.

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 my method take a single parameter indicating the file it should

load into memory. This parameter needs to be the complete path to the file.

First, I create a new FileSeekableStream instance

using the passed in path as a parameter. I then need to create an image stream.

For convenience I will be using the provided static JAI factory. The static

JAI.create method’s first parameter is the type

of object I want to create, while all the other parameters depend on what

object I want to create. In this case I want to create a stream, so I pass

it the FileSeekableStream instance I just created.

The JAI.create method returns a RenderedOp

object. Now that I have my image in memory I am going to go ahead and get

its height and width using the respective getHeight() and getWidth() methods of RenderedOp.

No matter what type of image manipulation I want to do, I will always need

to write to disk the resulting image. In order to write an image to disk I

will need to know what type of encoding to use as well as the name of the

file to create. Below is the method I created for writing an 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. Using the static JAI.create method

I encode my image by passing it a RenderedOp (the

image), the FileOutputStream I just created, and

the type of encoding to use. It then calls the encode method on my behalf,

which write the image to disk in the appropriate format. Almost all of the

popular image encodings are support except for GIF. Check the JAI documentation

for a list of what encodings are supported. For the most part PNG is an acceptable

substitute for GIF.

Now that I have finished my writeResult method

I have actually gotten image format conversion for free. This is because I

can load an image in any acceptable format and then write it as any of the

supported encodings. For example, I could load a BMP image and then write

it as a JPG thus converting the image from a bitmap to a jpeg.

From here I can create new methods for each additional type of image manipulation

operation I want to support. As stated earlier, I also want to be able to

generate thumbnails, crop images, and create borders. When creating thumbnails

it is often easy to distort the image by not scaling each dimension according

the image’s aspect ratio. For my thumbnail method I am going to accept a single

number that represents what the longest edge of the resulting image should

be. I will then scale the image according to its aspect ratio to the desired

edge length. The following code is my thumbnail method.

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);

}

My first step is to determine if the image is tall or wide. I do this simply

by seeing if the height is greater than the width. From there I create a modifier

value based on the desired edge length divided by the longest edge. Now that

I have my modifier value, I need to create a ParameterBlock to pass to the scale method. My first parameter

is the image source. From there I add parameters for the x and y scale factor.

Notice how I use the same x and y scale factor. This keeps the image from

being distorted. The rest of the parameters aren’t so important for generating

thumbnails and are more useful for scaling operations. If you are interested

in different types of scaling operations the JAI documentation will describe

how these additional parameters can be useful for you. After creating the

ParameterBlock I pass it to the static JAI.create method, which calls scale and returns my result.

For image cropping I decided to have my method crop the same amount for both

the height and width of the image. Thus, my method only takes a single parameter,

how much edge to crop. The code for the method is below.

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, I need to create a ParameterBlock. My first

parameter is the image source. From there I need to add the x and y origins.

The origin is where the cropping should start. Next, I add the width and height

of crop. I determine the width and height by subtracting the origin from its

respective edge. Since I am cropping the same amount for both the height and

the width, I use the same value for x and y and thus for subtracting from

the width and height. Finally, I pass the ParameterBlock

to the static JAI.create method, which calls crop

and returns my result.

Much like the crop method, I decide to have my border method use the same

size border for each side of the image. Besides the width of the border, my

method will also allow a color for the border to be specified. Thus, my method

takes two parameters; the code is as follows.

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, I create a ParameterBlock and set the image

source. From there I add a parameter for each side’s border width. Since I

am using the same border width for all sides, this value is the same. Next

I need to add the border color, which is done with two parameters, a BorderExtenderConstant

and the color. There are more options for border file than using a single

constant color, but that is out of the scope of this article. Again, the JAI

documentation will provide the details of what additional border operations

are possible and how to make use of them. Having created my ParameterBlock,

I pass it to the static JAI.create method, which

calls border and returns my result.

With my Java class complete I can compile it. Make sure you include the JAI

jars in your CLASSPATH before attempting to compile it. Once my Java class

is compiled I simply place in ColdFusion’s CLASSPATH,

so that I can make use of it from my CFC.

To get started on my CFC, I am going to declare three variables in my component

body, iu, loaded, and result.

The code for the three declarations is below.

<cfobject type="java" name="iu" class="ImageUtils" action="create">

<cfset loaded = false>

<cfset result = false>

As you can see, I named my Java class “ImageUtils”

and I am using the <cfobject> tag to create

an instance of it. Since all of my CFC’s methods will be making use of the

ImageUtils class I created the instance in my component body

instead of in an individual method. My two other variables, loaded and result,

are simply booleans representing the state of my

CFC.

Since all my CFC does is wrap my Java class, all the methods are very straight

forward. Each one is included below and briefly explained.

<cffunction name="load" access="public">

<cfargument name="filename" type="string" required="true">

<cfscript>

iu.load(arguments.filename);

loaded = true;

</cfscript>

</cffunction>

The above load method simple passes the file to load directly to ImageUtils

and then sets the boolean loaded to true indicating

that an image has been loaded.

<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 above writeResult method checks to see if a

result has been created 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 above thumbnail method checks to see if an image has been loaded. Then

it calls the ImageUtils thumbnail method and sets

the boolean result to true

indicating that a result has been created.

<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 above crop method checks to see if an

image has been loaded. Then it calls the ImageUtils

crop method and sets the boolean

result to true indicating that a result has been created.

<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 above border method checks to see if an image has been loaded.

Then it calls the ImageUtils border method and sets

the boolean result to true

indicating that a result has been created.

After creating my Java class and associated wrapper CFC, I am now able to

perform image manipulation from ColdFusion with ease. Further, both the Java

class and the CFC are easily extendible to support additional operations that

are implemented with JAI. While creating a wrapper CFC may seem like additional

work for nothing, further additions to the Java class could prove challenging

for ColdFusion to make use of. 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.

Matt Liotta started his development career at the age of twelve by building 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 worked with an early ASP, Cignify, to build their transaction processing system for payroll time data. For this project, Matt created a message queuing system to connect significant bodies of code in C++ and VB with the main application server. He also built a code distribution system for Consumer Financial Networks, as well as the first online account management system for Grizzard Communications. Matt did consulting around San Francisco for companies such as Williams Sonoma and Yipes Communications.

Soon after, he built gMoney's Group Transaction System using an innovative XML messaging architecture for ColdFusion that matches conceptually with the now popular web services paradigm. He also wrote a C++ knapsack algorithm to realize nearly a 20-fold improvement over a similar approach written entirely in CFML. Later at TeamToolz, he designed a highly secure and scalable network architecture for ColdFusion 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 recently founded.

Matt is also a frequent speaker on web architecture:

  • Moving Legacy Applications to the Web (Emory Web Developers Users Group, Atlanta --Feb, 98)
  • The Benefits of Web-based Enterprise Calendaring (Emory Web Developers Users Group, Atlanta -- Aug, 98)
  • Monitoring and Managing Services Remotely Using TAPI (Atlanta Visual Basic Users Group, Atlanta -- Nov, 99)
  • Scalable, Extensible Cold Fusion Architecture (Bay Area ColdFusion Users’ Group, San Francisco; Aug, 00)
  • Scalable, Extensible Cold Fusion Architecture II (CF_Scale Conference, Washington, D.C. -- Nov, 00)
  • Cold Fusion Scalability Panel (CF_Scale Conference, Washington D.C. -- Nov, 00)
  • Introducing CF Espresso (including white paper) (CF_South Conference, Orlando -- Feb, 01)
  • Utilizing Reverse Proxies (Web Services World, San Jose -- Apr, 01)
  • Cold Fusion on Linux (A CF Odyssey Conference, Washington, D.C. -- Jun 01)
  • Architecting Web Services (Web Show 2001, San Francisco -- Sep, 01)
  • Code Techniques in MX Panel (Bay Area ColdFusion Users' Group, San Francisco -- Jul, 02)
  • ColdFusion Cruise, May, 03

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.org Evolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.