Reading Images With Fallback Approaches In ColdFusion

Posted March 8, 2013 at 5:24 PM by Ben Nadel

Tags: ColdFusion

One of the things that I love about ColdFusion is how freaking easy it is to read, write, and manipulate images without any 3rd party software. You get all that magic in ColdFusion, right out of the box. Sometimes, however, your users upload an image that ColdFusion doesn't really like, and you have to start taking a different approach. Or rather, you have to start taking a variety of different approaches. You could go with something like ImageMagick. But, as much as possible, I like to stick to the ColdFusion internals - less can go wrong, less to maintain.


 
 
 

 
  
 
 
 

When reading in a ColdFusion image, I try to go with the basics first; and then, if necessary, fallback to using more complex approaches. In the following code, I'm using a combination of solutions that I found on the web. From Zac Spitzer, I borrowed reading images as binary. From Joey Krabacher, I borrowed reading images using the Java Advanced Imaging (JAI) library. And, as far as the JAI goes, I don't really understand what's going on there - I just copied what Joey had.

In the following code, I use each technique for demonstration purposes. In reality, however, I would only use the fallbacks if the previous attempt(s) failed to read-in the image.

  • <cfscript>
  •  
  •  
  • // Full path to image files.
  • path = expandPath( "./images/monkey.png" );
  • //path = expandPath( "./images/wrong-extension.jpg" );
  • //path = expandPath( "./images/cmyk.jpg" );
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • try {
  •  
  • image = imageRead( path );
  •  
  • writeOutput( "PASS: imageRead()<br />" );
  •  
  • } catch ( any error ) {
  •  
  • writeOutput( "FAIL: imageRead()<br />" );
  • writeOutput( "--- #error.message#<br />" );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • try {
  •  
  • image = imageNew( path );
  •  
  • writeOutput( "PASS: imageNew( path )<br />" );
  •  
  • } catch ( any error ) {
  •  
  • writeOutput( "FAIL: imageNew( path )<br />" );
  • writeOutput( "--- #error.message#<br />" );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • try {
  •  
  • image = imageNew( fileReadBinary( path ) );
  •  
  • writeOutput( "PASS: imageNew( binary )<br />" );
  •  
  • } catch ( any error ) {
  •  
  • writeOutput( "FAIL: imageNew( binary )<br />" );
  • writeOutput( "--- #error.message#<br />" );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • try {
  •  
  • imageFile = createObject( "java", "java.io.File" ).init(
  • javaCast( "string", path )
  • );
  •  
  • fileSeekableStream = createObject( "java", "com.sun.media.jai.codec.FileSeekableStream" ).init( imageFile );
  •  
  • parameterBlock = createObject( "java", "java.awt.image.renderable.ParameterBlock" ).init();
  •  
  • parameterBlock.add( fileSeekableStream );
  •  
  • // Use the Java Advanced Imaging library to read in the JPEG
  • // file (will throw error if NOT jpeg) as a buffered image.
  • // This will properly handle the different EXIF data types.
  • bufferedImage = createObject( "java", "javax.media.jai.JAI" )
  • .create(
  • javaCast( "string", "jpeg" ),
  • parameterBlock
  • )
  • .getAsBufferedImage()
  • ;
  •  
  • image = imageNew( bufferedImage );
  •  
  • // imageNegative( image ); // CAN HELP A LITTLE BIT.
  •  
  • writeOutput( "PASS: Java Advanced Imaging.<br />" );
  •  
  • } catch ( any error ) {
  •  
  • writeOutput( "FAIL: Java Advanced Imaging.<br />" );
  • writeOutput( "--- #error.message#<br />" );
  •  
  • } finally {
  •  
  • // Clean up the file stream, pass OR fail.
  • fileSeekableStream.close();
  •  
  • }
  •  
  •  
  • </cfscript>
  •  
  •  
  • <!--- If the file managed to be processed, then output it. --->
  • <cfif ! isNull( image )>
  •  
  • <p>
  •  
  • <cfimage
  • action="writeToBrowser"
  • source="#image#"
  • />
  •  
  • </p>
  •  
  • </cfif>

I'm using both imageRead() and imageNew() in this exploration. Both of these can take a file path or URL as the source of the image input. I'm using both functions to demonstrate that imageNew() can take a larger variety of arguments.

There's not too much explanation to offer here. And, as far as the Java Advanced Imaging, I don't want to offer up any information that's not accurate. I will say that if you try to read in a PNG that is labelled (incorrectly) as a JPG, the JAI will raise this exception:

Not a JPEG file: starts with 0x89 0x50

Anyway, just a quick post for Friday afternoon. Have a great weekend!



Reader Comments

Mar 8, 2013 at 6:23 PM // reply »
20 Comments

Thanks Ben, great info as usual. I've been doing more work with images lately myself and found a lot of photos that people were uploading were rotated in odd ways and that the EXIF information would often contain rotation information to "correct" them before processing. I've also had some images that just won't load or had color issues so I will try some of these techniques on them as well. Thanks for sharing.


Mar 8, 2013 at 7:31 PM // reply »
46 Comments

An incompatible palette is one issue, another is performance and compression. We were experiencing the CPU going to 100% when processing JPG images from digital cameras. For optimizing large images, we switched to CFX_OpenImage (C++ tag that uses GraphicsMagick). Thumbnail generation was much faster and the filesizes were smaller.
http://www.kolumbus.fi/jukka.manner/cfx_openimage/

Check out the documentation. There's a lot of features crammed into the library and many sample scripts.

[NOTE: I use NoScript 2.6.5.8 for Firefox and it alerted me of an XSS attempted when posting this response. It may be a false positive, but I wanted to report it. I switched to Google Chrome so I could post this.]


Mar 15, 2013 at 9:08 AM // reply »
11,314 Comments

@Justin,

From my own personal experience, I can say that rotation is super annoying :D I'll take photos with my phone. They look fine. I'll email them to myself. They look fine... then I open them in a different email and BLAM, they are sideways :( What?! That's good to know that the EXIF data can contain that information.


Mar 15, 2013 at 9:11 AM // reply »
11,314 Comments

@James,

Yeah, we are toying around with trying to use something like ImageMagick on the command line to get some better performance and file size. That's one thing that CF seems bad about is filesize. Also, once we are on the command line, there's all kinds of opportunities for "smushing" the images (or whatever they call it when you losslessly remove data from image).

Also, thanks for the XSS tip. I don't see anything in the page that is the obvious cause. I'll try to install that as well and see what I can find. Thanks!!!


Mar 15, 2013 at 9:22 AM // reply »
11,314 Comments

@James,

NoScript is pretty cool. I see a number of scripts loading, but it all looks like the GitHub / Video / Facebook / Google Analytics / AdSense stuff. It's possible that one of the comments in the sidebar had something funky in it at the time. I'll try to track it down. Again, thanks for the heads-up.


Apr 26, 2013 at 1:01 PM // reply »
1 Comments

Hi Ben,

With regard to CMYK images, it seems CF9 supports some of the operations in cfimage.

Check this page:
http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7945.html

The quote from the docs:


CMYK support
The cfimage tag supports reading and writing CMYK images, but does not support actions that require converting the images. For example, you can use CMYK images with the read, write, writeToBrowser, resize, rotate, and info actions. You cannot use CMYK images with the convert, captcha, and border actions. The same rule applies to image functions. For example, the ImageNew, ImageRead, and ImageWrite functions support CMYK images, but the ImageAddBorder function does not.

What are your thoughts on this? Seems to have slipped past a few people.


Jun 11, 2013 at 9:36 AM // reply »
11,314 Comments

@Michael,

Hmm, very interesting. In my current app, we really only resize images. I don't think we do anything that requires an actual type conversion. That said, I've noticed the error in Reading images with CMYK; although, even after reading them with the JAI toolkit, the colors are definitely messed up.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 19, 2013 at 2:01 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
I have coincidentally been beating my head against the S3 API for the last week or so. One big "gotcha" I had to work around was file names and paths containing spaces. Remember to URL Enco ... read »
Jun 19, 2013 at 1:27 PM
Using Slice(), Substring(), And Substr() In Javascript
very good article. By the way IE supports negative values in substr or slice in verson 10. ... read »
Jun 19, 2013 at 11:33 AM
Filter vs. ngHide With ngRepeat In AngularJS
In your assessment, is it correct to say that given a list of say 500 items its more performant to use the `ngHide` method over the `filter` method? ... read »
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools