Ask Ben: Extracting And Resizing A ZIP Archive Full Of Images With Coldfusion

Posted August 31, 2009 at 10:21 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

Great!... Ok, What I need is to upload a .zip file containing large jpgs (1024X768 max), the form would contain two fields ID and the ZIP file. The ID field is the name of the folder that will be created or overwritten (inside this folder will be two others: "tn" and "lg"), then your imageutils will process the images saving 3 versions: "large" into "../ID/lg" and "small" into "../ID/tn" the medium size will be saved into another existing folder. The only part im totally "ignorant" about is how to process the zip file containing the images. Also zip file needs to be deleted when all done.

While you mention the "ImageUtils" project, from our previous emails back and fourth, you mentioned that you were running ColdFusion 8. As such, rather than worry about using the Java layer to handle the Zip and the Image manipulation, I'm simply going to address this using ColdFusion 8's CFZip and CFImage tags. Trust me, if you have the option, use them - they will make your life a whole lot easier.

For this demo, I'll carry through your intent, but change it slightly to make it easier to read. I'm going to take a ZIP of images and a target directory name. Then, I'm going to extract all of the images from the ZIP and store them in the target directory at three different image dimensions - large, medium, and small. Each of the three versions will be stored in a sub-directory, with the large being the raw, uploaded image, the medium being 50% size, and the small being 25% size.

 
 
 
 
 
 
 
 
 
 

There's not a whole lot of explaining to do as far as the code goes, so I hope that the following code demo will be sufficient. The only real caveat in the situation is the ability for ColdFusion to work with the selected images. Sometimes, ColdFusion just can't work with some images. I have no idea why - it just doesn't like it; and, what's worse is that it will simply hang, not throwing any errors (at least not in the short term - I could have played with the request timeout to try and force an error). In the video above, you can see that I'm working with a ZIP archive, "shooter2.zip". I'm doing that because some images in my first zip were not playing nice and the CFImage / resize action was hanging.

