Post

High quality dynamically resized images with .net

This is a old post taken from my previous blogging system unfortunately I have temporary lost all the great comments with coding examples that people posted on the subject. Its worth checking out Nathanael Jones post on different pitfalls to avoid in image resizing and his module.

A lot of web sites make use of code which dynamically resizes images. This technique is great for producing thumbnails on the fly. In fact, I used it for the listing pages of this blog. I was a little disappointed with the quality. The image looked blurred and I could often see dithering or compression artefacts. While working on another project I have spent some time researching how you can increase image quality while resizing with .Net.

.Net provides a of set easy to use image manipulation classes called System.Drawing (GDI+). The default settings for these classes are based on speed not quality. There are many examples on the web of how to resize images using these classes, but most of them produce low quality images. The most basic example would have the following code.


System.IO.MemoryStream memoryStream = new System.IO.MemoryStream( byteArray );
System.Drawing.Image image = System.Drawing.Image.FromStream( memoryStream );
System.Drawing.Image thumbnail = new Bitmap(newWidth, newHeight);
System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage( thumbnail );
graphic.DrawImage(image, 0, 0, newWidth, newHeight);
thumbnail.Save(Response.OutputStream,System.Drawing.Imaging.ImageFormat.Jpeg);

You start with a byte array which contains the image data loaded from either a database or file. You then resize the bitmap using a number of System.Drawing methods and finally save the bitmap to an output stream. In this case the output stream is the Response output stream. The first improvement is to include a quality setting for the JPEG compression.


System.Drawing.Imaging.ImageCodecInfo [] Info = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
System.Drawing.Imaging.EncoderParameters Params = new System.Drawing.Imaging.EncoderParameters(1);
Params.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
Response.ContentType = Info[1].MimeType;
thumbnail.Save(Response.OutputStream,Info[1],Params);

Major benefits can be gained by resetting the Graphic object properties to use the most effective algorithms. The InterpolationMode especially effects resizing, the others are useful for when you are using any compositing methods. As we are using the DrawImage method which is a compositing method you should include all four properties.


graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.CompositingQuality = CompositingQuality.HighQuality;

By default, .Net will utilize a web-safe palette when converting a bitmap to an image suitable for a web page. The result is that most gif files created using the above code would produce badly dithered images. Morgan Skinner wrote a fantastic article “Optimizing Color Quantization for ASP.NET Images” on how to create the optimal palettes for gif output. If you wish to output gif files you should include his code in your project and modify your code to fork when creating gif or jpeg output.


System.IO.MemoryStream memoryStream = new System.IO.MemoryStream( byteArray );
System.Drawing.Image image = System.Drawing.Image.FromStream( memoryStream );
System.Drawing.Image thumbnail = new Bitmap( newWidth, newHeight );
System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage( thumbnail );

graphic.InterpolationMode = InterpolationMode.HighQualityBicubic; graphic.SmoothingMode = SmoothingMode.HighQuality; graphic.PixelOffsetMode = PixelOffsetMode.HighQuality; graphic.CompositingQuality = CompositingQuality.HighQuality; graphic.DrawImage(image, 0, 0, newWidth, newHeight); if( contentType == "image/gif" ) { using ( thumbnail ) { OctreeQuantizer quantizer = new OctreeQuantizer ( 255 , 8 ) ; using ( Bitmap quantized = quantizer.Quantize ( bitmap ) ) { Response.ContentType = "image/gif"; quantized.Save ( Response.OutputStream , ImageFormat.Gif ) ; } } } if( contentType == "image/jpeg" ) { info = ImageCodecInfo.GetImageEncoders(); EncoderParameters encoderParameters; encoderParameters = new EncoderParameters(1); encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L); Response.ContentType = "image/jpeg"; thumbnail.Save(Response.OutputStream, info[1], encoderParameters); }

All this additional processing can add load time. On commercial sites the developers at my company often build-in a caching mechanism for commonly requested sizes.

Although System.Drawing provides a GetThumbnailImage method it only works well when the requested thumbnail image has a size of about 120 x 120 pixels. If you request a large thumbnail image, there could be a noticeable loss of quality. This function also searches for thumbnails stored in the image data which can cause problems if the original was generated with a digital camera. Because of these issues I use the DrawImage method.

Data formats:

API