Creating great thumbnails in ASP.NET

The built in function for creating thumbnails in ASP.NET is extremely convenient and very simple to implement.

int width = 190;
int height = 190;
Bitmap source = new Bitmap("c:\someimage.gif");
System.Drawing.Image thumb = source.GetThumbnailImage(width,height,null,IntPtr.Zero);

 original (31.7k)

The trouble is that it produces relatively poor quality results and excessively large file sizes. The thumbnails tend to look very muddy when using this route, but many times it's good enough for whatever your needs may be.

An Alternative

The alternative that I use regularly involves redrawing the image using the System.Drawing.Graphics library. It is very simple to implement but produces superior results if for no other reason than its file size. The following is the standard function I use for creating thumbnails.

public static Bitmap CreateThumbnail(Bitmap source, int thumbWi, int thumbHi, bool maintainAspect)
        {
            // return the source image if it's smaller than the designated thumbnail
            if (source.Width < thumbWi && source.Height < thumbHi) return source;

            System.Drawing.Bitmap ret = null;
            try
            {
                int wi, hi;

                wi = thumbWi;
                hi = thumbHi;

                if (maintainAspect)
                {
                    // maintain the aspect ratio despite the thumbnail size parameters
                    if (source.Width > source.Height)
                    {
                        wi = thumbWi;
                        hi = (int)(source.Height * ((decimal)thumbWi / source.Width));
                    }
                    else
                    {
                        hi = thumbHi;
                        wi = (int)(source.Width * ((decimal)thumbHi / source.Height));
                    }
                }

                // original code that creates lousy thumbnails
                // System.Drawing.Image ret = source.GetThumbnailImage(wi,hi,null,IntPtr.Zero);
                ret = new Bitmap(wi, hi);
                using (Graphics g = Graphics.FromImage(ret))
                {
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.FillRectangle(Brushes.White, 0, 0, wi, hi);
                    g.DrawImage(source, 0, 0, wi, hi);
                }
            }
            catch
            {
                ret = null;
            }

            return ret;
        }

aG8klKbxhE-h43x2_tLGsw (10.5k)

This function is handy because it's parameters include a flag for maintaining the aspect ratio of the image along with the thumbnail size you would like. The thumbnail magic happens in this portion of the code:

                using (Graphics g = Graphics.FromImage(ret))
                {
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.FillRectangle(Brushes.White, 0, 0, wi, hi);
                    g.DrawImage(source, 0, 0, wi, hi);
                }

This method is slightly slower but the results are hard to ignore as illustrated by the comparison below:

(31.7k)original aG8klKbxhE-h43x2_tLGsw (10.5k)

Pretty nice improvement in both file size and quality I would say, but....

We can do even better

Now we can add into the mix some JPEG compression and really optimize the results. I won't pretend to fully understand how the JPEG compression code below works, but it sure does the trick.

                //Configure JPEG Compression Engine
                System.Drawing.Imaging.EncoderParameters encoderParams = new System.Drawing.Imaging.EncoderParameters();
                long[] quality = new long[1];
                quality[0] = 75;
                System.Drawing.Imaging.EncoderParameter encoderParam = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
                encoderParams.Param[0] = encoderParam;

                System.Drawing.Imaging.ImageCodecInfo[] arrayICI = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
                System.Drawing.Imaging.ImageCodecInfo jpegICI = null;
                for (int x = 0; x < arrayICI.Length; x++)
                {
                    if (arrayICI[x].FormatDescription.Equals("JPEG"))
                    {
                        jpegICI = arrayICI[x];
                        break;
                    }
                }

This code will set up the encoderParameters needed for saving the new compressed thumbnail. The quality[0] value is where you set the compression level. I've had success going as low as a value of 40 for some applications, but when quality is a major requirement I find 75 to do very well. To use this engine you would execute the JPEG Compression code before you save your thumbnail, then use its encoderParamaters as a parameter when saving. For example:

	System.Drawing.Image myThumbnail = CreateThumbnail(myBitmap,Width,Height,false);                

	//Configure JPEG Compression Engine
                System.Drawing.Imaging.EncoderParameters encoderParams = new System.Drawing.Imaging.EncoderParameters();
                long[] quality = new long[1];
                quality[0] = 75;
                System.Drawing.Imaging.EncoderParameter encoderParam = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
                encoderParams.Param[0] = encoderParam;

                System.Drawing.Imaging.ImageCodecInfo[] arrayICI = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
                System.Drawing.Imaging.ImageCodecInfo jpegICI = null;
                for (int x = 0; x < arrayICI.Length; x++)
                {
                    if (arrayICI[x].FormatDescription.Equals("JPEG"))
                    {
                        jpegICI = arrayICI[x];
                        break;
                    }
                }
	
	myThumbnail.Save(Path.Combine(SavePathThumb, fileName), jpegICI, encoderParams);
                myThumbnail.Dispose();