Caveats aside, I tried to take care of most errors in my error handling, delivering insightful error message back to the end user. Let's take a look at the code:

  • <!--- Param the form fields. --->
  • <cfparam name="form.submitted" type="boolean" default="false" />
  • <cfparam name="form.name" type="string" default="" />
  • <cfparam name="form.archive" type="string" default="" />
  •  
  • <!--- Create an array for errors. --->
  • <cfset errors = [] />
  •  
  •  
  • <!---
  • Define the path of our temp directory. This will be the
  • directory into which our upload will be stored while it's
  • being processed.
  • --->
  • <cfset tempDirectory = (
  • getDirectoryFromPath( getCurrentTemplatePath() ) &
  • "temp\"
  • ) />
  •  
  • <!---
  • Define the path of our upload directory. This will be the
  • directory in which we create our target directory (in which
  • the images will be saved).
  • --->
  • <cfset uploadDirectory = (
  • getDirectoryFromPath( getCurrentTemplatePath() ) &
  • "upload\"
  • ) />
  •  
  •  
  • <!--- Check to see if the form has been submitted. --->
  • <cfif form.submitted>
  •  
  • <!--- Validate the form data. --->
  •  
  • <!--- Was name entered. --->
  • <cfif !len( form.name )>
  •  
  • <cfset arrayAppend(
  • errors,
  • "Please enter the name of your target folder."
  • ) />
  •  
  • <cfelseif reFind( "[^\w_\-]", form.name )>
  •  
  • <cfset arrayAppend(
  • errors,
  • "Your name contains invalid characters. Only alpha-numeric and _ and - are allowed."
  • ) />
  •  
  • </cfif>
  •  
  • <!--- Was archive selected. --->
  • <cfif !len( form.archive )>
  •  
  • <cfset arrayAppend(
  • errors,
  • "Please select an image ZIP file to upload."
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Check to see if we have any preliminary form errors at
  • this point. If not, we can start to investigate deeper.
  • --->
  • <cfif !arrayLen( errors )>
  •  
  • <!---
  • If we have no errors yet, then we should try to upload
  • the image ZIP file. At that point, we can perform some
  • more validation of the ZIP and the contained files.
  • --->
  •  
  • <!--- Upload the selected archive file. --->
  • <cffile
  • result="upload"
  • action="upload"
  • filefield="archive"
  • destination="#tempDirectory#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Store an easy short-hand for our uploaded zip file
  • (since we are going to be referring to it a lot later
  • on).
  • --->
  • <cfset targetZip = "#upload.serverDirectory#\#upload.serverFile#" />
  •  
  • <!---
  • Now that the archive was uploaded, let's search it for
  • image files. Because the file interaction might throw
  • an error (if it was not a zip), let's wrap in a try /
  • catch block.
  • --->
  • <cftry>
  •  
  • <!---
  • Search target ZIP for images. We are going to limit
  • our search to JPG images.
  • --->
  • <cfzip
  • name="zippedImages"
  • action="list"
  • file="#targetZip#"
  • recurse="true"
  • filter="*.jpg"
  • />
  •  
  • <!---
  • Check to see if there were any images found. If
  • there we none found, then this ZIP is invalid.
  • --->
  • <cfif !zippedImages.recordCount>
  •  
  • <!--- Throw an error. --->
  • <cfthrow
  • type="NoImagesFound"
  • message="There were no images found in the selected ZIP file."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- Catch any errors about target images. --->
  • <cfcatch type="NoImagesFound">
  •  
  • <!--- Add error message. --->
  • <cfset arrayAppend( errors, cfcatch.message ) />
  •  
  • </cfcatch>
  •  
  • <!--- Catch any file interaction errors. --->
  • <cfcatch type="any">
  •  
  • <!--- Add error message. --->
  • <cfset arrayAppend(
  • errors,
  • "There was a problem reading your ZIP file. Make sure that the selected file is a valid ZIP archive."
  • ) />
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  •  
  • <!---
  • Check to see if we have any errors. If we do, then
  • something is wrong with the selected ZIP file and we
  • don't need to have it on our server. Let's delete it.
  • --->
  • <cfif arrayLen( errors )>
  •  
  • <!--- Delete the invalid zip file. --->
  • <cffile
  • action="delete"
  • file="#targetZip#"
  • />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • At this point, we've done all of the primary and secondary
  • testing of the form data and the selected ZIP file. Let's
  • check to see if we have any errors.
  • --->
  • <cfif !arrayLen( errors )>
  •  
  • <!---
  • There are no errors which means that conditions should
  • be perfect for unzipping and resizing our images.
  • --->
  •  
  • <!---
  • Create the path to our target directory (where we will
  • be uploading the images).
  • --->
  • <cfset targetDirectory = "#uploadDirectory##form.name#\" />
  •  
  • <!---
  • Check to see if our target directory exists in our
  • upload directory. If it does, then we need to stip out
  • existing files. If it doesn't, then we need to crate
  • it (and its sub-directories).
  • --->
  • <cfif directoryExists( targetDirectory )>
  •  
  • <!---
  • Since the directory exists, we are going to ASSUME
  • that the sub-directory structure is already valid.
  • As such, we are ONLY going to strip out existing
  • files, leaving directories in place.
  •  
  • NOTE: The reason we don't simply overwrite them
  • later on is that there might be less images in the
  • new zip than in the existing directory.
  • --->
  •  
  • <!---
  • Query for files in the target diretory. We don't
  • care about the sub-directories as these should be
  • the ones we want to exist.
  • --->
  • <cfdirectory
  • name="fileList"
  • action="list"
  • directory="#targetDirectory#"
  • type="file"
  • recurse="true"
  • />
  •  
  • <!--- Loop over the existing images to delete. --->
  • <cfloop query="fileList">
  •  
  • <!--- Delete the existing files. --->
  • <cffile
  • action="delete"
  • file="#fileList.directory#\#fileList.name#"
  • />
  •  
  • </cfloop>
  •  
  • <cfelse>
  •  
  • <!---
  • Since the target directory does not exist, we
  • need to create it as well as the sub-directories
  • for large, medium, and small images.
  • --->
  •  
  • <!---
  • Create the taret directory (based on the user-
  • entered name in the form submission).
  • --->
  • <cfdirectory
  • action="create"
  • directory="#targetDirectory#"
  • />
  •  
  • <!--- Create our "large" image directory. --->
  • <cfdirectory
  • action="create"
  • directory="#targetDirectory#large\"
  • />
  •  
  • <!--- Create our "medium" image directory. --->
  • <cfdirectory
  • action="create"
  • directory="#targetDirectory#medium\"
  • />
  •  
  • <!--- Create our "small" image directory. --->
  • <cfdirectory
  • action="create"
  • directory="#targetDirectory#small\"
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have our target directories in place,
  • let's read in our zipped images and resize them.
  • For this demo, I'm going to ASSUME that the uploaded
  • images are the *large* versions and that for sake of
  • simplicity, the sizes will be as follows:
  •  
  • Large: 100% (dimensions)
  • Medium: 50% (dimensions)
  • Small: 25% (dimensions)
  • --->
  •  
  • <!---
  • Because we are dealing again with file interaction
  • (and file interaction that requires a specific file
  • type), let's wrap this up in a try / catch block.
  • --->
  • <cftry>
  •  
  • <!---
  • Loop over the zipped images (that we gathered
  • above when checking for valid images).
  • --->
  • <cfloop query="zippedImages">
  •  
  • <!---
  • Read the images in as a binary value (which
  • we can use as the source in our image
  • manipulation).
  • --->
  • <cfzip
  • variable="imageData"
  • action="readBinary"
  • file="#targetZip#"
  • entrypath="#zippedImages.name#"
  • />
  •  
  • <!---
  • Since we are uploading multiple images, I have
  • decided to use the current record of the image
  • query as the image name. This is only to
  • prevent duplicates. Create a short hand for
  • the file name here since we are going to be
  • using it many times below.
  • --->
  • <cfset imageName = "#zippedImages.CurrentRow#.jpg" />
  •  
  •  
  • <!---
  • Write this image this raw, original image to
  • our large image folder.
  • --->
  • <cffile
  • action="write"
  • file="#targetDirectory#large\#imageName#"
  • output="#imageData#"
  • />
  •  
  • <!---
  • Resize the raw image to 50% for the medium
  • image (and store it in the medium directory
  • in same step).
  • --->
  • <cfimage
  • action="resize"
  • source="#imageData#"
  • destination="#targetDirectory#medium\#imageName#"
  • width="50%"
  • height="50%"
  • />
  •  
  • <!---
  • Resize the raw image to 25% for the small
  • image (and store it in the small directory
  • in the same step).
  • --->
  • <cfimage
  • action="resize"
  • source="#imageData#"
  • destination="#targetDirectory#small\#imageName#"
  • width="25%"
  • height="25%"
  • />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Catch any file interaction errors. --->
  • <cfcatch type="any">
  •  
  • <!--- Add error message. --->
  • <cfset arrayAppend(
  • errors,
  • "There was a problem reading / resizing one of the zipped images. Please make sure that all images are valid JPG images (NOTE: Try opening and resaving the images)."
  • ) />
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  •  
  • <!---
  • At this point, we are done with all of our procesing -
  • or, as much as we could. No matter how much we got
  • through in this last step, let's delete the ZIP file
  • as it no longer servers any purpose.
  • --->
  • <cffile
  • action="delete"
  • file="#targetZip#"
  • />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • <cfoutput>
  •  
  • <!--- Reset the buffer. --->
  • <cfcontent type="text/html" />
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Extracting And Resizing A ZIP Archive Full Of Images With Coldfusion</title>
  • </head>
  • <body>
  •  
  • <h1>
  • Extracting And Resizing A ZIP Archive Full Of Images With Coldfusion
  • </h1>
  •  
  • <p>
  • Please upload your image ZIP file and specify the
  • name of the target directory into which the processed
  • images will be put.
  • </p>
  •  
  • <!--- Check to see if there were any errors. --->
  • <cfif arrayLen( errors )>
  •  
  • <h2>
  • Please review the following:
  • </h2>
  •  
  • <ul>
  • <cfloop
  • index="errorMessage"
  • array="#errors#">
  •  
  • <li>
  • #errorMessage#
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • </cfif>
  •  
  • <form
  • action="#cgi.script_name#"
  • method="post"
  • enctype="multipart/form-data">
  •  
  • <!--- Submission flag. --->
  • <input type="hidden" name="submitted" value="true" />
  •  
  •  
  • <p>
  • <label>
  • <strong>Folder Name:</strong><br />
  • <input type="name" name="name" size="30" />
  • </label>
  • </p>
  •  
  • <p>
  • <label>
  • <strong>Image ZIP:</strong><br />
  • <input type="file" name="archive" size="50" />
  • </label>
  • </p>
  •  
  • <p>
  • <input type="submit" value="Upload Image Zip" />
  • </p>
  •  
  • </form>
  •  
  •  
  • <!---
  • Check to see if the form has been submitted and that
  • we don't have any errors. If this is true, we can
  • show the thumbnails of the uploaded images.
  • --->
  • <cfif (
  • form.submitted &&
  • !arrayLen( errors )
  • )>
  •  
  • <h2>
  • Uploaded Images
  • </h2>
  •  
  • <ul>
  • <cfloop query="zippedImages">
  •  
  • <li>
  • <img
  • src="./upload/#form.name#/small/#zippedImages.CurrentRow#.jpg"
  • alt="Small Image: 25%"
  • style="border: 2px solid black ;"
  • />
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • </cfif>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

Like I said above, I think the code is fairly self-explanatory. I tried to comment it thoroughly. I hope this helps!



Reader Comments

Aug 31, 2009 at 12:30 PM // reply »
1 Comments

Thanks Ben, will start running some tests today and will post the results later.

The CForce will be with you, always. :)


