Go Picture Crazy: Resize Images Using GDI+

ne of the many improvements of .NET’s GDI+ over its Win32-based predecessor is its high-level image processing API. It’s a doddle for Web servers running ASP.NET to stream JPEG data on the fly, and standalone desktop applications also benefit from a good set of methods to load, save, and convert image data from a variety of resources and media. However, just because it’s easy to do something with GDI+ doesn’t necessarily mean you should use it, and one place you’re likely to get stung?sooner or later?is when you try to resize an image.

The System.Drawing.Image and System.Drawing.Bitmap namespaces have every manner of method to manipulate image formats and properties, and bang!?in the middle of both namespaces is one method that looks exactly like what you want:

Image GetThumbnailImage(int thumbWidth, int thumbHeight ...) 

Sure enough, pass an Image or a Bitmap to GetThumbnailImage, and you’ll get back another image at the dimensions you specified. But you should take a cue from the method’s title: What it wants to return you is a thumbnail, not simply a resized image.

You want to manipulate the size of an original image, but the best method provided in GDI+ for doing so is designed to work with embedded thumbnails, not originals, causing poor image quality and lack of control over aspect ratios.

Use the ImageResize class and API to grab and manipulate images based on any property you find convenient.

Out on a Thumbnail
Working with thumbnails is inherently troublesome. The problem is, certain kinds of image formats (for example, JPEG) may already include embedded thumbnails, and under these circumstances GetThumbnailImage() just extracts the image’s existing thumbnail and scales it to the proportions you specify, rather than generating a new bitmap by resampling the original image. In fact, many digital cameras will generate and embed a thumbnail automatically whenever you take a picture, so that you can quickly browse through any pictures you’ve taken on the camera itself.

If you want to generate a thumbnail that’s substantially larger than the embedded thumbnail, GetThumbnailImage() is likely to produce a severely degraded image. For example, try downloading Figure 1 (a large file, so I’ve linked to it rather than try to display it in flow) and running the following code, which resizes it to 655 x 480 pixels:

void ThumbnailResize(string str_file, Size new_size)   {      Image src_image = Image.FromFile(str_file);      Image dst_image = src_image.GetThumbnailImage(new_size.Width,          new_size.Height, null, System.IntPtr.Zero);      dst_image.Save("tn-" + str_file,          System.Drawing.Imaging.ImageFormat.Jpeg);      dst_image.Dispose();      src_image.Dispose();   }   ThumbnailResize("aikido.jpg", new Size(655, 480)); 

Open up the rescaled image, and you’ll see something like Figure 2. Compare this with the same image resized in Paint Shop Pro (see Figure 3). The difference in quality is painfully apparent.

Figure 2. Resolution 421 Pixels per Inch.
Figure 3. Resolution 180 Pixels per Inch.
Editor’s note: Because we display supporting figures as thumbnail images on article pages, what you see on this page is essentially a thumbnail of a thumbnail. Please click on the thumbnails shown and view the images as popups in order to see the actual effect as the author intends.

Finer Control
Even when your source images don’t contain embedded thumbnails, though, there are good reasons why you can do better than call GetThumbnailImage() to do your resizing. For a start, GDI+ gives you considerable control over the way your image is resampled, so it makes sense to take advantage of this flexibility. Consider the following code:

void ImageResize(string str_file, Size new_size)   {      Image src_image = Image.FromFile(str_file);      Bitmap bitmap = new Bitmap(new_size.Width, new_size.Height,          src_image.PixelFormat);      Graphics new_g = Graphics.FromImage(bitmap);      new_g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;      new_g.InterpolationMode =         System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;      new_g.DrawImage(src_image, 0, 0, bitmap.Width, bitmap.Height);      src_image.Dispose();      bitmap.Save("ir-" + str_file, System.Drawing.Imaging.ImageFormat.Jpeg);      bitmap.Dispose();      new_g.Dispose();   }
Figure 4. Resolution 96 Pixels per Inch.

Having used your initial image to create a Graphics surface, you are now free to configure the way that surface is rendered. You achieve this by setting a variety of flags that the renderer will inspect when it composes the final image. Because you’re now working with the full image, any embedded thumbnails are conveniently overlooked. You can see the result in Figure 4, a vast improvement on Figure 2, and of a comparable quality with Figure 3.

