Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
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 like CFX Image, Alagad Image component, and Magik Tag. These products, while excellent in quality, just meant adding one more level of complexity to any application that required 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 most 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 management 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:
- Retrieving information about an image.
- Reading an image into memory.
- Resizing an image.
- Rotating an image.
- Adding a border to in image.
- Converting an image from one file format to another.
- Creating a CAPTCHA image.
- Writing an image to a file.
- Writing an image to the browser.
In addition to the CFImage tag, ColdFusion has introduced dozens of new image manipulation functions including:
- ImageAddBorder()
- ImageBlur()
- ImageClearRect()
- ImageCopy()
- ImageCrop()
- ImageDrawArc()
- ImageDrawBeveledRect()
- ImageDrawCubicCurve()
- ImageDrawLine()
- ImageDrawLines()
- ImageDrawOval()
- ImageDrawPoint()
- ImageDrawQuadraticCurve()
- ImageDrawRect()
- ImageDrawRoundRect()
- ImageDrawText()
- ImageFlip()
- ImageGetBlob()
- ImageGetBufferedImage()
- ImageGetEXIFTag()
- ImageGetHeight()
- ImageGetIPTCTag()
- ImageGetWidth()
- ImageGrayscale()
- ImageInfo()
- ImageNegative()
- ImageNew()
- ImageOverlay()
- ImagePaste()
- ImageRead()
- ImageReadBase64()
- ImageResize()
- ImageRotate()
- ImageRotateDrawingAxis()
- ImageScaleToFit()
- ImageSetAntialiasing()
- ImageSetBackgroundColor()
- ImageSetDrawingColor()
- ImageSetDrawingStroke()
- ImageSetDrawingTranspare()
- ImageSharpen()
- ImageShear()
- ImageShearDrawingAxis()
- ImageTranslate()
- ImageTranslateDrawingAxi()
- ImageWrite()
- ImageWriteBase64()
- ImageXORDrawingMode()
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 any of you that 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 am talking about). Please note that any underlying methods of the actual Java/ColdFusion objects are found by gathering object meta data 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.
addBorder( int, java.lang.String, java.lang.String )
returns: void
blur( int )
returns: void
brighten()
returns: void
clearRect( int, int, int, int )
returns: void
copyArea( int, int, int, int )
returns: coldfusion.image.Image
copyArea( int, int, int, int, int, int )
returns: coldfusion.image.Image
crop( float, float, float, float )
returns: void
draw3DRect( int, int, int, int, boolean, boolean )
returns: void
drawArc( int, int, int, int, int, int, boolean )
returns: void
drawCubicCurve( double, double, double, double, double, double, double, double )
returns: void
drawLine( int, int, int, int )
returns: void
drawLines( [I, [I, boolean, boolean )
returns: void
drawOval( int, int, int, int, boolean )
returns: void
drawPoint( int, int )
returns: void
drawQuadraticCurve( double, double, double, double, double, double )
returns: void
drawRect( int, int, int, int, boolean )
returns: void
drawRoundRect( int, int, int, int, int, int, boolean )
returns: void
drawString( java.lang.String, int, int, coldfusion.runtime.Struct )
returns: void
flip( java.lang.String )
returns: void
getBase64String( java.lang.String )
returns: java.lang.String
getClass()
returns: java.lang.Class
getColor( java.lang.String )
returns: java.awt.Color
getCurrentGraphics()
returns: java.awt.Graphics2D
getCurrentImage()
returns: java.awt.image.BufferedImage
getExifMetadata( javax.servlet.jsp.PageContext )
returns: coldfusion.runtime.Struct
getExifTag( java.lang.String, javax.servlet.jsp.PageContext )
returns: java.lang.String
getHeight()
returns: int
getImageBytes( java.lang.String )
returns: [B
getIptcMetadata( javax.servlet.jsp.PageContext )
returns: coldfusion.runtime.Struct
getIptcTag( java.lang.String, javax.servlet.jsp.PageContext )
returns: java.lang.String
getSource()
returns: java.lang.String
getWidth()
returns: int
grayscale()
returns: void
info()
returns: coldfusion.runtime.Struct
initializeMetadata( javax.servlet.jsp.PageContext )
returns: void
invert()
returns: void
overlay( coldfusion.image.Image )
returns: void
paste( coldfusion.image.Image, int, int )
returns: void
readBase64( java.lang.String )
returns: void
resize( java.lang.String, java.lang.String, java.lang.String )
returns: void
resize( java.lang.String, java.lang.String, java.lang.String, double )
returns: void
rotate( float, float, float, java.lang.String )
returns: void
rotateAxis( double )
returns: void
rotateAxis( double, double, double )
returns: void
scaleToFit( int )
returns: void
scaleToFit( java.lang.String, java.lang.String, java.lang.String )
returns: void
scaleToFit( java.lang.String, java.lang.String, java.lang.String, double )
returns: void
setAntiAliasing( java.lang.String )
returns: void
setBackground( java.lang.String )
returns: void
setColor( java.lang.String )
returns: void
setDrawingStroke( coldfusion.runtime.Struct )
returns: void
setDrawingStroke( float, int, int, float, [F, float )
returns: void
setRenderingHint( java.awt.RenderingHints$Key, java.lang.Object )
returns: void
setTranparency( double )
returns: void
setXorMode( java.lang.String )
returns: void
sharpen( float )
returns: void
sharpenEdge()
returns: void
shear( float, java.lang.String, java.lang.String )
returns: void
shearAxis( double, double )
returns: void
translate( int, int, java.lang.String )
returns: void
translateAxis( int, int )
returns: void
write( java.lang.String, float )
returns: void
writeBase64( java.lang.String, java.lang.String, boolean )
returns: void
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 these underlying methods. For more information about what these do (as they are undocumented), you might want to 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 large number of file formats that can be read in and written out. To give you insight into what is available, ColdFusion 8 had 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 an write to.
Now that we have the 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 actually 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:
- Absolute path name.
- Path name relative to the web root.
- URL.
- CFImage variable
- BLOB / Byte array.
- Base64 encoded image data.
- Binary object.
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 in color information, source value, and dimensions into a struct. Running the above code, we get the following CFDump output:
The other input types give a similar output, so I will not 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 is not just important that the resultant data is image data, it is 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 cannot 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 can get the idea here that ColdFusion is really 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 in 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 will 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 take a look at ImageRead(). Like the CFImage tag, the ImageRead() function can take a variety of source types:
- Absolute path name
- Web relative path name
- URL
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 straight forward 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:
- Absolute path name.
- Web relative path name.
- URL.
- ColdFusion image variable.
- A BLOB / byte array.
- A Java buffered images.
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.
So that just about wraps up reading in image files. I know this post is waaaay to long, so let's quickly cover writing images. As with reading in images, writing them can be done both 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://some-image-domain/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 at the same name. Now, not only is it super easy to grab images, it is super easy to convert image types.
We used the WRITE action here to grab the image and convert it to 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 vs. 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://some-image-domain/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 either 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 that 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 am not sure I like have 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 very slow and puts a drain on the ColdFusion resources. Storing an image to a temp directly and then serving is 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 strait 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):
Name - The name of the ColdFusion image object you are going to write.
Destination - The absolute or relative path name to which you are going to write the image. This argument is optional if the Source attribute of your image is available. So, for instance, if you read in the image locally via CFImage and then called ImageWrite() without a destination, it would overwrite the original image based on the internal Source. However, if you grabbed the image from a URL via CFImage, you must include a destination value with ImageWrite() as you cannot use the Source value (external URL) to store the image.
Quality - A value between 0 and 1 that can be used to set the JPG quality.
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://some-image-domain/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:
Name - The name of the ColdFusion image object that we are writing to disk.
Destination - Since the destination here is not an image but a text file, the destination is required (the internal Source of the image will not help us).
Format - This is the format of the image that we are going to encode. This is required otherwise ColdFusion will not know which headers and conversion algorithm to include.
InHTMLFormat - This specifies how the data is going to be written the text file. If you specify false (default) or omit the argument, the Base64 is written to the file without any headers. If you specify true, the Base64 encoding is written to the disk with HTML-style headers.
Here, we can modify our pervious 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://some-image-domain/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 (ex. ./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 do not 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.
Want to use code from this post? Check out the license.
Reader Comments
A couple of notes on the generated images and CFFileServlet...
The images are created in <cf8root>/tmpCache/CFFileServlet/ - and then either in _cf_image/ or _cf_captcha/ (when you get on to your CAPTCHA tutorial!).
Images are cleared out of the temp directory 5 minutes after they are created.
They are not served as a web server request - if you stop the ColdFusion8 service, the image is not accessible. But it's not a normal CF mapping either, as they are only accessible from within a CFML page...
My guess - going by the fact the directory is called CFFileServlet - is that there's a tiny Java servlet that delivers the temporary file that's been created, probably with much lower overhead than if it were served via CFContent.
@Ben:
The name incicates this, but the CFFileServlet isn't a virtual folder, but it's a servlet defined in the web.xml file.
My guess is images are stored in memory, and when written to the browser removed from memory. They could be going into cache folder though.
It looks like Seb's done a little more research into storing. My guess is the only thing the servlet does is stream the file to browser. Very simple low overhead. It also shouldn't count as a "Simulataneous Thread" in the admin.
Servlets are executed at the JRun/Servlet Engine level.
I am not familiar with servlets, so sorry if my information is inaccurate. It was just conjecture. As far as it only being available in the page request, this is not correct. After my page rendered, I was able to to call those images directly from the URL after the page had fully rendered. It appears now, however (some hours later) that they are no longer available.
So, at least it's good to know that this directory is being cleared out on a regular basis.
I am not familiar with this web.xml file. Is that something that IIS would be looking at? Does ColdFusion 8 update that upon install?
Yes, you can call the images directly from the URL, but not if you stop the CF application service...
@Seb,
That is interesting. This area of the server is not in my current field of knowledge. Thanks for the insights.
Don't know much about it myself - just what I deduced from poking and prodding!
Nice tutorial Ben!
Hi Ben,
Good stuff.
FYI - I filed and fixed a bug (#69715) on the handling of the extensions when using a URL as an image source. We now correctly parse the format from the path of the URL, but more importantly we do not filter the URL based on the extension of the request. This should allow you to hit a .cfm page that returns an image, or any other non-image extension. Use with care however, as anything that isn't an image will throw an exception.
Look for this in the Final ColdFusion 8 release.
Keep up the great posts!
Tom
Adobe CF Team
@Tom,
That is awesome to hear! I know other people have mentioned how awesome it is that you guys on the Adobe team are actually fixing things that people point out in the public Beta. Rock on!
Hey, if you get a chance, I would love for you to take a quick glance at Part II of this series. I was finding that CFImage, when reading in a variable and then overwriting itself, was not actually overwriting itself. This feels like a bug to me, but I am also not sure how CFImage works behind the scenes.
Thanks a lot.
The initial implementation of ImageWriteToBrowser was like what you had imagined i.e; we were streaming the content to the browser. This had few limitations which were really huge.
- you could have only one image in the response.
- There was no way to have any other content in the response if ImagewriteToBrowser was used.
- In real world application, where you want to modify an image and send in the response along with other text/html content, this model was forcing the users to save the image object on disk and create a url, stuff it in the page and then delete that image once it is served. This was way too much work to be handled by CF developers.
Hence we changed it to make it more usable :-)
When you use ImageWriteToBrowser, we create a temporary image on your behalf, create a URL, stuff it in the page and send the response. We take care of deleting this temporary file. As Dan and Seb rightly pointed out, this temporary image is served by CFFileServlet which has a very low overhead and should not concern you.
@Seb, How did you figure out all about CFFileServlet ? :-)
@Rupesh,
I think your choice is 100% well done. It makes it much much much more usable. The only thing would be cool is if the CFImage[ action=WriteToBrowser ] could take a CLASS attribute that could go into the IMG tag for CSS. But otherwise, this is a hugely cool feature.
While I'm very happy to see image manipulation finally make its way into the core language, I was actually hoping that the image functionality would make it's way in the form of an object/component.
While it would have been a new concept to the language, it would have made perfect sense to introduce the first native object/component.
Image manipulation makes perfect sense for OO programming.
Hi,
I'm playing around with the ImageResize()-function and get the feeling it doesn't work a hundred percent. I use it like
<cfset myImage=ImageNew(img)>
<cfset ImageResize(myImage,"480","","highestQuality", 1)>
But sometimes end up with an image with a width of 479 pixels - one pixel less then intended. Have you noticed this behaviour?
Thanks for your blog,
Bard
@Bard:
If I were to guess at the problem, since you're not specifying a height it's an issue relating to keep the issue proportional. If you're height is an odd number, than there may be some offsetting to keep proportional values correct.
Is this a random occurrence or is it consistent with certain images? If it's random resizing the same exact image, than that looks like a bug. If it's consistent with certain images, it could be what I suggested.
@Brad,
I am gonna have to go with Dan on this one. In fact, that's a "bug" that I even come across in real graphics editors such as FireWorks or Photoshop. When I try to proportionally resize a canvas in FW or PS, 99.99% of the time it works great, but very rarely is it actually a pixel off.
I have the same issue here.
When I resize a picture to, let's say, 320px width and let height="" to go proportionally with it, I often get a picture of 319px width. This happens almost every time with resizing, the one pixel difference.
How hard can it be to really resize the picture to 320px and then let the height difference 1 pixel more or less? It's the width we specify so that one at least should be the right size...
When using the CFIMAGE captcha function, I am getting a broken image. The source code reads as this:
<imgsrc="/CFFileServlet/_cf_image/_cfimg892407289798721295.png">
Why is this failing to load? Is it a mapping issue? And how is this mapped?
@Brian,
It looks like the HTML is bad:
"imgsrc"
There should be a space there. Are you stripping out white space or anything of your generated content?
Hi Ben,
Thanks for the idea. Actually it is my fault when I copied over the <img src> tag. In my actual page it is correct, just failed to do it correctly when I posted on here. So the search continues.
Brian
@Brian,
Run the page and then try to copy and paste the IMG src value into the browser URL. What comes up? Maybe it will give you a better error message.
@Ben,
Not sure what you mean. I dont think we want to use the URL. We arent really getting any error messages, except when we use Absolute or Relative paths in the destination attribute.
I notice most people who use this avoid destiantion all together. They let the system serve the file up through the CFfile Servlet, but for us it wont work.
Brian
@Brian,
Sorry, maybe I didn't understand what the error was. Are you getting an "image not loading" type error", or are you getting a ColdFusion error? What is the code that creates the WriteToBrowser action?
@Ben,
Sorry I didn't really make it clear. There is no error. The image path is broken. It cannot find the CFFile/Servlet/ path. But maybe you are on to something. Do you have to use the writetobrowser() fucntion to get this to work? At this time we are only using <cfimage action="captcha">
@Brian,
That is too strange. I am not sure what could even be causing your problem.
@Ben
We actually just solved the problem. We never created a virtual directory on the server to map to the temp folder. Thanks for the help. It is working really nice.
Thanks,
Brian
@Brian,
Glad you got it working. Sorry I couldn't give you any real advices.
I also had the broken image error and creating the virtual directory to the 'CFFileServlet' folder worked.
Hi All
Ref the pixel difference issue. I found it happening and wow is it anoying. I suppose the better things are - the more you notice the small shortcomings.
I'm trying to get things in a 700x700 window.
I have an image 685x909, when I use cfimage to resize it goes to 525 and 699 rather than 700.
In photoshop it goes to 700 x 525. I don't have fireworks.
I used to use the alagad image cfc to do everything (http://www.alagad.com/go/products-and-projects/image-component) - which, when I reverted back to - resized it to 525x700 fine....
Hopefully it's a characteristic that will change - after all, some of us retentives like things pixel perfect.
M@)
I have this 1 pixel error problem all the time.
In one example, I'm resizing a 600 x 600 image, and I specifically tell cfimage to resize to 338 x 338.
What I get, is 337 x 337 - every time.
Photoshop can manage this with no problems at all... no-one seems to have an answer.
I saw somewhere in the CF docs that you can get around this bug by adding an interpolation method, but it doesn't make any difference for me.
@Matt, @Gary,
Yeah, the pixel perfect stuff is definitely irritating. Luckily, unless you are doing something where the photo sizes are mission critical, it's generally noticeable. I hope they fix this in the next release.
Unfortunately, it's a serious problem for me. In order to adhere to the design's vertical rhythm, I have to specify the exact image size in the css file... This means that I'm using the browser to resize the image to the extra pixel, which makes product images look blurry.
I just don't understand why the problem occurs, surely CFImage is using the same resize engine as Photoshop?
Gafware's CFX_Image doesn't have this problem, but I'm trying to move everything over to CFImage for portability, as not all hosts will allow me to install the custom tag - and previous versions have a colour rendering issue in IE8.
Does anyone know if Railo has the same issue?
For all having the pixel problem when resizing an image: an easy 'hack' is to add a decimal point to the new image size. this seems to fix the 'missing pixel':
<cfset ImageResize(myimage, 338.1, 338.1)>
Azadi
@Azadi,
Really? Awesome tip. Obviously, we shouldn't "have" to do that, but it's good to know that that fix is there. And since we can't have a decimal be a decimal size, I don't think there can be any side-effects.
How can i display an image from a byte array (or say byte object) in coldfusion.
@Tola,
The CFImage and ImageNew() can take a byte array as their source value I think, which will be converted internally to a ColdFusion image object. Then, just use CFImage to WriteToBrowser.
@Azadi,
Thanks for the fix, saved my skin with this. Figured it was some sort of rounding error.
@Ben,
Here I am again! lol
@Jon,
Glad to have you here :)
Hi Ben,
Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-shear-mastery
See the Pet's Cup example at the bottom?
Would I use Shear for this and does the shear supported by CF have the ability to actually create an authentic wrapping around effect?
Any direction on this would be very much appreciated!
@Darren,
I think that's way beyond what ColdFusion's basic image manipulation can do at this point.
Anyone know if there's a way to get the number of pages in a .tiff file with any of CF's image functions?
Thanks
@Jeremy,
I believe that the ImageUtils.cfc has a method for getting the page count from TIFFs:
http://imageutils.riaforge.org/
Hi Ben,
Good Tutorial. I follow you a lot and love your blog site. I could not help mentioning one thing; if you are resizing or copying image from a just uploaded file and if the source image file is large (even a 500kb), you will get an error due to the fact that CF can not process it fast enough.
So, you have to kinda give a break using SLEEP() in the process after the image processing has been done.
For example,
<cfimage action="read" source="http://www.example.com/image.jpg" name="myImage">
<cfset ImageScaleToFit(myImage,575,431,"bilinear")>
<cfimage source="#myImage#" action="convert" destination="newImage.jpg">
<cfset sleep(10) />
<image src="newImage.jpg">
Here, I gave 10 ms break after the resize process and it is fantastic! 10 ms can handle up-to 4-5 meg image file resizing.
Thanks for reading.
@Neyadhish,
Very peculiar. I'm trying to think back if I've had to deal with large photos. I think I have, but not sure I rand into this error.
What kind of requestTimeOut do you set in your CFSetting tag? I wonder if the Sleep() is somehow messing with the allowed timeout.
@Ben,
It is. But I have tested again and sleep() seem to be keeping windows hanging. As a result, multiple people were having issues uploading and removing images when they are trying to do the image processing simultaneously.
So, I removed sleep() and it seems to be working for the large files now. I have no explanation.
I have an issue though; I can upload image resize it using cfimage, but for some reason some files get locked and I am unable to remove them for a while. Probably CF8 fix issue.
I normally do not set CFSetting tag requestTimeOut for image uploads due to variable image uploading time.
@Neyadhish,
I have run into random locking issues on files; but, unfortunately I can't think of how I solved things. Usually, I think I just put the file delete at the end of the algorithm. Usually, I can read a file in, and then do some stuff to the in-memory data to give a bit of a buffer before deleting the file.
I am having the same issue regarding the locking of the image file, any ideas on how to unlock so I can delete it if necessary?
I import image URLs from suppliers and usually just pass them on to my shopping site but oddly sometimes the image has a .jpg extension but is actually a BMP image. They display ok in the browser but a separate site that does the thumbnailing produces an error because they aren't a JPG.
I don't want to read in every image regardless of its actual format and have to store a local copy which is correctly converted to JPG.
Has anyone found anyway to determine the ACTUAL format of an image regardless of its extension?
ImageInfo() does specify the actual format.
Sorry that last line should say "ImageInfo() does NOT specify the actual format."