Aug 31, 2009 at 12:34 PM // reply »
11,238 Comments

@Felipe,

Always glad to help. I hope this gives you some inspiration.


Aug 31, 2009 at 1:33 PM // reply »
1 Comments

Perhaps a way to get around CFImage hanging on some images would be to fork off CFImage inside a CFThread and then wait for the thread to process to a certain timeout. If it fails, then it only doesn't process the one image (and you get an error) and you can go on with the rest of ZIP.


Aug 31, 2009 at 2:27 PM // reply »
11,238 Comments

@John,

That's an interesting idea. I guess you could then add a CFThead / JOIN and wait for the timeout. Cool idea.


Aug 31, 2009 at 2:43 PM // reply »
131 Comments

@Ben,

One of the most common unexpected failures in CFIMAGE manipulation is dealing with JPEG images in CMYK format. There is no programmatic way (not even viewing File Properties in Windows Explorer) to tell the difference, but browsers cannot render CMYK jpegs (or progressive jpegs) and neither can the Adobe image engine. They have to be RGB and non-progressive.

Other than that, the other supported file formats seems to be pretty straightforward, although it's important to realize that not all supported formats are web-viewable: e.g., the cfimage engine will crunch TIFF files, but the browser can't display the results for you.


Aug 31, 2009 at 2:46 PM // reply »
11,238 Comments

