Although the Image Component has all of the most requested functionality, from time to time I get requests for features I can’t or won’t implement. Lucky for these users, they can easy to add features to the Image Component themselves! I tend to get two types of feature questions or requests. These can be categorized as follows:
- Does the Image Component write to GIF Files?
- Does the Image Component support a particular type of functionality in a particular way?
Let me address these below.
Does the Image Component write to GIF Files
The short answer to this question is no. (But keep reading!) The Image Component leverages classes provided by the Java platform underlying ColdFusion. The classes which come with Java do not support writing to GIF files so the Image Component does not natively support writing to GIF files.
But wait! You can still write to GIF files, it’s just a little harder.
The Image Component uses a Java BufferedImage internally to store image data. However, you as a user of the Image Component, have access to this data via the getBufferedImage() and setBufferedImage() methods. This means that you can grab the raw data the Image Component is working with, manipulate it and, optionally, put it back into the Image Component. This also means that anything you can do to a BufferedImage you can do with help from the Image Component.
How does this relate to writing GIF images? Well, today I stumbled across this page: http://www.acme.com/java/software/Acme.JPM.Encoders.ImageEncoder.html. This page shows the Java Docs for a freeware Java GifEncoder class. This particular class is part of a larger package provided by ACME Laboratories (no association with Wile E. Coyote). Read the website for more information about ACME.
You may note that the GifEncoder method accepts an Image object and an OutputStream object. Coincidently, Image is the superclass for BufferedImage! An OutputStream is also quite easy to create from ColdFusion. I’ll show you how in a second.
If you download the ACME package and extract it into a Java class path you will have the beginnings of the ability to write GIF images from the Alagad Image Component. I am running ColdFusion on top of JRun, so I extracted the ACME package into my c:JRun4lib directory. You will need to figure out where to extract this package yourself. Don’t forget to restart ColdFusion before continuing.
Once ColdFusion was done restarting, I created a new CFM page and started coding. I started by reading an image:
<!--- create the Image.cfc and read an image ---> <cfset myImage = CreateObject("Component", "Image") /> <cfset myImage.readImage(expandPath("transparentImage.png")) />
The GifEncoder class’s constructor requires an Image object and a Outputstream. So, I added the next few lines of code.
<!--- get the bufferedImage from the Image Component ---> <cfset BufferedImage = myImage.getBufferedImage() /> <!--- create a FileOutputStream (a type of OutputStream) and init to the image to write to ---> <cfset FileOutputStream = CreateObject("Java", "java.io.FileOutputStream").init(JavaCast("string", expandPath("myOtherImage.gif"))) />
Once this code was in place I had everything I needed to write a Gif except a GifEncoder! This next line helped out with that:
<cfset GifEncoder = CreateObject("Java", "Acme.JPM.Encoders.GifEncoder").init(BufferedImage, FileOutputStream) />
Now I had a GifEncoder which was configured to write my PNG image to a GIF file, “myOtherImage.gif”. Just one more line of code to actually do the job:
<cfset GifEncoder.encode() />
The very first time I ran this code it worked like a charm! My transparent PNG was successfully converted to a transparent GIF. Woo Hoo!
So, that’s a good example of how to use getBufferedImage() and a little creative Java-from-ColdFusion to do things which the Image Component can’t itself do.
But, it gets even better! Check out the next example, and be sure to see the last section too!
Does the Image Component support a particular type of functionality in a particular way?
Once again, the short answer to this question is no. But, you guessed it; there is a way to add most any functionality to the Image Component. Here’s a good question I received the other day:
I’m watermarking images with a logo. However, I don’t want to be required to calculate the location for the watermark image each time I do this. Is there any way to simply say “Draw an image in the lower right corner, or the center top of the Image?”
Well, the Image Component does provide a way to watermark images by drawing one image into another with the drawImage() method. However, you’re responsible for the calculations involved in determining where the new Image should be placed.
If you wanted to place an image in the bottom center of another image you could perform the following steps:
- Read the watermark image and get its width and height
- Read the image being watermarked and get its width and height
- Subtract the height of the watermark from the height of the image being watermarked to get the Y coordinate for drawing.
- Subtract the width of the watermark from the width of the image being watermarked and divide that number by two to get the X coordinate.
- Draw the watermark into the image using the drawImage() method, passing it the coordinate information.
This could easily be modified to place the watermark in various locations, but I’ll leave that to you. Here’s the code implementing the steps outlined above:
<!--- create the two image objects ---> <cfset myImage = CreateObject("Component", "Image") /> <cfset myWatermark = CreateObject("Component", "Image") /> <!--- read the image and watermark ---> <cfset myImage.readImage(expandPath("forest1.jpg")) /> <cfset myWatermark.readImage(expandPath("watermark.png")) /> <!--- get the image's width and height ---> <cfset imgWidth = myImage.getWidth() /> <cfset imgHeight = myImage.getHeight() /> <!--- get the watermark's width and height ---> <cfset waterWidth = myWatermark.getWidth() /> <cfset waterHeight = myWatermark.getHeight() /> <!--- get the coordinates to draw into ---> <cfset x = Round((imgWidth - waterWidth)/2) /> <cfset y = imgHeight - waterHeight /> <!--- draw the watermark into the image ---> <cfset myImage.drawImage(expandPath("watermark.png"), x, y) /> <!--- write the new image to disk ---> <cfset myImage.writeImage(expandPath("watermarkedImage.jpg"), "jpg") />
And here’s the resulting image when I run the code:
The Big Picture
Now wouldn’t it be cool if you could add methods to the Image Component to do these two things? Once again, you guessed it; you can! This time it’s simply a matter of extending the Image.cfc file. The following example creates a new CFC named “superImage.cfc” which extends Image.cfc and adds the ability to draw images into the bottom center of your image and allows you to save the images as a GIF file. This is all in addition to everything else the Image Component can already do!
The following code encapsulates all of the functionality discussed above into two methods in a component which extends the Image Component:
<cfcomponent displayname="SuperImage" extends="Image" hint="I add a few methods to the Alagad Image Component."> <cffunction access="public" hint="I write a gif Image!" name="writeGif" output="false" returntype="void"> <cfargument hint="I am the path to write the GIF image to." name="path" required="yes" type="string"/> <cfset BufferedImage=getBufferedImage() var/> <cfset FileOutputStream=CreateObject("Java", "java.io.FileOutputStream").init(JavaCast("string", arguments.path)) var/> <cfset GifEncoder=CreateObject("Java", "Acme.JPM.Encoders.GifEncoder").init(BufferedImage, FileOutputStream) var/> <!--- write the gif image! ---> <cfset GifEncoder.encode()/> </cffunction> <cffunction access="public" hint="I draw an image into the center bottom of the current Image." name="drawImageInCenterBottom" output="false" returntype="void"> <cfargument hint="I am the path of the image to draw." name="path" required="yes" type="string"/> <cfset myWatermark=CreateObject("Component", "SuperImage") var/> <!--- get the image's width and height ---> <cfset imgWidth=getWidth() var/> <cfset imgHeight=getHeight() var/> <!--- declare the watermark's width and height ---> <cfset var waterWidth=0/> <cfset var waterHeight=0/> <!--- declare x and y ---> <cfset x=0/> <cfset y=0/> <!--- read the watermark ---> <cfset myWatermark.readImage(arguments.path)/> <!--- get the watermark's width and height ---> <cfset waterWidth=myWatermark.getWidth()/> <cfset waterHeight=myWatermark.getHeight()/> <!--- get the coordinates to draw into ---> <cfset x=Round((imgWidth - waterWidth)/2)/> <cfset - waterHeight y=imgHeight/> <!--- draw the watermark into the image ---> <cfset drawImage(arguments.path, x, y)/> </cffunction> </cfcomponent>
The following is some code using our new SuperImage component:
<cfset mySuperImage = CreateObject("Component", "SuperImage") /> <cfset mySuperImage.readImage(expandPath("someCrazyImage.png")) /> <cfset mySuperImage.drawImageInCenterBottom(expandPath("watermark.png")) /> <cfset mySuperImage.writeGif(expandPath("myGif.gif")) />
Now how cool is that?! Can you do that with any competing image products? I think not!. No go buy yourself a dozen licenses for Image Component, already!