Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using GDI+ code in a WPF application for lossless JPEG image rotation

0.00/5 (No votes)
9 Mar 2010 1  
How to use GDI+ code in a WPF application for lossless JPEG image rotation

About six months ago, I started investigating how to perform lossless JPEG bitmap image rotation under WPF and started a thread on The Code Project to see if anyone had a solution.

I also found (and contributed to) this thread on the Microsoft .NET Framework Developer Center.

The last post in the latter thread was my own, dated June 9, 2009, with no interest expressed since. Perhaps everybody was waiting until .NET 4.0 came out to see if the problem would be addressed. Unfortunately I see no evidence that it has. Perhaps it wasn't addressed because it is relatively easy to add GDI+ code to your WPF app to solve the problem. Unfortunately, the GDI+ solution is only good for JPEG, because GDI+ doesn't support Windows Media Photo Files (.wdp), which has compression superior to JPEG and is supported in WPF.

In any case, I was getting increasing pressure to add lossless JPEG rotation to my application from beta testers and now that it looked as if .NET 4.0 wasn't going to be of any help, I decided it was time to bite the bullet and solve the problem in my WPF app through GDI+ code. A side-benefit was that the entire image metadata block was transparently transferred intact to the destination image, except for the Orientation metadata item which I had to set to 1 to indicate that rotation was no longer necessary in the JPEG image file.

A caveat is that lossless JPEG image rotation is only mathematically possible if pixel dimensions of both width and height are multiples of 16, because the basic units of JPEG bitmaps are pixel arrays 16x16. That's not that big a deal because the native formats of commercial digital cameras meet this restriction.

The first thing you have to do to integrate GDI+ into your WPF application is to include the System.Drawing and System.Drawing.Imaging namespaces into your assembly. To enable that, you have to add a reference to the System.Drawing .NET assembly. After that, you can compile references to GDI+ classes such as System.Drawing.Image and several related classes that you'll need.

The only code I'm going to quote is the specific functions that access GDI+: RotateImage and helper function, GetEncoderInfo. But higher level code that calls RotateImage iterates through a list of a large number of image file paths in a folder hierarchy of indefinite depth. Code to do that enumeration and iteration is just standard C#/WPF code that need not distract us here. But because there could be thousands of JPEG image files in the enumeration, you have to execute the code in a separate thread to keep the UI responsive. I do that through the WPF BackgroundWorker thread, the details of which are also beyond the scope of this tip.

Worth mentioning however is the EncoderValue parameter input to RotateImage. This is one of three values that is determined from calling code that looks at the Orientation metadata value in each JPEG image file. The GDI+ EncoderValues of interest are TransformRotate90, TransformRotate180, and TransformRotate270, depending on whether the Orientation metadata value is 6, 3, or 8. Also not shown is how I get the Orientation metadata value from each image. That's just WPF twiddling and diddling with the BitmapMetadata and JpegBitmapDecoder classes that are also beyond the scope of this tip.

Here then is the code for RotateImage and the helper function, GetEncoderInfo:

private void RotateImage(string filename, EncoderValue encoderValue)
{
    System.Drawing.Image image;
    PropertyItem[] PropertyItems;
    string filenameTemp;
    var Enc = System.Drawing.Imaging.Encoder.Transformation;
    EncoderParameters EncParms = new EncoderParameters(1);
    EncoderParameter EncParm;
    ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
 
    // Load the image to rotate.
    image = System.Drawing.Image.FromFile(filename);
 
    // We're correcting the orientation, so set it to 1.
    PropertyItems = image.PropertyItems;
    PropertyItems[0].Id = 0x0112; // 0x0112 as specified in EXIF standard
    PropertyItems[0].Type = 3;
    PropertyItems[0].Len = 2;
    byte[] orientation = new Byte[2];
    orientation[0] = 1; // little Endian
    orientation[1] = 0;
    PropertyItems[0].Value = orientation;
    image.SetPropertyItem(PropertyItems[0]);
 
    // We cannot store into the same image, so use a temporary image instead.
    filenameTemp = filename + ".temp";
 
    // For lossless rewriting, we must rotate the image by 90 degree increments.
    EncParm = new EncoderParameter(Enc, (long)encoderValue);
    EncParms.Param[0] = EncParm;
 
    // Now write the rotated image with Orientation metadata set to 1.
    image.Save(filenameTemp, CodecInfo, EncParms);
 
    // Release memory now, since we could be doing a bunch of these.
    image.Dispose();
    image = null;
    GC.Collect();
 
    // Delete the original file, which will be replaced below.
    System.IO.File.Delete(filename);
 
    // Replace the original file with the rotated file.
    File.Move(filenameTemp, filename);
 
    // Delete the temporary image.
    System.IO.File.Delete(filenameTemp);
}
 
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    int j;
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    for (j = 0; j < encoders.Length; ++j)
    {
        if (encoders[j].MimeType == mimeType)
            return encoders[j];
    }
    return null;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here