@Jason,

That's good to know. Will CMYK throw an error though? My machine just seemed to hang on the image. Re-saving via Fireworks (as a JPG) seemed to take care of it.

I did what I could in the above demo, alerting the user to try such a movie if the CFImage tag was failing.


Aug 31, 2009 at 3:05 PM // reply »
131 Comments

Nope, generally I wouldn't get errors with invalid jpegs, just the churn. After trying a bunch of different try/catch setups, I ended up testing the initial file right after upload, before even trying any image functions on it:

path = cffile.serverDirectory & "\" & cffile.serverFile;
if (cffile.fileWasSaved and not isImageFile(path)) {
rtnStruct["error"] = "ERROR: The imaging system was unable to process the file you uploaded. Please ensure that you are not using a progressive JPEG or a CMYK JPG.";
}

So, if (rtnStruct.success) then I call my resize or reformat methods, otherwise I just show rtnStruct.error back to the user.


Aug 31, 2009 at 7:17 PM // reply »
76 Comments

Just a tip, thus far we cannot save images properly with built in CF8 save features - tag or function based, without getting all sorts of random errors... :(

However I use something like this to save which so far has fixed all random errors:

<!--- when save is needed, imageSource is a CF8 image object --->
<cfset saveBufferedImage(imageSource, destination) />

<!--- other code excerp --->
<cfset variables.TAB = chr(9) />

<cffunction name="init" access="public" output="false" returntype="model.system.image.ImageService" hint="Initializes the service.">

<cfset super.init() />

<cfset setJavaImageIO(createObject("java", "javax.imageio.ImageIO")) />
<cfreturn this />
</cffunction>

<cffunction name="getJavaImageIO" access="public" returntype="any" output="false">
<cfreturn variables.instance.javaImageIO />
</cffunction>
<cffunction name="setJavaImageIO" access="private" returntype="void" output="false">
<cfargument name="javaImageIO" type="any" required="true" />
<cfset variables.instance.javaImageIO = arguments.javaImageIO />
</cffunction>

<cffunction name="getWriterFormatName" access="private" output="false" returntype="string">
<cfargument name="filename" type="string" required="true" />

<cfset var validFormats = arrayToList(getJavaImageIO().getWriterFormatNames(), variables.TAB) />
<cfset var pos = listFind(validFormats, lCase(listLast(arguments.filename, ".")), variables.TAB) />

<cfif pos>
<cfreturn listGetAt(validFormats, pos, variables.TAB) />
<cfelse>
<cfreturn "jpeg" />
</cfif>
</cffunction>

<cffunction name="saveBufferedImage" access="private" output="false" returntype="void" hint="Takes a CF image object and gets the buffered image object from it. Saves via java to circumvent lame CF8 image bugs">
<cfargument name="image" type="any" required="true" />
<cfargument name="destination" type="string" required="true" />

<cfset getJavaImageIO().write(
imageGetBufferedImag(arguments.image),
getWriterFormatName(arguments.destination),
createObject("java", "java.io.File").init(arguments.destination)
) />
</cffunction>

Hope that helps, till they fix the bugs :)