compressed (2.39k)

Which still looks pretty darn good for 2.39k.

Conclusion and final comparison

Here is the final comparison between the 3 thumbnails going from largest file size to smallest:

original aG8klKbxhE-h43x2_tLGsw compressed

Largest = 31.7k

Uncompressed redraw = 10.5k (67% smaller)

Compressed redraw = 2.39k (92% smaller)

It's hard to ignore those results. The source code for the thumbnail function and the JPEG compression engine are below.

ThumbnailGenerator.cs (1.97 kb)

JPEGCompressionConfig.cs (969.00 bytes)

kick it on DotNetKicks.com


Related posts

Comments

May 10. 2008 06:25 AM

Jonathan Adams

I use this method on my website www.watchfinder.co.uk to auto thumbnail and water mark my images, I neat thing to do especially to save your servers processing power, is to flush the re-sized image out to disk and create a cache.

This gives you the convenence of auto thumbnailing but without the headache of killing your server when the traffic goes up Smile

Jonathan Adams

May 10. 2008 08:01 AM

Matt

That is a good tip Jonathan, I haven't thought about auto thumbnailing images. So far whenever I've needed to generate a thumbnail I create the physical file at the time the image is processed, whether it be uploaded or imported etc.

I can see how thumbnailing at runtime could simplify image display since there would only be one file, one filename and one location for each image.

Matt

May 10. 2008 09:31 AM

xoner

Thanks, great post!!

xoner

May 10. 2008 10:22 AM

pingback

Pingback from alvinashcraft.com

Dew Drop - May 10, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

May 12. 2008 06:42 PM

pingback

Pingback from davidabrahams.wordpress.com

Great thumbnail generation for ASP.NET « David Abrahams’ Weblog

davidabrahams.wordpress.com

May 13. 2008 04:52 AM

dmitry39

thx, good solution for our tasks.

Regards from Russia Smile

dmitry39

May 13. 2008 06:14 AM

Richard H

Great post, I've done a similar image component where I can pass it the width and/or height I want it to resize to and let it figure out the rest. It's also pretty easy to layover watermarks, or even add a dropshadow image around the thumb you're resizing (so long as you get a consistent thumbnail size for all your thumbs).

The reason the "quick and easy" way is so dirty is because the thumbnail it shows is actually the thumbnail that many image programs save into the JPEG when saving the JPEG, and it is horrendously low quality for that reason.

As for the JPEG compression level, even 100% quality still compresses it somewhat, it doesn't generate an uncompressed image. I think there are some other tweaks you can do with encoder parameters by setting the interpolation type for resizing of the image (it's been a while since I did mine), I'll try and dig them out and post them if you're interested.

Richard H

May 13. 2008 08:24 AM

Matt

@Richard H,

I would definitely be interested in learning more ways to manipulate images. The watermarking would be great to know and also I'm curious about image cropping if you have any knowledge on that topic. Thanks!

Matt

May 13. 2008 07:01 PM

Richard H

Bear with me - it's been a while since I wrote this... turns out my code doesn't have interpolation settings in it, although I have done that on something else. It looks as though I went down a slightly different route with saving the image via a memory stream then byte array, possibly due to how I manipulated the image, possibly not.

This example also had image cropping in it too, as it happens Smile You have to set a source and destination crop rectangle, which handles the resizing also. For the mask, allow the space around the image in this case - but if you can use a transparent GIF you can set it the same size, and do overlay. I didn't have much luck with transparent PNG's. You can't do really decent transparency on PNG-8, it has to be PNG-24, and I don't think I had any luck with getting ASP.Net to read those well.

Rectangle srcRect = new Rectangle(0, 0, originalImage.Width, originalImage.Height);
Rectangle destRect = new Rectangle(5, 5, NewWidth, NewHeight);

Rectangle maskRect = new Rectangle(0, 0, NewWidth + WidthOfMask), NewHeight + HeightOfMask);



To create the drop shadow mask:

System.Drawing.Image maskImage;
maskImage = new Bitmap(Server.MapPath(maskImage));

System.Drawing.Image oriImage = System.Drawing.Image.FromFile(Server.MapPath(originalImagePath));

Size newSize = new Size(NewWidth + WidthOfMask, NewHeight + HeightOfMask);
System.Drawing.Bitmap newBitmap = new System.Drawing.Bitmap(originalImage, newSize);

Graphics thisG = Graphics.FromImage(newBitmap); // Create new empty image with correct size

// Clear image first
thisG.Clear(Color.White);

// Now add place bg shadow
thisG.DrawImage(maskImage, maskRect, maskRect, GraphicsUnit.Pixel);

// Now add image
thisG.DrawImage(originalImage, destRect, srcRect, GraphicsUnit.Pixel);


That takes care of resizing and adding a mask surrounding the image. Loading a watermark is exactly the same - just with a transparent image, and not necessarily with buffer padding around the edges.

As for cropping the image, I've written a couple of routines that determined whether it was a portrait or landscape image, and cropped either the full height or full width out, to get a constant e.g. 150x112 out of the middle of the image the largest it could be, edge to edge on either height or width. Again - it's all in determining the boundaries of your source rectangle, and getting it the same aspect as your destination rectangle.

Phew - it's too late for this stuff! If that needs any more explaining, just ask Smile

Richard H

May 14. 2008 07:50 AM

pingback

Pingback from code-inside.de

Wöchentliche Rundablage: ASP.NET MVC, Silverlight 2, TDD, WPF, jQuery… | Code-Inside Blog

code-inside.de

May 14. 2008 09:46 AM

pingback

Pingback from kevinchamplin.com

Creating great thumbnails in ASP.NET

kevinchamplin.com

May 21. 2008 03:23 AM

Cliff

Hi all,

You don't by any chance have a VB.Net version of this code do you....

Also not being good with C I can't see where the image is writen to the output ie: how you get it to display on screen where you want it. Do you need an Image Placeholder on the screen that you write the ImageDraw to...

Great explaination though.

Cliff

May 21. 2008 04:37 AM

pingback

Pingback from blogs.microsoft.co.il

Better thumbnail quality with less size – see how - I hate Spaghetti (code)

blogs.microsoft.co.il

May 21. 2008 06:04 AM

Guy Harwood

if you simply flip the image 180 each way before you render the thumbnail you will get good results.

this ensures it doesnt use the embedded thumbnail that most digi cameras automatically include in every picture.

Guy Harwood

May 21. 2008 07:20 AM

mike

Shouldn't you decide the encoding based on the original? If the original is a gif, the thumbnail should be a gif too. Same with png.

Is that possible?

mike

May 21. 2008 09:08 AM

Matt

@Cliff,

The CreateThumbnail function will return a Bitmap object. Once you have that object you can do whatever it is you need to do with it. In my experience, the need I've had to generate a thumbnail came at the time the original image was uploaded. When the original image was being save to the server, I would create a thumbnail and save the thumbnail to the server as well. You can then use the thumbnail from the disk to display on your pages however you see fit.

@Mike,
Using this technique I'm not sure that the original encoding matters because the image is being loaded as a Bitmap regardless. No matter what the original encoding is, the image manipulation is done on a Bitmap object then re saved using JPEG encoding. It could very well be that you can get better quality results by testing for the original encoding and using it throughout instead of converting to a bitmap etc.

Thanks for your comments!

Matt

May 21. 2008 12:00 PM

Becoming a pilot

Hey Matt, thanks a lot! I have a generic ashx for generating my images just using plain GetThumbnailImage...While image quality isn't the best, I haven't cared much since it's *good enough* for most of my projects. I have never even paid any attention for the image size. So I will certainly change it to use your code instead. Guess that's a problem by only dynamically generating the thumbs: you never see the size of the file Smile

Becoming a pilot

May 21. 2008 08:36 PM

pingback

Pingback from blog.nunogomes.net

Very useful links

blog.nunogomes.net

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

May 22. 2008 03:48 PM

Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008