CFImage Functionality is Just Awesome!

Finally, ColdFusion 8 has incorporated image manipulation directly into the ColdFusion tag and scripting language. No more are we, as developers, chained to third-party products.

These products, while excellent in quality, just mean adding one more level of complexity to any application that requires server-side image manipulation. Now, with CFImage and an abundance of image-related functions, ColdFusion has done to image manipulation what it has done to almost every other aspect of Web applications development - it has made it simple.

Due to the wide array of image functionality in ColdFusion 8, I am going to try and break this tutorial up into several small and manageable parts. The first part will cover basic reading and writing of images using CFImage and the related image manipulation functions. But, before we get into that, let's just quickly touch upon these new features as a whole.

ColdFusion 8 has given us CFImage. CFImage provides us with tag-based access to only a small subset of the image functionality including:

The ColdFusion 8 CFImage tag and the image-related functions all deal with a new ColdFusion object:

coldfusion.image.Image

Most of you are not going to care about this one all that much, but for those of you who are interested in the underlying Java methods of the coldfusion.image.Image object, I have listed them below (skip past this if you have no idea what I'm talking about). Please note that any underlying methods of the actual Java/ColdFusion objects are found by gathering object metadata and through reflection; none of these methods are documented or officially supported. If you choose to use them, you do so at your own risk and discretion.

For those of you who took a look at the underlying Java methods, you can see that there's a lot of stuff wrapped up in these image objects. The ColdFusion 8 CFImage tag and the related image manipulation functions probably provide some sort of facade that wraps around the underlying methods. For more information about what these do (as they are undocumented), try exploring the java.awt library. Ben Forta stated at the New York ColdFusion Users group that Adobe has built the ColdFusion image manipulation around this library, but that they have also greatly extended it to work around the limits that this library holds.

One of the most exciting features of the new ColdFusion 8 image manipulation is the large number of file formats that can be read in and written out. To give you insight into what is available, ColdFusion 8 has provided two methods: GetReadableImageFormats() and GetWriteableImageFormats(). These return comma-delimited lists of the file types that ColdFusion can deal with.

Calling GetReadableImageFormats() returns:

BMP, GIF, JFIF, JPEG, JPEG 2000, JPEG-LOSSLESS, JPEG-LS, JPEG2000, JPG, PNG, PNM, RAW, TIF, TIFF, WBMP

Calling GetWriteableImageFormats() returns:

BMP, GIF, JFIF, JPEG, JPEG 2000, JPEG-LOSSLESS, JPEG-LS, JPEG2000, JPG, PNG, PNM, RAW, TIF, TIFF, WBMP

Notice that ColdFusion 8 can handle GIF images! Super sweet! I don't know how they handled that one (since I thought Prodigy owned the patent on that or something silly), but it's good to see that we have a huge variety of image formats that we can read and write to.

Now that we have a general overview of what kind of new and exciting image manipulation features are available in ColdFusion 8 and how many image types can be utilized, let's get into the meat of this part of the tutorial: reading and writing images. To start with, let's examine reading images using the CFImage tag. When reading in an image, the source of the image can be any one of the following:

Did anyone else see "URL" and get mentally turned on? 'Cause I did...but, let's not get ahead of ourselves. I want to quickly explore these different source type values and, to do so, I am going to use the INFO action of the CFImage tag. In the first example we are using the CFImage tag in conjunction with an absolute path name to the image.

<!---
Read in the image file. This will read in the binary
image into an object of coldfusion.image.Image.
--->
<cfimage
action="INFO"
source="#ExpandPath( './lady.jpg' )#"
structname="objImageInfo"
/>

<!---
The CFImage tag above stores the image information
into a struct, objImageInfo. Dump out this struct.
--->
<cfdump
var="#objImageInfo#"
label="CFImage : Info Read"
/>

This reads color information, source value, and dimensions into a struct. Running the above code, we get the following CFDump output shown in Figure 1.

The other input types give a similar output, so I won't show the CFDump, however, I will walk through the code. The next one on the list is the Web-relative path:

<!--- Read in the image using a web-relative path. --->
<cfimage
action="INFO"
source="./lady.jpg"
structname="objImageInfo"
/>

This gives us the same output, but the source attribute is "./lady.jpg".

The next input type is a URL (sha-wing!):

<!--- Read in the image using a URL. --->
<cfimage
action="INFO"
source="http://localhost/testing/cf8/lady.jpg"
structname="objImageInfo"
/>

This gives us the same output, but the source attribute is "http://localhost/testing/cf8/lady.jpg". When reading from a URL-based image, you have to be careful about the URL format. It's not just important that the resultant data is image data, it's also important that the last thing in the URL be the file name. For example, using a URL with this query string:

<!--- Read in the image using a URL. --->
<cfimage
action="INFO"
source="http://localhost/testing/cf8/lady.jpg?referrer=bennadel"
structname="objImageInfo"
/>

...will throw the ColdFusion error:

The com image format is not supported on this operating system. Use GetReadableImageFormats() and GetWriteableImageFormats() to see which image formats are supported.


The problem here is that ColdFusion is not recognizing the query string of the URL. This also means that you can't grab URLs that are served by ColdFusion using CFContent. This will only work to direct image URL access. This seems like a bug to me as I would have assumed it was just doing a binary grab via CFHttp (or something like that), but I guess there is more to it than I can understand. I would rather it try to grab the resultant data and throw an exception if the read fails.

Of course, there is a hack to get around this: you can add the image format to the query string:

http://localhost/testing/cf8/lady.jpg?referrer=bennadel&type=.jpg

This will work quite nicely. You get the idea here that ColdFusion is just doing some sort of ListLast() type functionality on the URL to get the file extension. A bit of a hack, but it works if you need it to.

The next input type is a ColdFusion image object. We don't have to perform actions just on new images; we can perform them on existing image objects:

<!--- Read the image into ColdFusion memory. --->
<cfimage
action="READ"
source="#ExpandPath( './lady.jpg' )#"
name="objImage"
/>

<!--- Get the image info from the existing image. --->
<cfimage
action="INFO"
source="#objImage#"
structname="objImageInfo"
/>

The next input type is the BLOB (Binary Large Object Bitmap) and the byte array. I am grouping these two together because I am sure what the difference really is. To me, they are both binary objects (I think). I don't generally deal with these sorts of objects:

<!--- Read in the binary image data. --->
<cffile
action="READBINARY"
file="#ExpandPath( './lady.jpg' )#"
variable="binImage"
/>

<!--- Read the binary directly into an image object. --->
<cfimage
action="INFO"
source="#binImage#"
structname="objImageInfo"
/>

When you read in an image in this fashion, the source attribute is empty.

The next input type is a Base64 encoded string. The CFImage tag has the boolean attribute, IsBase64, which flags the input as being Base64 data. This doesn't mean the target file is a Base64 data text file - this means the actual input value is Base64 image data:

<!--- Read in the binary image data. --->
<cffile
action="READBINARY"
file="#ExpandPath( './lady.jpg' )#"
variable="binImage"
/>

<!---
Read in the Base64 image data. To get Base64 image
data, we can convert the binary read we did above.
--->
<cfimage
action="INFO"
source="#ToBase64( binImage )#"
structname="objImageInfo"
isbase64="true"
/>

I don't deal with Base64 encoded images all that often, but this seems like something that could be very useful when put into clever hands. When you read in an image in this fashion, the source attribute is empty. The Base64 data does not require headers to be used.

For all the above "Read" demos, we were really just getting the info about the image. If you want to actually read the image into a ColdFusion variable (in the form of the coldfusion.image.Image data type), use the action "READ":

<!--- Read image into memory. --->
<cfimage
action="READ"
source="#ExpandPath( './lady.jpg' )#"
name="objImage"
/>

While this reads the image into memory, if you CFDump it out, you'll get the same output as if you were dumping out the INFO action result.

Now that we have covered the tag-based reading, let's examine reading in images using the new ColdFusion 8 image functions. To start with, let's look at ImageRead(). Like the CFImage tag, the ImageRead() function can take a variety of source types:

The ImageRead() function takes this is as the only argument:

<!--- Read in the image using an absolute path. --->
<cfset objImage = ImageRead(
ExpandPath( "./lady.jpg" )
) >

<!--- Read in the image using a web-relative path. --->
<cfset objImage = ImageRead(
"./lady.jpg"
) >

<!--- Read in the image using a URL. --->
<cfset objImage = ImageRead(
"http://localhost/testing/cf8/lady.jpg"
) />


Unlike the CFImage tag, ImageRead() does not have a boolean flag for reading in Base64 data. When it comes to functions, if you want to read in Base64 image data, you have to use the ImageReadBase64() function:

<!---
Read in the binary image data. This will read
the data into a binary object, NOT the ColdFusion
image object.
--->
<cffile
action="READBINARY"
file="#ExpandPath( 'lady.jpg' )#"
variable="binImage"
/>

<!---
Read in the image by converting the binary image
data to a Base64 encoding.
--->
<cfset objImage = ImageReadBase64(
ToBase64( binImage )
) />

In addition to these straightforward methods, ColdFusion 8 can also use the ImageNew() function to read in images. ImageNew() has other features (optional arguments), but for our read/write tutorial, let's just concentrate on the first argument - the image source. Like the methods and tags above, ImageNew() can also take a variety of source types:

We have looked at most of these types above. The only one here that stands out is the Java buffered image. This is the object that the ColdFusion image is wrapped around and is exposed through the function ImageGetBufferedImage(). To demonstrate this, we can grab the buffered image of one image object and use it to instantiate a new image:

<!--- Read in the image. --->
<cfset objImage = ImageNew(
"./lady.jpg"
) />

<!---
Using the buffered image data from the
first image, create a new image.
--->
<cfset objImage2 = ImageNew(
ImageGetBufferedImage( objImage )
) />

This kind of functionality can be super useful if you are interfacing with another Java component that handles image manipulation using the java.awt library and all you have access to is the underlying buffered image.

That just about wraps up reading in image files. Let's quickly cover writing images. As with reading in images, writing them can be done by using the CFImage tag as well as the image functions. The CFImage tag makes writing files especially easy since the source attribute is so flexible. Here, we are going to read an image from a URL and save it to disk:

<!---
Grab the image from the give source URL and save
it to disk (at the relative web path).
--->
<cfimage
action="WRITE"
source="http://farm1.static.flickr.com/240/458712628_2d6eff71e2.jpg"
destination="funny.gif"
overwrite="true"
/>

Notice that the destination path is not an absolute server path - it's a path relative to the current Web page. Also notice that the destination image was a GIF file format. This will automatically convert the image from the JPG that was served at the URL to the GIF image format that we save onto the disk. By default ColdFusion will throw an error if there are naming conflicts. To overcome this, you can set the Overwrite attribute to true (defaults to false) - this will overwrite any existing files of the same name. Now, not only is it super easy to grab images, it's super easy to convert image types.

We used the WRITE action here to grab the image and convert it to a new file format. You can also use the CONVERT action to accomplish just about the same thing. The only real difference (that I can see) between using a straight up WRITE versus CONVERT is that the CONVERT action allows you to also save the image data into a ColdFusion image object:

<!---
Grab the image from the give source URL and save
it to disk (at the relative web path).
--->
<cfimage
action="CONVERT"
source="http://farm1.static.flickr.com/240/458712628_2d6eff71e2.jpg"
destination="funny.gif"
overwrite="true"
name="objImage"
/>

<!--- Write the image to the browser. --->
<cfimage
action="WRITETOBROWSER"
source="#objImage#"
format="jpg"
/>


Here, not only are we converting the image from a JPG format to a GIF image format, we are also storing the image data into the ColdFusion 8 image object, objImage. This also demonstrates our next mode of image writing: writing the image directly to the browser.

Notice that to do this, I am specifying the source of the image (the image object we created) and the output format of JPG. The format attribute can be PNG, JPG, or JPEG but it defaults to PNG. Now, when I first saw this action, I had assumed it was working the same way CFContent worked - by streaming the file to the browser as the only returned content. However, WriteToBrowser actually returns the image inline to the page.

If you look at the source of the page with the rendered inline image, you will see something like this:

<img src="/CFFileServlet/_cf_image/_cfimg892407215213369995.jpg">

If I run that page a few times, I get a variety of different source values:

<img src="/CFFileServlet/_cf_image/_cfimg892407215213369995.jpg">
<img src="/CFFileServlet/_cf_image/_cfimg-6160259686183934424.jpg">
<img src="/CFFileServlet/_cf_image/_cfimg-9120958970657699225.jpg">
<img src="/CFFileServlet/_cf_image/_cfimg-5497181758516755239.jpg">

ColdFusion is actually writing your file to some sort of temporary image storage and then serving it up the way any other image or file would be served up. But what is CFFileServlet? If I look in the root of my ColdFusion 8 test account, there is no such directory. This is some sort of public mapping, but realize this - this is not a ColdFusion request; this is an image file request. Image file requests go the Web server, not the ColdFusion application server. I assume this means that in order for this to work, IIS (or which ever Web server you use) must have a mapping for CFFileServlet to some ColdFusion directory. This makes me nervous. I think it's an awesome feature, but I'm not sure I like having to rely on mappings and tying in with settings external to the ColdFusion application server.

On the flip side, however, I do like this for the very reason that the image request is not going to the ColdFusion server. Serving up images via ColdFusion's CFContent tag is relatively slow and puts a drain on the ColdFusion resources. Storing an image to a temp directly and then serving it using the Web server is going to be 10 times more efficient.

I would like to know more about how CFFileServlet works and specifically how often that directory is cleaned out. I don't want to start writing a lot of images to the browser only to find out that it is clogging up this temp directory. Unfortunately, this directory is not discussed in the ColdFusion 8 CFML Reference manual.

Images can also be written to disk after a variety of CFImage image manipulation actions, but for now, we are trying to stick to just straight up read/write, not manipulation.

When it comes to writing images using ColdFusion 8 image functions, there are basically two options: ImageWrite() and ImageWriteBase64(). ImageWrite() take three arguments (potentially):

Reading an image from a URL and writing it to disk could be done this way:

<!--- Grab the image from the give source URL. --->
<cfimage
action="READ"
source="http://farm1.static.flickr.com/183/392739661_70619076b7.jpg"
name="objImage"
/>

<!--- Write the image to the disk. --->
<cfset ImageWrite(
objImage,
"./waterfall.jpg",
.75
) />

As you can see, since we are grabbing the image via a URL, we must include the destination value. I have also included the JPG quality value, but this was not necessary as that value defaults to .75.

ImageWriteBase64() writes ColdFusion images to text files using a Base64 encoding. It takes four arguments:

Here, we can modify our previous example to read from a URL and store to disk as a Base64-encoded JPG image:

<!--- Grab the image from the give source URL. --->
<cfimage
action="READ"
source="http://farm1.static.flickr.com/183/392739661_70619076b7.jpg"
name="objImage"
/>

<!--- Write the image to the browser. --->
<cfset ImageWriteBase64(
objImage,
"./waterfall.txt",
"JPG"
) />

Just a final note on paths; the Web-relative path (e.g., /lady.jpg) is relative to the currently executing Web page, not to the currently executing ColdFusion template. Therefore, if you are in an included template, you might get files stored in unexpected places if you don't fully grasp this concept.

Well, that's it. That's the quick(ish) overview on how you can read and write image files using ColdFusion 8's new CFImage tag and accompanying image functions. It's awesome that there is such a variety of input and output methodologies. Sorry that this went longer than intended, but this introduction only scratched the surface. ColdFusion's new image functionality is just plain awesome.

•   •   •

This article was reprinted with permission from Ben Nadel's blog:
www.bennadel.com/index.cfm?dax=blog:766.view.

© 2008 SYS-CON Media