In my experience, the default Graphics surface settings have been sufficient for most applications, but if you do want to customize things, the table below gives a few options to customize the smoothing, interpolation, and pixel-offset algorithms used to produce your new image.

Table 1. Smoothing, Interpolation, and Pixel Offset Modes for System.Graphics

Smoothing Mode Interpolation Mode Pixel Offset Mode
AntiAlias Bicubic Default
Default Bilinear Half
HighQuality Default HighQuality
HighSpeed High HighSpeed
None HighQualityBicubic None
? HighQualityBilinear ?
? Low ?
? NearestNeighbour ?

A Simple Resizing API
While the code above will produce good quality thumbnails of arbitrary dimensions, this isn’t always what you want. In fact, most of the time, you’ll want to keep the image’s aspect ratio the same, and resize it proportionally so that either the final image has a specified pixel width or height, or it has been scaled down to a specified percentage of its original dimensions. For convenience, I decided to put all this together into a dedicated assembly, which exposes the following simple API:

Table 2. API for the ImageResize class

Name Class Type Description
File Property String Sets the path from which the image should be loaded
Image Property Image Sets the image object from which to generate thumbnails
PreserveAspectRatio Property Bool Flag denoting whether the aspect ratio of the original image should be preserved (default is true)
UsePercentages Property Bool Whether to interpret the Width and Height properties as absolute dimensions or percentages of the original image dimensions
Width Property Double The width of the thumbnail
Height Property Double The height of the thumbnail (units depend on whether UsePercentages is true or false)
GetThumbnail Method Image Creates a thumbnail based on the properties that have been specified

I could just as soon have exposed a single, static method, but I find a formal ImageResize object both more intuitive and more flexible for several reasons. First, if you want a thumbnail of a specified height but don’t care about the width, you just set the ImageResize object’s Height property and don’t specify a Width. In contrast, if I’d implemented GetThumbnail as a fully parameterized method, you would have had to pass in some “magic number” (perhaps 0 or ?1) to signify that you didn’t want to specify this field.

Second, resizing large images takes a lot of processing power and uses a lot of RAM, as I discovered while experimenting with the raw JPEGs downloaded from my digital camera. With an object-based approach you can cache images for immediate performance benefits. For example, the first thing GetThumbnail does is to check whether the source image has changed; if not, it uses the last image it has available. This means that if you need to generate thumbnails repetitively from a single image (for example, by displaying the image in a window and letting the user drag it to a new size with the mouse), the loading step only happens once. By the same token, if none of the ImageResize object’s properties have changed between consecutive calls to GetThumbnail, it returns you a cached image rather than calculating a new one.

Finally, the object-based approach lets you group your preferred settings in one place, which makes for simpler code when you want to perform the same operation on a lot of different images.

To use the class, just compile the ImageResize assembly and add a reference to it in any projects that need to resize images. For example, the following code loads an image called “tabi.jpg,” and reduces it to 35 percent of its original size, while maintaining its aspect ratio:

ImageResize o = new ImageResize();   o.File = "c:/temp/tabi.jpg";   o.Height = 35;   o.UsePercentages = true;   o.GetThumbnail().Save("c:/temp/test.jpg",       System.Drawing.Imaging.ImageFormat.Jpeg); 

If instead I’d wanted to resize the image to absolute percentage or pixel dimensions, I would have specified a Width as well as a Height for my resized image.

Images for the Web
When I originally wrote the ImageResize class I envisaged I would mostly be using it from Windows Forms-based applications. However, it’s just as easy to use it server-side in order to stream dynamically resized images on demand. All you need to do is set the content type to “image/jpeg” and stream the image directly into the Response stream in your Page_Load() method:

Response.ContentType = "image/jpeg";   ImageResize o = new ImageResize();      // Load your image and perform any resizing here   o.File = ?      o.GetThumbnail().Save(Response.OutputStream,       System.Drawing.Imaging.ImageFormat.Jpeg); 

Hopefully this simple class will cover most of your image resizing needs, as well as take care of basic image caching issues, leaving you free to concentrate on the more important programming tasks. It should also keep you from scratching your head when GetThumbnailImage doesn’t return the data you expect.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts