Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with:

Reminder: Duplicate() Does Not Work With ColdFusion Images

By Ben Nadel on
Tags: ColdFusion

Someone contacted me this morning needing help debugging an image thumbnailing problem. Part of the issue that they were running into was that the Duplicate() function did not seem to work with ColdFusion image objects. This can be confusing since the documentation seems to indicate that it will work:

Task: Create a ColdFusion image from another ColdFusion image.

Functions and Tags: ImageCopy function with the ImageWrite function or the Duplicate function, or by passing the image to the ImageNew function or the cfimage tag.

This language is quite awkward and a little ambiguous; but, it does seem to state that there are many ways to duplicate a ColdFusion image object. Unfortunately, most of these don't actually work - at least not in the way that I have interpreted them.

Let's run though a few demos. First, we are going to read in a picture of the beautiful, sexy, two-time Olympic Gold medalist, and "Dancing With The Stars" celebrity, Misty May. Then, we are going to duplicate Misty May's image using ColdFusion's Duplicate() method. Then we are going to add a caption to both images and write them to the browser:

  • <!--- Read in the image of the very cute Misty May. --->
  • <cfimage
  • action="read"
  • source="./misty_may.jpg"
  • name="objMistyMay"
  • />
  •  
  • <!---
  • Now that we have the clean Misty May picture in memory
  • as a ColdFusion image objects, let's duplicate it.
  • --->
  • <cfset objMistyMayDuplicate = Duplicate( objMistyMay ) />
  •  
  •  
  • <!--- Add a caption to the ORIGINAL image. --->
  • <cfset ImageAddCaption(
  • objMistyMay,
  • "Dang, she is wicked cute!"
  • ) />
  •  
  • <!--- Add a caption to the DUPLICATE image. --->
  • <cfset ImageAddCaption(
  • objMistyMayDuplicate,
  • "Even her duplicate is hot!"
  • ) />
  •  
  •  
  • <!--- Write ORIGINAL image to the browser. --->
  • <cfimage
  • action="writetobrowser"
  • source="#objMistyMay#"
  • format="jpg"
  • />
  •  
  • <!--- Write DUPLICATE image to the browser. --->
  • <cfimage
  • action="writetobrowser"
  • source="#objMistyMayDuplicate#"
  • format="jpg"
  • />

When we run this code, we get the following output:

 
 
 
 
 
 
Sexy Misty May VollyBall Picture Duplicated Using ColdFusion Duplicate() Method. 
 
 
 

Notice that the second caption written to the duplicate image was actually written to both images. This is because the Duplicate() method only creates a copy of the image reference, not a copy of the underlying binary data. It seems as if Duplicate() is no different than simply assigning the image to two variables.

So, Duplicate() clearly doesn't work. But what about the other techniques mentioned in the documentation above? Let's try "passing the image to the ImageNew function":

  • <!---
  • Now that we have the clean Misty May picture in memory
  • as a ColdFusion image objects, let's duplicate it using
  • the ImageNew() function.
  • --->
  • <cfset objMistyMayDuplicate = ImageNew( objMistyMay ) />

This gives us the same output as above.

OK, but what about "by passing the image to the ImageNew function or the cfimage tag":

  • <!---
  • Now that we have the clean Misty May picture in memory
  • as a ColdFusion image objects, let's duplicate it using
  • the CFImage tag.
  • --->
  • <cfimage
  • action="read"
  • source="#objMistyMay#"
  • name="objMistyMayDuplicate"
  • />

Unfortunately, this also gives us the same output as above. It looks like so far 3 out of the 4 methods in the documentation don't actually do anything.

It turns out, the ImageCopy() method is the only one of the documented techniques that works. To demonstrate this, let's create a ColdFusion user defined function that wraps around the ImageCopy() method:

  • <cffunction
  • name="ImageDuplicate"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I duplicate a ColdFusion image.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Image"
  • type="any"
  • required="true"
  • hint="I am the image being duplicated."
  • />
  •  
  • <!---
  • Return the duplicated image by copying the entire
  • canvas to a new ColdFusion image object.
  • --->
  • <cfreturn ImageCopy(
  • ARGUMENTS.Image,
  • 0,
  • 0,
  • ImageGetWidth( ARGUMENTS.Image ),
  • ImageGetHeight( ARGUMENTS.Image )
  • ) />
  • </cffunction>

With this method in place, we can now replace our duplication line with the following:

  • <!---
  • Now that we have the clean Misty May picture in memory
  • as a ColdFusion image objects, let's duplicate it using
  • our user defined function that leverages ImageCopy().
  • --->
  • <cfset objMistyMayDuplicate = ImageDuplicate( objMistyMay ) />

With this code in place, when we re-run the above demo, we get the following output:

 
 
 
 
 
 
Sexy Misty May VolleyBall Image Duplicated Using ColdFusion's ImageCopy() Function. 
 
 
 

As you can see, each of the above image now has the appropriate caption. This is because the second caption is written to the duplicate image which, thanks to ImageCopy() was not a reference to the original image.

There's nothing wrong with doing something like reading a file in to memory more than once if you need to create a duplicate; I've done that a bunch of times. But, when it comes to creating a duplicate ColdFusion image from an existing ColdFusion image, ImageCopy() is the only method that I have been able to use successfully.

