Image Manipulation with Open-Source Image.cfc
Posted on Apr 03, 2006
A recent post on Jason Sheedy's weblog asked about java image manipulation. In the comments, I stated that I recommended the Alagad Image Component if you could pay for the component. I also have discussed using tmt_img, but while tmt_img is free and the source is viewable, it is not redistributable and therefore not true open-source. The other option I mentioned I had not yet used, and that was Rick Root's image.cfc which is licensed under the BSD Open Source, which does allow redistribution with minor conditions. With this in mind, I decided to run some simple tests using image.cfc, with varying levels of success, that I will discuss here (you can see the results here - please note that it does not recreate images that have already been successfully created).Much of my tests were similar to the examples page, which serves as the primary documentation for image.cfc. However, opening and reading the methods in image.cfc doesn't require any specialized knowledge to understand for methods where documentation might be somewhat lacking at the moment. I decided to test out some of the most commonly used methods, which would be determining an image's size, resizing/scaling and cropping. I also tried testing multiple methods on a single image with the scalex() and addText() methods. The sizing, resize, scale and crop tests were all successful and very easy to acheive. The last test was not, and I have not yet determined the cause. First let's look at how you would use image.cfc to get an image's size:
<cfset imageCFC = createObject("component","com.image") /> <P>
<!--- use the getImageInfo(imageObj,inputFile) function to get the image height and width ---> <cfset strImageInfo = imageCFC.getImageInfo('',expandPath('img/cimg2399.jpg')) /> <cfoutput> <div style="width:#strImageInfo.width#px;float:left;margin:10px;"> Orginal image:<br /> <img src="img/cimg2399.jpg" /><br /> Width: #strImageInfo.width# pixels<br /> Height: #strImageInfo.height# pixels </div> </cfoutput>
The first line should be obvious, it just instantiates the image component (so I won't repeat that in below code examples). Next, I just set a structure from the built-in getImageInfo() method. The arguments for nearly all methods of image.cfc expect either an image object (1st argument) or a path to an image (2nd argument). The image object would be if you were doing multiple methods on a single image (which I will discuss later), otherwise this will always just be blank (in cases where you do use it, the 2nd argument for the inputFile/path would be blank). That was pretty painless and effective. Now let's look at resizing.
<!--- use the resize(imageObj,inputFile,outputFile,newWidth,newHeight) function to get the image height and width ---> <cfset imageCFC.resize('',expandPath('img/cimg2399.jpg'),expandPath('img/cimg2399_resize.jpg'),150,200) /> <cfset strImageInfo = imageCFC.getImageInfo('',expandPath('img/cimg2399_resize.jpg')) /> <cfoutput> <div style="width:#strImageInfo.width#px;float:left;margin:10px;"> Resize to 150 x 200:<br /> <img src="img/cimg2399_resize.jpg" /><br /> Width: #strImageInfo.width# pixels<br /> Height: #strImageInfo.height# pixels </div> </cfoutput>
Again, the resize method takes either a image object or an inputFile. The third argument in nearly all the methods of image.cfc is the file path of the file you wish to export (it will either create the file or overwrite an existing one). Then it just takes a new width and new height (a jpegCompression level is an optional last argument, but is defaulted to 90). Again this is pretty straightforward stuff (note: I recall the getImageInfo() method on the created image to test the resulting dimensions on the actual image created, this could be done also using the image object returned by the resize call). One note is that the scale methods I discuss below are actually just calls to the resize method with the width or height set to 0, which causes the resize method to calculate the scaled width/height. Therefore you could choose to scale using the resize method, however using the scalex() or scaley() methods makes for a slightly easier method call (and makes the code a little more readable I suppose). So let's look at a scale.
<!--- use the scaleX(imageObj,inputFile,outputFile,newWidth) function to get the image height and width ---> <cfset imageCFC.scaleX('',expandPath('img/cimg2399.jpg'),expandPath('img/cimg2399_scaleX.jpg'),150) /> <cfset strImageInfo = imageCFC.getImageInfo('',expandPath('img/cimg2399_scaleX.jpg')) /> <cfoutput> <div style="width:#strImageInfo.width#px;float:left;margin:10px;"> Scale to 150 width:<br /> <img src="img/cimg2399_scaleX.jpg" /><br /> Width: #strImageInfo.width# pixels<br /> Height: #strImageInfo.height# pixels </div> </cfoutput>
At this point, this should be self-explanatory. The important part here (at least to me) is that in my (admittedly, extremely limited) test, the scale performed quickly and with minimal quality loss (obviously, your results may vary depending on the size and colors of your starting image). So, without further ado, here is a crop:
<!--- use the crop(imageObj,inputFile,outputFile,fromX,fromY,newWidth,newHeight) function to get the image height and width ---> <cfset imageCFC.crop('',expandPath('img/cimg2399.jpg'),expandPath('img/cimg2399_crop.jpg'),0,0,150,150) /> <cfset strImageInfo = imageCFC.getImageInfo('',expandPath('img/cimg2399_crop.jpg')) /> <cfoutput> <div style="width:#strImageInfo.width#px;float:left;margin:10px;"> Crop top-left 150x150:<br /> <img src="img/cimg2399_crop.jpg" /><br /> Width: #strImageInfo.width# pixels<br /> Height: #strImageInfo.height# pixels </div> </cfoutput>
In this case, you specify the starting coordinates within the image (I used the top left corner, i.e. 0,0) and then how many pixels wide and high from that point. This should be familiar to anyone who has used image editing software, so I will move on to the issues I ran into.
My next test tried to run multiple methods on an image to first scale it and then add text to it. The code looked like this:
<!--- use multiple operations to resize the image and then add some text ---> <cfset strImage = imageCFC.scaleX('',expandPath('img/cimg2399.jpg'),'',125) /> <cfset strFont = structNew() /> <cfset strFont.size = 12 /> <cfset strFont.color = 'white' /> <cfset imageCFC.addText(strImage.img,'',expandPath('img/cimg2399_multiple.jpg'),0,0,strFont,'Luke - 3 yrs old') /> <cfset strImageInfo = imageCFC.getImageInfo(strImage.img,'') /> <cfoutput> <div style="width:#strImageInfo.width#px;float:left;margin:10px;"> Scaled to 125 width and text added:<br /> <img src="img/cimg2399_multiple.jpg" /><br /> Width: #strImageInfo.width# pixels<br /> Height: #strImageInfo.height# pixels </div> </cfoutput>
In this case, I actually set the result of the method call into a variable so that I can use the image object to pass it to the next method. I also create a structure for the font (based upon the example from the example page), then I pass the image object and the font structure to the addText method (along with the placement coordinated 0,0 and the text I want to write). You will notice that in this case I actually call the getImageInfo() method on the image object. I do this because regardless of what I did, I could not get the file to be written as it should in the addText call. However, the getImageInfo() does appear to return the correct dimensions for the resized image. Since I could not get this to work, I decided to (almost) exactly recreate the example from the examples page (only changing the image to my own, which I tested both as a jpg and png) like so:
<cftry> <cfset results12 = imageCFC.scaleX("", expandPath('img/cimg2399.png'), "", 100)> <cfset results13 = imageCFC.flipVertical(results12.img, "", "")> <cfset results14 = imageCFC.flipHorizontal(results13.img, "", "")> <cfset fontDetails.size = 15> <cfset fontDetails.color="black"> <cfset results15 = imageCFC.addText(results14.img, "", expandPath('img/cimg2399_test.png'), 5, 5, fontDetails, "Sample Text")> <cfcatch type="any"> <cfoutput> <div style="width:150px;float:left;margin:10px;"> <p>#cfcatch.Message#</p> <p>#cfcatch.Detail#</p> <p>#cfcatch.rootCause.cause.message#</p> </div> </cfoutput> </cfcatch> </cftry>
On my test page you will see the resulting error information to the far right ("Object Instantiation Exception."). This is the error I get when I used a png. I believe testing a jpeg didn't give this error, it just never did anything at all that I could see (i.e. the file was never written, just as in my own test above). Now there could be some form of user error, and hopefully Rick or someone else can point out my mistake, but I wanted to share the issues nonetheless in case others encounter it (please keep in mind I am not meaning this as a criticism per se, I am, as always, very grateful of the time and effort that others put into these projects as well as their generosity in making them free to the rest of us all).
Even with the above issues in mind, the component performed nicely for the other methods (with image quality on par with the "competition"), and those will probably be enough to suit a large number of applications. In fact, we are already using Image CFC on an small application at Hasbro that needed only these basic functions. I also think it is worth keeping an eye on this project as it matures even if you need some of the more advanced features.
For the addText() method to work you need to specifiy a font.fontFile and point to it in the file system.
In my case it was:
<set name="font.fontFile" value="/usr/local/NewAtlanta/BlueDragon_Server_62/jre/lib/fonts/LucidaTypewriterBoldOblique.ttf" />
If you are rocking linux make sure you have xorg-x11 installed.
Posted By Nick Maloney / Posted on 04/18/2006 at 12:39 PM
Just wanted to point out that the code does work, the issue that I had with it not writing the file with the addtext function was the path to my font file. Please reference the following link for more details on how to scaleX an image and addtext to it all with passing the image object to the next function: http://www.easycfm.com/forums/viewmessages.cfm?Forum=12&Topic=9698
In that discussion, I posted it originally looking for help with the samples posted on this site. I played with it for a few hours until I figured out the problem and how to actually get it all to work.
Thanks for posting the code samples, they really got me started with image.cfc which I plan on converting old code that used open source custom tags over to this.
Posted By H Jaber / Posted on 07/25/2006 at 7:02 PM
Just to put it out there ... did you run into any issues with IMAGE TYPE = 0?
I am trying to test various things with imageCFC but in particular Watermarking. It will not let me do it as it throws an error when Image Type is 0. Even the convert function does not seem to be able to fix this issue. I'm getting Image Type = 0 for PNG, JPG, GIF ... I'm sort of hoping it's something I'm not doing right!
Very frustrated :)
Posted By Steve / Posted on 07/31/2007 at 7:03 PM
In reference to the Exception you get on your demo page:
"Element WIDTH is undefined in STRIMAGEINFO."
I get the same sort of error if I try and reference the source image directly like this:
<cfset imgInfo = imageCFC.getImageInfo("","myImage.jpg") />
But if I change that toa full path:
<cfset imgInfo = imageCFC.getImageInfo("","C:\MyImages\myImage.jpg") />
Not sure if this helps you at all.
Posted By Steve Glachan / Posted on 07/31/2007 at 7:30 PM
Sorry last post :)
I am now working with iEdit and it has been great for the purposes I needed it for plus it is very easy to use with plenty of useful functions:
Posted By Steve Glachan / Posted on 08/01/2007 at 9:12 PM