s there any reason to do image processing in unmanaged code anymore? GDI+’s managed classes may just be good enough to give the unmanaged approach (mainly in unmanaged C++) a run for its money. GDI+ classes allow you to read the metadata information stored in images recorded by a digital camera&151;and then resize those images while ensuring image quality. The ImageAttributes and ColorMatrix classes help with color adjustment and the image codecs class specifies the colour depth and quality of the jpeg images.
In this article, you’ll learn about GDI+’s enhanced image manipluation capabilities by building a multithreaded, image-resizing tool. Decide for yourself whether it’s better!
![]() | |
Figure 1. The sample application, showing various extracted metadata. This image was recorded using a Canon EOS D60. |
The Sample Application
The application, shown in Figure 1, was written using C#. Scale the image uniformly by specifying a percentage, or specify the height and width for the new image. You can also specify the number of threads you want to use to process the image. Simply select the image you want to resize and click the resize button (or select resize on the short cut menu).
When you select File?>Open, a common dialog screen allows you to choose the images you wish to modify. Various details about each of the selected images are extracted and populated in a data grid. Details such as focal length, aperture, and ISO speed are extracted from the metadata contained in the image.Right-click anywhere on the displayed image and you’ll see the shortcut menu (Figure 2). Use the short cut menu to carry out various image transformations.
![]() | |
Figure 2. The image resizing tool performs various colour transformations. |
Drawing Images in .NET
To draw images on the form, you need to let the .NET Framework know of your intention to handle the paint event:
this.Paint+=new PaintEventHandler(this.ImageProcessorUI_Paint);
You also need to provide an event handler?in this instance, ImageProcessorUI_Paint. Make sure its signature matches the PaintEventHandler delegate:
void ImageProcessorUI_Paint(object sender, PaintEventArgs e)
The Graphics Object
The Graphics object is contained in the PaintEventArgs parameter (which is the second parameter of the event handler ImageProcessorUI_Paint), and it summarizes the GDI+ drawing surface. Use it to draw images to the screen. The Graphics object has two important properties, both of which are enumerations from the System.Drawing.Drawing2D namespace:
- SmoothingMode: This property specifies whether anti-aliasing will be applied when drawing images. Anti-aliasing is used to smooth out distortion in images.
- InterpolationMode: This property is used when you stretch or shrink images. With shrinking, you are mapping several pixels to a single pixel. In stretching, you are mapping a single pixel to several pixels. .NET provides several algorithms to achieve this, and you specify the algorithm you wish to use with the InterpolationMode.
Reading MetaData Information from an Image
The sample application reads metadata information from digital images, and displays the focal length, aperture, shutter speed, ISO speed, and date the image was recorded. In .NET, an image is represented by the class System.Drawing.Bitmap (derived from the System.Drawing.Image class). PropertyItem in the System.Drawing namespace represents a particular piece of metadata. There are two methods on System.Drawing.Bitmap that you can use to write or read metadata from images:
- setPropertyItem: Use this method to write metadata information into images.
- getPropertyItem: Use this method to retrieve metadata stored in image files.
All the pieces of metadata in an image are available in the image’s PropertyItems property.
The Id property of a PropertyItem object is an integer that uniquely identifies the piece of metadata a PropertyItem represents. However, metadata pieces are identified in the real world using hexadecimal string, so you need to convert this integer to an hexadecimal string using some code like this (mg is an instance of a System.Drawing.Image class)
PropertyItem p = mg.PropertyItems[i]; string imageIdentifier = p.Id.ToString("X");
MSDN contains some well-known tags for the EXIF image format. The sample application contains additional tags that you can use. www.exif.org also contains an exhaustive list of all the tags a digital image can possibly contain.
Metadata comes in various data types: bytes, signed/unsigned rational, signed/unsigned, long, etc. This sample application contains many of examples for decoding some of these data types.
Resizing and Saving Images
Resizing an image is fairly easy. First, retrieve the image from file:
Image mg = Image.FromFile(sImageName,true);
Create a new bitmap with the desired dimensions:
Size newSize = new Size(10,20); Bitmap bp = new Bitmap(newSize.Width,newSize.Height);
Next, obtain a graphics object from the new image:
Graphics g = Graphics.FromImage(bp);
To control the quality of the image, specify the smoothing, interpolation, and pixeloffset modes:
g.SmoothingMode =SmoothingMode.HighQuality; g.InterpolationMode =InterpolationMode.HighQualityBicubic; g.PixelOffsetMode =PixelOffsetMode.HighQuality;
Then, specify the dimensions of the new image:
Rectangle rect=new Rectangle(0,0,newSize.Width,newSize.Height);Draw the old image on to the new image using the graphics object: g.DrawImage(mg,rect,0,0,mg.Width,mg.Height,GraphicsUnit.Pixel);
The image’s new dimensions are contained in a bitmap object (bp), which is stored in memory. At this point, you can copy the metadata from the old image into the new image:
foreach (PropertyItem pItem in mg.PropertyItems) { bp.SetPropertyItem(pItem); }
The method I used to save all image types apart from JPEG is:
bp.Save(obImage.Path,System.Drawing.Imaging.ImageFormat.Gif);
For jpeg images, I used another overloaded method of save, which allows for control over various properties of the new image, such as color depth and compression ratio. This method uses the ImageCodeInfo, Encoder, and EncoderParameter classes.
First, identify an image codec for the jpeg image. Use the ImageCodecInfo class (from System.Drawing.Image) to do this:
ImageCodecInfo[] codecs=ImageCodecInfo.GetImageEncoders();ImageCodecInfo codec = null;for (int i = 0; i
Ideally, you want to retain a colour depth of 24bits per pixel for the new image, and maintain a quality level of 80 percent of the original image (80 percent compression). To achieve this, create an encoder parameter for quality and colour depth:
if (codec!=null){Encoder encoderInstance=Encoder.Quality;EncoderParameters[] encoderParametersInstance=new EncoderParameters(2);EncoderParameter encoderParameterInstance=new EncoderParameter(encoderInstance, 80L);encoderParametersInstance.Param[0]=encoderParameterInstance;encoderInstance=Encoder.ColorDepth;encoderParameterInstance=new EncoderParameter(encoderInstance, 24L);encoderParametersInstance.Param[1]=encoderParameterInstance;}
Save the new image to the file using the image codec for jpeg files and an array of EncoderParameters:
bp.Save(ImagePath,codec,encoderParametersInstance);}
You use similar code when drawing the image to the form. The difference is that you do this in the onPaint event of the form, and obtain the graphics object, our drawing surface from the form. The sample application allows end users to perform some colour adjustment, using the ImageAttributes class. One of the overloaded versions of DrawImage takes a parameter of ImageAttributes class. Here's how to use an ImageAttributes class to scale the red colour component of an Image:
Create an ImageAttributes object:ImageAttributes attributes = new ImageAttributes();Create a 5 by 5 identity matrix:Float[][] colourArray = getIdentityMatrix();
When you apply the identity matrix to the image, it will leave the image unchanged. To scale the red colour of the image by a factor of two, change the element of the identity matrix at row 0 and column 0 to 2:
colourArray[0][0]=2f;Use this matrix to create a ColorMatrix object: ColorMatrix matrix = new ColorMatrix(colourArray);Pass this ColorMatrix object to the ImageAttribute object we created earlier:attributes.SetColorMatrix(matrix,ColorMatrixFlag.Default,ColorAdjustType.Bitmap); Specify smoothing mode, interpolation mode and PixelOffsetMode:e.Graphics.SmoothingMode = SmoothingMode.HighQuality;e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;Now go ahead and draw the image:e.Graphics.DrawImage(mg,rect,0,0,mg.Width,mg.Height, GraphicsUnit.Pixel,attributes);
I hope this sample application has wet your appetite. There is a lot more in the GDI+ classes than what could be covered here. Go ahead and explore!