If you are interested in the ImageAddCaption() method, I just added it for testing purposes:

  • <cffunction
  • name="ImageAddCaption"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I add a caption to the bottom of the image.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Image"
  • type="any"
  • required="true"
  • hint="I am the image we are updating."
  • />
  •  
  • <cfargument
  • name="Caption"
  • type="string"
  • required="true"
  • hint="I am the caption being added to the image."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!---
  • Set the local drawing color for our caption
  • text background.
  • --->
  • <cfset ImageSetDrawingColor(
  • ARGUMENTS.Image,
  • "##262626"
  • ) />
  •  
  • <!---
  • Draw background rectangle (over which our caption
  • text will be drawn).
  • --->
  • <cfset ImageDrawRect(
  • ARGUMENTS.Image,
  • 0,
  • (ImageGetHeight( ARGUMENTS.Image ) - 23),
  • ImageGetWidth( ARGUMENTS.Image ),
  • (ImageGetHeight( ARGUMENTS.Image ) - 23),
  • true
  • ) />
  •  
  • <!--- Set the drawing color for the caption text. --->
  • <cfset ImageSetDrawingColor(
  • ARGUMENTS.Image,
  • "##FFFFFF"
  • ) />
  •  
  • <!--- Set the font properties. --->
  • <cfset LOCAL.FontProperties = {
  • Style = "bold",
  • Font = "verdana",
  • Size = "12"
  • } />
  •  
  • <!--- Write the caption text to image. --->
  • <cfset ImageDrawText(
  • ARGUMENTS.Image,
  • ARGUMENTS.Caption,
  • 10,
  • (ImageGetHeight( ARGUMENTS.Image ) - 7),
  • LOCAL.FontProperties
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>



Reader Comments

@Elliott,

I reported this back in the CF8 public beta ;) It was even addressed some where, but I can't find the post. One of the Adobe developers said that they did this on purpose because they didn't want the memory duplication to get out of hand. Since images can be very large, they decided a copy-by-reference made more sense.

I think the documentation is just wrong.

Reply to this Comment

Steve here: taking fault for having the thumbnail set problem :P

Actually Ben has exposed something here that wasn't really my issue, Duplicate() seems to be working for ME LOL,

My REAL issue was: if I read a file in with
<cfimage action="read" name="myImage">

then delete or rename the original file,

whenever I call a resize method, passing in source="#myImage#"
it complains the original file is no longer on the disk.

I can cdump toString(myImage) and see a nice long base64 string,
so it appears it IS in memory...

I just want to know if someone has some insight on why passing an object in as source doesn't work.

So don't jump on me about this,
also, I suspect that duplicate isn't the whole problem, but possibly setting the caption may suffer from the same bug that resize functions do, They don't work on the object, they work on data from the source file?

My reasoning behind this is,
if I load an image into an object,
and I resize myImage 50x50,
then I resize myImage 300x300,
I get a large blurry image (from the loss of the 1st resize)

if I use <cfset thm1 = Duplicate(myImage)>
and then I resize thm1 to 50x50,
and then I resize myimage to 300x300
I get 2 perfect resizes (the larger one is not blurry)
Pointing to the fact that duplicate is working in this case?

NOTE: I still can't rename or remove the original file or the resizes will fail.

Reply to this Comment

How about this. It seems to show duplicate DOES duplicate the image data, and I can change thm without changing the original,
(I can also change the original without changing the thm)

[code]
<cfimage action="read" source="#photo#" name="objImage">

<cfset thm = Duplicate(objImage)>

<cfoutput>
objImage<br>
<textarea cols="80" rows="20">
#ToBase64(objImage)#
</textarea>
thm<br>
<textarea cols="80" rows="20">
#ToBase64(thm)#
</textarea>

<cfset ImageScaleToFit(thm,50,"")>
objImage after thm resize (still the same)<br>
<textarea cols="80" rows="20">
#ToBase64(objImage)#
</textarea>
thm after resize<br>
<textarea cols="80" rows="20">
#ToBase64(thm)#
</textarea>

</cfoutput>

[/code]

I think that when you use duplicate it still copies the objImage.Source property, and other properties that may remain the same, and soem functions are not really accepting an object as source="">

Reply to this Comment

@Steve,

I think something is definitely funny with the way things are being handled. I want to see if I can duplicate things on my end.

Reply to this Comment

I just tested duplicate() in ColdFusion 10 and it works correctly (creating 2 separate images). I tried it on CF9 and it failed.

Before I consider reporting this, is this considered a new ColdFusion "bug" or "feature"?

Reply to this Comment

@James,

Ha ha, excellent question! From what I can remember, I believe Rupesh told me that this was originally done (in CF8) as a way to cut down on processing. Like a shallow copy vs. a deep copy. That said, I can't remember why (or if he even explained) this was thought to be a meaningful usecase.

That said, I would look at the duplicate-new-image as the "correct" behavior. Not a bug fix (since the previous behavior was apparently intended); but, more like a feature-enhancement.

Reply to this Comment

Ben, You have helped me out of many a CF jam over the years so here a little something I figured out. Feel free to publish it. In your example above one bad thing happens... If you want to write that duplicate to a database using imageGetBlob() it doesn't work because it can't find the file type. Dump the image and you'll see. If you re-read the original you can get around it.

<cfimage action="read" name="originalImage" source="#file.serverDirectory#\#file.serverFile#">
<cfimage action="read" name="thumbImage" source="#originalImage#">
<cfimage action="read" name="webImage" source="#originalImage#">

<cfset ImageScaleToFit(thumbImage,80,'') />
<cfset ImageScaleToFit(webImage,300,'') />

<cfdump var="#originalImage#">
<cfdump var="#thumbImage#">
<cfdump var="#webImage#">

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.