Aug 31, 2009 at 9:24 PM // reply »
14 Comments

Just wanted to toss in another reason for isImageFile() (and others): to help circumvent attacks (where executable code is uploaded, then ran).


Sep 1, 2009 at 2:24 AM // reply »
5 Comments

Ben, thanks for sharing the blog post on "Extracting And Resizing A ZIP Archive Full Of Images With Coldfusion." I can relate?no doubt!

I really thankful to u for doing such a great job


Sep 1, 2009 at 8:29 PM // reply »
3 Comments

Hi guys... thanks Ben gain!

About the CCFILE fucntion nameconflict="makeunique"Originally I was thinking to overwrite with new data. But it got me thinking that the user might enter an existing ID or folder name. Instead of makeunique or overwrite existing info; is there a way to let the user know that folder allready exist and give the option to overwrite or enter a new name?


Sep 2, 2009 at 8:23 AM // reply »
11,238 Comments

@Shuns,

Very interesting. I wonder where my stuff was hanging - on the resize or the file write. It looks like your stuff helps with the file write; but, if it was hanging on the resize, then I'm out of luck :)

@Felipe,

During the form validation, you would just need to check to see if the directory exists DirectoryExists(). If it does, perhaps the easiest way would be to allow them a checkbox for "overwriting" existing directories. If that box is checked, just proceed. If not, return a form validation error.


Sep 2, 2009 at 8:40 AM // reply »
11,238 Comments

@Jason,

Does IsImageFile() actually check to make sure the file is a valid image?


Sep 2, 2009 at 8:56 AM // reply »
131 Comments

Not only that, it specifically checks that the file is an image type that the CF image functions can operate on ... pretty handy.


Sep 2, 2009 at 9:11 AM // reply »
11,238 Comments

@Jason,

Really? That is bad ass!


Sep 2, 2009 at 6:36 PM // reply »
76 Comments

Well hope that helps Ben - I never had problems with the image operations, only when I went to actually save them to disk. Even though sometimes CF would report as though it was an operation issue...



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
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools