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!
Comments on: "Add your own functionality to the Alagad Image Component" (8)
Sweet! And just in time for me doing my talk on the Image CFC too.
That just bloody well rawks!
In a ‘type 2’ sort of question way, I was looking at the component the other day with a view to handling .bmp and .wmf files. I noticed the docs mentioned the component supports .png, .jpg etc. and may support more depending on your system setup. Does this mean that if the JAI or something similar is also installed on your machine the Alagad component will magically support more file types if the appropriate API’s are muscled into the JVM?
You’re absolutly right. If you follow the instructions on downloading and setting up the JAI (found here: http://www.alagad.com/index.cfm/name-jaiformats) the new formats will magically start working. Why don’t you download the component from Alagad and give it a try. However, from what I know, the JAI does not have readers for WMF, so you might be required to implement something akin to "type 1" above.
I purchased a license for the Algad Image Component about a month ago because I thought it could manipulate .gif images. I didn’t read the fine print so I was unhappy to find that this Image Component could only READ gifs.
I was very happy to find this fix for the problem I am encountering but I’m having some problems getting it to actually work. I’m getting this error:
too many colors for a GIF
The error occurred in SuperImage.cfc: line 12
11 : <!— write the gif image! —>
12 : <cfset GifEncoder.encode() />
13 : </cffunction>
I get this error with a .png as well as with .jpegs. I can read and write a .gif image just fine. Anyone know why I am getting this error?
I’d imagine it’s down to the fact that your .png or .jpg is more than 256 colours, which is all .gif can handle I believe. Hence the error.
Shawn, Sorry about your issues with the Image Component not supporting GIF images. Unfortunately, it’s a limitation of the Java APIs I’m using and not of the Image Component itself.
I really don’t know anything about the error you’re seeing. The examples in this entry were thrown together in about 15 minutes. I simply downloaded the GIF encoder, read the docs, and slapped together some quick code.
I’m sure that with a little Googling you would be able to find either a work-around to this problem or a new encoder to use.
I love the stuff above and have tried writing a small cfc to extend Alagad in the way you have described above. However, I am having a strange problem…
I create an object using the extended cfc, but it is missing the rotate function. All the other functions seem to be there and work in the same way, but the rotate function is suddenly missing… very strange.
I am currently using the trial version of the Alagad component, so am not sure if that has anything to do with it.
Have you experienced a problem like this or can anyone replicate this problem??
Update to the comment above. This appears to be caused by a variable called rotate existing within the image.cfc. When the component get extended, the variables.rotate is inherited and not the function called ‘rotate’. I guess that this is some sort of naming conflict.