From time to time people will ask (either here, or cf-talk, or wherever) about the speed of ColdFusion 8's image resizing. Typically they are asking because the speed can be a bit slow. My normal response is to remind people that Adobe provided numerous ways to resize, ranging from very slow and high quality options to much quicker and less quality options. I've been meaning to write a script that would compare these resize options and I finally got a chance too this morning.
Before I go into the script, I have to apologize. I normally tell people about the resize options (called interpolation algorithms) and then point out that Adobe ships 17 choices. However I was wrong. The options are grouped into a list of values with generic terms like highPerformance or highestQuality and named ones like lanczos and hanning (which sound like Star Trek names if you ask me).
Here is where things get a bit weird. You would think there would be a one to one relation between the generic and named options. However there are more named options than generic ones. My assumption is that Adobe mapped some of the generic options to one of the named options. I created the following list would uses both the generic and named options, and should represent a move from highest quality to highest performance.
- highestQuality
- lanczos
- highquality
- mitchell
- mediumPerformance
- quadratic
- mediumquality
- hamming
- hanning
- hermite
- highPerformance
- blackman
- bessel
- highestPerformance
- nearest
- bicubic
- bilinear
My script would go through each of these, perform a resize and see how long it took, how big the file was, and output a link to the image so I could see the quality.
In my first test using a 300k or so JPG, the highest quality resize took a huge amunt of time - close to 4 minutes. In my second test, using a larger image, it took a lot less time. The docs do say that resizing performance/result will depend on the source image, which isn't surprising, but it bares repeating I think.
Something else I noticed was that - both with the super slow to resize image and the not so slow image - I was not able to visibly tell a difference. It may be that I don't have an artist's eyes. It may also be that when I saved the image, I always used the highest quality setting (so in other words, a range of options for resizing, but always the best for saving). Based on what I saw though - at least for images to be shown on the web - I see no reason to not use the quickest resize. Of course 2 tests isn't very scientific, but it's something to keep in mind. ColdFusion defaults to the highest performance. I'd probably not use that default in my applications.
I've included the script I used below. All you have to do is modify the sourceImage line to test yourself. Here is the results from my last test:
Method | Size | Time (Seconds) |
---|---|---|
highestQuality | 425Kb | 1.703 |
lanczos | 425Kb | 1.703 |
highquality | 392Kb | 1.187 |
mitchell | 392Kb | 1.188 |
mediumPerformance | 392Kb | 1.203 |
quadratic | 367Kb | 0.938 |
mediumquality | 390Kb | 0.704 |
hamming | 390Kb | 0.688 |
hanning | 398Kb | 0.687 |
hermite | 397Kb | 0.703 |
highPerformance | 390Kb | 0.687 |
blackman | 407Kb | 0.687 |
bessel | 385Kb | 1.828 |
highestPerformance | 433Kb | 0.031 |
nearest | 435Kb | 0.016 |
bicubic | 433Kb | 0.032 |
bilinear | 414Kb | 0.031 |
Notice that there seems to be no real difference in file size. At most 50k or so separates them. Also notice that bessel is oddly slow compared to the algorithms near it. That may be one to avoid.
In case you want to see the images themselves (I felt they were too big to include on this blog entry), I've linked the original, highest quality, and highest performance images:
<cfscript>
/**
* Will take a number returned from a File.Filesize, calculate the number in terms of Bytes/Kilobytes/Megabytes and return the result.
* v2 by Haikal Saadh
*
* @param number Size in bytes of the file. (Required)
* @return Returns a string.
* @author Kyle Morgan (admin@kylemorgan.com)
* @version 2, August 7, 2006
*/
function fncFileSize(size) {
if ((size gte 1024) and (size lt 1048576)) {
return round(size / 1024) & "Kb";
} else if (size gte 1048576) {
return decimalFormat(size/1048576) & "Mb";
} else {
return "#size# b";
}
}
</cfscript>
<cfset methods = "highestQuality,lanczos,highquality,mitchell,mediumPerformance,quadratic,mediumquality,hamming,hanning,hermite,highPerformance,blackman,bessel,highestPerformance,nearest,bicubic,bilinear">
<cfset results = queryNew("method,size,time")>
<cfset sourceImage = expandPath("./DSC00014.jpg")>
<cfset finfo = getFileInfo(sourceImage)>
<cfset img = imageRead(sourceImage)>
<cfset iinfo = imageInfo(img)>
<cfdump var="#iinfo#" label="File Size in Bytes: #finfo.size#">
<cfimage action="writeToBrowser" source="#sourceImage#">
<hr/>
<cfloop index="m" list="#methods#">
<cfoutput>
<h2>Resize Method: #m#</h2>
<cfset newImage = duplicate(img)>
<cfset timer = getTickCount()>
<cfset imageScaleToFit(newImage, 700, 700, m)>
<cfset total = getTickCount() - timer>
<cfset filename = m & "" & getFileFromPath(sourceImage)>
<cfset imageWrite(newImage,expandPath(filename),1)>
<cfset finfo = getFileInfo(expandPath(filename))>
<cfoutput><p>#fncFileSize(finfo.size)# bytes at #total/1000# seconds</p></cfoutput>
<img src="./#m##getFileFromPath(sourceImage)#">
</cfoutput>
<cfset queryAddRow(results)>
<cfset querySetCell(results, "method", m)>
<cfset querySetCell(results, "size", fncFileSize(finfo.size))>
<cfset querySetCell(results, "time", total/1000)>
<cfflush>
</cfloop>
<cftable query="results" border colHeaders htmlTable>
<cfcol header="Method" text="#method#">
<cfcol header="Size" text="#size#">
<cfcol header="Time (Seconds)" text="#time#">
</cftable>
Archived Comments
VERY interesting. I wonder if you would have a different "human" evaluation of the finished images if you added ImageSetAntialiasing before the resize...
According to the docs, that is for drawing and doing text on images. I'm not sure it would apply.
One thing I may want to look at is the blurFactor option for resizing. To be honest, I was a bit unsure of why you would want _any_ blur. It seems like that would be used for enlarging images only.
Most of the examples in the live docs include something like this, as a matter of course:
<!--- Turn on antialiasing to improve image quality. --->
<cfset ImageSetAntialiasing(myImage,"on")>
<cfset ImageScaleToFit(myImage,100,"","lanczos")>
... although I've never seen side-by-sides that demonstrated what that improved quality was supposed to look like, or seen any other explanation of what it does beyond what you are citing from the live docs.
Hmmmmm....
You are correct. From the docs:
Use the ImageSetAntialiasing function to improve the quality of the rendered image.
Testing now...
So I added this to my code:
<cfif structKeyExists(url, "aa")>
<cfset imageSetAntiAliasing(newImage)>
</cfif>
I added it right after the first gettickcount. I tested both w/o it, and with it. The times are a bit off, but not in a significant way. To be honest, I see no difference. I thought maybe I could see it in the moss, looking closely, but I was not able to.
I'm going to try a third option now - off. I assume it is the same as not setting it at all... but I'm going to test that too.
I tested, and still can't tell. But it could be my source image. I had thought that a 'scenic' pic like that would be best for revealing problems, but maybe I'm wrong.
When I do that kind of testing, I like to take a "people" portrait about 1000 pixels wide and take it down to 200 pixels or so, but I don't have anything readily on hand to demo the effects. Maybe if work quiets down next week I'll mimic the script in your posting and give it a try with that sort of image...
Funny you mention people portrait. I tried a picture of me and still wasn't able to see any differences. It was of me from the waist up, not too close though.
I wonder if this test implies that - in general, quality is pretty darn good when you resize. You lose more quality when you _save_. Since I always saved at top quality, it could be why I'm not seeing as much of a change.
I should also try a GIF and TIFF too.
This is interesting. I gave up on cfimage and went back to Efflare CFImagecr3 because of the times. Minutes for high quality, and the image quality for faster speed was poor compared with Efflare. I'm interested that you had varying results depending on the actualy image, and not the size. I'm mostly resizing images of paintings which have rather a lot of detail and colour so that may impact things, although still in the 400-500k size. Can you show me the image that took 4 minutes?
The picture was a shot of some ships at harbor by Steven Erat (http://www.talkingtree.com). I'm trying to find it online so I can link to it there. I don't want to post it here w/o his permission. If I can't find it I'll ask him.
Found it:
http://www.flickr.com/photo...
Thanks Ray - Camden Harbour eh?
I'll do some tests with this image, although I'm really surprised it makes a big difference
Has anyone written a CFC yet which essentially replicates photoshop's save for web command?
I'd guess the blurfactor is intended mostly for reducing artifacts & shrinking file sizes when using high compression? But I'm being lazy and not actually testing it, so the max value could be like a giant gaussian blur instead of a just slightly noticable modification....
@TomK: I don't use Photoshop, so I have no idea what that is. ;) But there _is_ a CFC that does a lot of common stuff. Check out imageUtils at RIAForge.org.
Great Post, was a really help !!!
Thanks
This post just saved me a lot of time and headaches. A massive flex image upload system I wrote last year broke once Coldfusion 8 was deployed to our servers. Seems that Coldfusion was taking forever to resize large progressive images, and would eventually just timeout. After reading this, I added a "bilinear" interpolation to the ImageResize function and it works perfectly (and without a significant drop in quality!) Thanks alot Raymond.
Glad I could help.
I've found that in photoshop it's better to reduce images in 50% steps. It yields much better images.
For instance if your image is 1000px and you want a 100px thumbnail it would take 4 resize operations:
1000 : 50% = 500
500 : 50% = 250
250 : 50% = 125
125 : manual resize = 100
I wonder if the same is true with CFimage as well.
Try it and tell us. :) Should be easy enough to automate in CF.
haha yeah I've been trying to do so all day! You know what it's like to have a lot of open projects.
Maybe my next project will be something that converts ideas into time. :P
Show and tell:
http://www.webveteran.com/b...
"Sure enough, the multiple pass method in PhotoShop yields a nicer image. However, it looks like a single pass resize with CFimage looks better."
Now of course you should also compare using the multiple resize types as well. :)
Naahhh... why would I want a lower quality images?
great article, i did my own testing after reading this and can confirm those findings, and the results appeared to be very similar for gifs or jpgs (in magnitude, not actual processing times). small note, if you have fonts on your images then the difference between bilinear and highestquality does become very noticeable; for images with fonts on them i found the best low setting to be "highperformance". Maybe adjusting the antialiasing as mentioned in a comment above would improve the font output at lower levels? might be worth playing with
Great blog!
I've also been testing the ImageResize with the ImageSetAntialiasing on/off and I see no different.
My biggest probelem is to get good quality for my thumbs.
If I Resize e.g. 3504x2336 px image to 920x613 and then to 100x67 the 920x613 quality is fine (specially if I use the ImageSharpen) but the 100x67 is has poor quality.
I tryed both bilinear and the Highest Quality and the differnt was negligible in the 920 pixel image but huge in the 100 pixel thub.
Do you know any trick's to help me whit my thumbs?
Not having a lot of luck with ImageResize. My host server uses SeeFusion, so my script is timing out after 17 seconds. It seems to me though, from what I have read here, that it shouldn't be taking that long to complete.
My app allows the user to upload a front and a back image of a coin and resizes them both to 600x600 pixels, then resizes again to 60x60 for the thumbnail. Using getTickCount, I find that it can take almost 3-6 seconds to resize an 800x800 to 600x600, even using bilinear.
Most peculiar.
Ray I have to think something is wrong with our server. It is taking an EXTREMELY long time to resize our images. I ran your test (minus the first four algorithms) on a 424kb jpg. Except for the last four the times are ridiculous. This doesn't seem normal from my searching on the web.
Method Size Time (Seconds)
mediumPerformance 363Kb 471.384
quadratic 343Kb 369.116
mediumquality 362Kb 265.692
hamming 362Kb 265.255
hanning 368Kb 264.927
hermite 368Kb 266.177
highPerformance 362Kb 265.427
blackman 376Kb 264.724
bessel 355Kb 724.482
highestPerformance 405Kb 0.515
nearest 413Kb 0.094
bicubic 405Kb 0.172
bilinear 393Kb 0.25
This may help: http://www.coldfusionjedi.c...
I upgraded to the lastest (1.6.0_16). This version keeps causing CF to restart each time I run my resize script (I think because the JVM crashes). I switched to the version in your example (1.6.0_13) and ran the script again.
Highest Quality: 666.993 seconds
lanczos 660.074 seconds
highquality 466.821 seconds
mitchell 471.246 seconds
mediumperformance 480.584 seconds
quadratic 371.537 seconds
mediumquality 269.695 seconds
hamming ....
Here the script timed out (I had it limited to an hour)
If you have any other suggestions I would appreciate it. I am working on a photo application so this is becoming a little burdensome! Our other server (production) has no problem with this (JVM 1.6.0_01).
I opened the image I was using and converted it to a .bmp file with PaintShop pro:
Method Size Time (Seconds)
highestQuality 1.12Mb 7.531
lanczos 1.12Mb 7.625
highquality 1.12Mb 5.407
mitchell 1.12Mb 5.344
mediumPerformance 1.12Mb 5.297
quadratic 1.12Mb 4.219
mediumquality 1.12Mb 2.968
hamming 1.12Mb 3.063
hanning 1.12Mb 3.204
hermite 1.12Mb 3.469
highPerformance 1.12Mb 3.156
blackman 1.12Mb 3.188
bessel 1.12Mb 8.297
highestPerformance 1.12Mb 3.438
nearest 1.12Mb 0.422
bicubic 1.12Mb 0.594
bilinear 1.12Mb 0.297
So it looks like it is an issue with the jpgs I was using for testing. I found this thread:
http://www.coldfusionmuse.c...
which suggests a JVM upgrade should have fixed it. I will work around by using 'nearest' on the dev box and it shouldn't be an issue on the produciton server. Still wish I new what the hell was going on!
How to resize a animated gif file without lose the animation ?
I believe you can't - and it is documented as so.
Thank you for posting. This saved me the effort of doing the tests myself and sped up my resizing by a factor of 5. Why the hell was I using "highestquality"?
Great stuff here, Ray. In case readers here don't do the math, note that there's a 50x improvement between Ray's slowest (1.7 sec) and fastest (.031 sec) results. Good thing he mentioned the 4 minute one, to catch people's attention. :-)
Indeed, this issue of slow image resizing due to the default interpolation (of "highestquality") is one that I find is the cause of many slow CF servers, especially if people may do many image resize operations in a single request (such as a process to upload and create thumbnails of images).
More than that, while Ray shows using imageScaleToFit, note that this issue applies also to imageResize and CFIMAGE action="resize". Indeed, until CF10, you can't even change the interpolation using the tag, so you need to switch to using the function.
I did a blog entry just yesterday on this (the defaults in the tag and functions, how to change from the tag to the function, and the change in CF10, among other things) at http://www.carehart.org/blo.... Readers should find it complements Ray's entry here nicely.
In fact, he added a comment there pointing me and my readers here, and I'm really glad he did. Great to see the numbers. This image resizing problem is a silent but often deadly killer (or crippler) that's important enough to warrant reminders as often as possible! Keep up the great work, Ray.
Damn.... while I knew there was a difference, the order of magnitude (50x) didn't sink in. Thanks for sharing that Charlie!
Has anyone tried to integrate the imgscalr jar into their CF app to use that resize method in comparison to the imageScaleToFit or imageResize functions from CF?
Just to follow up on the imgscalr (http://www.thebuzzmedia.com...
It is simple to integrate into CF and based off of some basic cpu usage analysis it seems far more efficient than the imageResize function in CF to shrink the size of an image while preserving quality. From the creators site:
This library makes uses of efficient Java2D scaling techniques advocated by the Java2D team which provides hardware accelerated operations on most platforms.
This library also implements the optimized incremental scaling algorithm proposed by Chris Campbell with some minor enhancements for good-looking (and quick) thumbnail generation (previously only possible with the discouraged Image.getScaledInstance method using the much slower SCALE_AREA_AVERAGE algorithm).
Anyways, hope it helps someone.
Matt