Ryan Whitaker

Dishes of Ryan

Highly performant, caching image resizer?

I'm in the ocean in a life ring, somebody please pull me out!

We have a client whose website gets a lot of traffic.  Lots of product images.  Lots of image resizing going on.

What I need is a highly performant, .NET-based image resizer.  It needs to cache the resized images to disk so it doesn't have to resize every time the page loads.  Optionally, it'd be ideal if it supported varying image resizing algorithms.  And it'd be great if I could adjust the quality level.

For the past three years, I've used United Binary's AutoImageSize component.  When it works, it's fantastic.  But, it crashes like nobody's business.  And nothing hurts my business like a component that crashes like nobody's business.

Anyone have any ideas? 

(Besides rolling my own using the uber-easy .NET framework.  I don't have time.)

Posted: Oct 21 2004, 01:03 PM by RyanW | with 8 comment(s)
Filed under:

Comments

Jamie said:

I was looking for something similar, didn't really find anything though, so I knocked up a SQL Server based image store, which basically cached the images in an in-memory hashtable when they were requested. Works quite well, but its kinda tied the framework we are using, so wouldn't work outside of this.

Let us know if you find anything though, I'd be interested to hear.
# October 21, 2004 2:09 PM

Aapo Laakkonen said:

I have built just that beside I didn't have need for many different rezising algorithms (althought they can be easily added, as long as we can use GDI+'s algorithms: Look at InterpolationMode class) and my resizer only supports the formats that GDI+ supports (that is PNG, GIF, TIFF, JPEG and BMP).

I currently have these parameters:

