Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Brian Kotek
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Brian Kotek

ImageGetEXIFMetadata() Requires Existing File For First Read In ColdFusion

By Ben Nadel on
Tags: ColdFusion

Yesterday, at InVision App, I was started to experiment with EXIF (Exchangeable Image File) image data for the first time in ColdFusion. I was doing so in an area of the code that consumed a temporary file that was deleted right before returning a ColdFusion Image object. When I then went to call imageGetEXIFMetadata() on the returned image object, I was getting an error. As it turns out, the first call to imageGetEXIFMetadata() needs the underlying physical file to exist.

To see this in action, we can copy a file to a temp location, read it in as a ColdFusion image object, delete the temp file, and then try to call imageGetEXIFMetadata():

  • <cfscript>
  •  
  • permPath = expandPath( "./image.jpg" );
  • tempPath = expandPath( "./temp.jpg" );
  •  
  • // Here, we are going to copy the binary file to a temp location for purposes that
  • // are beyond the scope of this exploration. But, notice that after we copy the PERM
  • // file, we read it in as a ColdFusion image object and then delete the TEMP file.
  •  
  • fileCopy( permPath, tempPath );
  •  
  • image = imageRead( tempPath );
  •  
  • fileDelete( tempPath );
  •  
  • // After we delete the underlying TEMP file, we are still able to access most
  • // properties of the image object, such as its pixel data and its dimensions.
  • width = imageGetWidth( image );
  • height = imageGetHeight( image );
  • writeOutput( "Dimensions: #width# x #height#." );
  •  
  • // BUT, an attempt to read the EXIF data from an image object with no physical
  • // file backing, results in a ColdFusion error.
  • writeDump( imageGetEXIFMetadata( image ) );
  •  
  • </cfscript>

When we run this code, we're able to read the image dimensions. But, when we get to the imageGetEXIFMetadata() function call, we get the following ColdFusion error:

Unable to read image source properly. /Applications/ColdFusion11/cfusion/wwwroot/cf11/exif/temp.jpg (No such file or directory)

As it turns out, ColdFusion doesn't read in the EXIF data when it creates the initial image object. As such, the first time that we call imageGetEXIFMetadata() on the image object, ColdFusion has to go back to the underlying physical file in order to access the metadata. At that point, according to the documentation, the EXIF data gets cached in memory:

The result of the ImageGetEXIFMetadata function is cached in the ColdFusion image to optimize performance. The ImageGetEXIFMetadata function applies only to JPEG images. If you try to retrieve metadata for Base64, BLOB, or other types of images, ColdFusion generates errors.

So, going back to my workflow with temporary image files, we can get around this by forcing ColdFusion to read the EXIF data into memory before we delete the temp file. This way, even if we don't consume the EXIF data directly within the workflow, we can ensure that the calling context (that receives the Image object) can read the EXIF data if it needs to.

  • <cfscript>
  •  
  • permPath = expandPath( "./image.jpg" );
  • tempPath = expandPath( "./temp.jpg" );
  •  
  • // Here, we are going to copy the binary file to a temp location for purposes that
  • // are beyond the scope of this exploration. But, notice that after we copy the PERM
  • // file, we read it in as a ColdFusion image object and then delete the TEMP file.
  •  
  • fileCopy( permPath, tempPath );
  •  
  • image = imageRead( tempPath );
  •  
  • // Before we delete the TEMP file, let's read in the EXIF data. At this point,
  • // we don't have to consume it; but, asking for it causes it to be read from the
  • // physical file and then cached in memory. This way, subsequent requests for the
  • // EXIF data (on this image object) will be pulled out of memory.
  • imageGetEXIFMetadata( image );
  •  
  • fileDelete( tempPath );
  •  
  • // After we delete the underlying TEMP file, we are still able to access most
  • // properties of the image object, such as its pixel data and its dimensions.
  • width = imageGetWidth( image );
  • height = imageGetHeight( image );
  • writeOutput( "Dimensions: #width# x #height#." );
  •  
  • // This time, the request for the EXIF data is pulled out of the in-memory Image
  • // object rather then being pulled from the underlying (already deleted) file).
  • writeDump( imageGetEXIFMetadata( image ) );
  •  
  • </cfscript>

As you can see, we're calling imageGetEXIFMetadata() and discarding the result right before deleting the underlying temp file. This forces ColdFusion to read in and cache the EXIF data such that the subsequent call to imageGetEXIFMetadata() can pull the data right out of memory. And, when we run the above code, we get the following page output:


 
 
 

 
 Accessing imageGetEXIFMetadata() in ColdFusion after the physical image was deleted. 
 
 
 

I assume that ColdFusion uses this approach because reading EXIF data is a relatively expensive process and likely unneeded for the majority of image operations. But, I have to admit, ColdFusion image manipulation has a ton of limitations around how files can be read in and consumed, including the fact that image files with non-image file extensions (such as ".tmp") are all but useless in certain file operations. It makes me sad.

As a side note, I actually ended up ditching ColdFusion's imageGetEXIFMetadata() function for this work altogether and started using ImageMagick's "-auto-orient" command to adjust the orientation of the underlying physical file before I read it into memory as a ColdFusion image object.




Reader Comments

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.