Width (int)
Height (int)
IgnoreSize (bool)
AllowCrop (bool
MaintainAspectRatio (bool)
AllowScaleDown (bool)
AllowScaleUp (bool)

(quality, interpolation, outputformat, etc. settings can be easily added).

All bool values defaults to true and only width or height is needed. I call it FileCacheModule (as it caches everything I need it to cache, eg. Documents etc. that are inserted in db - in addition it detects if file is compressed (eg. zipped) and then it can uncompress it before caching and sending it to user - eg. if I want to cache some files so that I do not pollute database too much) and it is implemented as ASP.NET module and it hooks to HttpApplication.ResolveRequestCache event. I also use HttpContext.Current.RewritePath call at the end of request so that I don't have to play with Http headers and other stuff, eg. checking at server for ETags etc. IIS also sends proper 304 headers if content hasn't changed. I would like to share the code with you, but I think it's a little bit too much hooked at our project that uses it, so it needs some coding to make it usable in other projects. But here is just a few ideas that you can try.

I count the size and position like this (code needs optimizing, but you can get the idea):

<code>
double originalRatio = (double) originalWidth / (double) originalHeight;

if (canvasWidth == 0) {
canvasWidth = (int) ((double) canvasHeight * originalRatio);
}
if (canvasHeight == 0) {
canvasHeight = (int) ((double) canvasWidth / originalRatio);
}

double canvasRatio = (double) canvasWidth / (double) canvasHeight;

int thumbnailHeight = canvasHeight;
int thumbnailWidth = canvasWidth;
int positionX = 0;
int positionY = 0;

bool willScaleDown = ((originalWidth > canvasWidth) || (originalHeight > canvasHeight));
bool willScaleUp = ((originalWidth < canvasWidth) && (originalHeight < canvasHeight));

if (maintainAspectRatio) {

if ((willScaleDown && allowScaleDown) || (willScaleUp && allowScaleUp)) {

if (allowCrop) {

if (originalRatio < canvasRatio) {
thumbnailWidth = canvasWidth;
thumbnailHeight = (int) ((double) thumbnailWidth / originalRatio);
} else {
thumbnailHeight = canvasHeight;
thumbnailWidth = (int) ((double) thumbnailHeight * originalRatio);
}

} else {

if (originalRatio < canvasRatio) {
thumbnailHeight = canvasHeight;
thumbnailWidth = (int) ((double) thumbnailHeight * originalRatio);
} else {
thumbnailWidth = canvasWidth;
thumbnailHeight = (int) ((double) thumbnailWidth / originalRatio);
}

}

} else if (willScaleDown && !allowScaleDown) {
if (allowCrop) {
thumbnailWidth = originalWidth;
thumbnailHeight = originalHeight;
} else {
// USE ORIGINAL IMAGE
}
} else if (willScaleUp && !allowScaleUp) {
thumbnailWidth = originalWidth;
thumbnailHeight = originalHeight;
}

} else {

if ((willScaleDown && allowScaleDown) || (willScaleUp && allowScaleUp)) {
thumbnailWidth = canvasWidth;
thumbnailHeight = canvasHeight;
} else if (willScaleDown && !allowScaleDown) {
if (allowCrop) {
thumbnailWidth = originalWidth;
thumbnailHeight = originalHeight;
} else {
// USE ORIGINAL IMAGE
}
} else if (willScaleUp && !allowScaleUp) {
thumbnailWidth = originalWidth;
thumbnailHeight = originalHeight;
}

}

if (ignoreSize) {
if (canvasWidth > thumbnailWidth) canvasWidth = thumbnailWidth;
if (canvasHeight > thumbnailHeight) canvasHeight = thumbnailHeight;
}

positionX = (int) Math.Floor(((double) canvasWidth - (double) thumbnailWidth) / 2d);
positionY = (int) Math.Floor(((double) canvasHeight - (double) thumbnailHeight) / 2d);
</code>
# October 21, 2004 3:08 PM

Aapo Laakkonen said:

bool willScaleUp = ((originalWidth < canvasWidth) && (originalHeight < canvasHeight));

should be

bool willScaleUp = ((originalWidth < canvasWidth) || (originalHeight < canvasHeight));

I know that the counting sucks in some situations, but that's life, :-). Most of the time it delivers great results.
# October 21, 2004 5:10 PM

Aapo Laakkonen said:

Here is an example of scaling based on the countings above:

<code>
using (Image canvasImage = new Bitmap(canvasWidth, canvasHeight, PixelFormat.Format32bppArgb)) {
using (Graphics graphics = Graphics.FromImage(canvasImage)) {
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.DrawImage(
originalImage,
positionX,
positionY,
thumbnailWidth,
thumbnailHeight);
}
</code>

You can use transformations if you like to, but the above example just draws.
# October 21, 2004 5:19 PM

Ryan Whitaker said:

Aapo,

Thank you for the help. It appears as if there are no ready-made components that do it, so I may have to implement a solution like what you have. I appreciate your feedback!
# October 21, 2004 7:09 PM

Chad Bostick said:

Hi Ryan,

I tried to respond to your .NET Oklahoma Bloggers post, but it was already closed. I am a fellow .NET Oklahoman who blogs at www.rockthefreeworld.com. Not a whole lot of .NET talk there, but I might post something useful from time-to-time. Then again I probably won't, I'm pretty useless.

As far as the image re-sizer, my buddy Glenn is the image-God of our particular cubefarm. I won't post his code without permission, but I'll give you his contact info if you'd like.
# November 18, 2004 11:32 PM

Sony Alpha said:

Thank you very much. this site has provided me with an understanding of caching image resizer

, It's just what I was looking for!

# June 15, 2008 2:16 PM

Nathanael Jones said:

Rescue ship here!

I needed the same thing, so I wrote my own..  and after testing it for 1.5 years, I'm releasing it as a component!

It has a managed disk caching system. When the cache limit is reached, the least used items are cleaned out. Memory and client-side caching are also used.

It supports a variety of image formats, and has a easy syntax:

image.jpg?thumbnail=jpg&maxwidth=100&maxheight=100

The most important thing is that it's rock-solid. We haven't turned up any new bugs in over a year. No memory leaks, crashes, or other issues. And we use it *heavily* on a high traffic site.

Take a look:

nathanaeljones.com/.../asp-net-image-resizer

# August 7, 2008 1:36 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)