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.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
TransformRotate270, depending on whether the
Orientation metadata value is
8. Also not shown is how I get the
Orientation metadata value from each image. That's just WPF twiddling and diddling with the
JpegBitmapDecoder classes that are also beyond the scope of this tip.
Here then is the code for
RotateImage and the helper function,
private void RotateImage(string filename, EncoderValue encoderValue)
var Enc = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
image = System.Drawing.Image.FromFile(filename);
PropertyItems = image.PropertyItems;
PropertyItems.Id = 0x0112;
PropertyItems.Type = 3;
PropertyItems.Len = 2;
byte orientation = new Byte;
orientation = 1;
orientation = 0;
PropertyItems.Value = orientation;
filenameTemp = filename + ".temp";
EncParm = new EncoderParameter(Enc, (long)encoderValue);
EncParms.Param = EncParm;
image.Save(filenameTemp, CodecInfo, EncParms);
image = null;
private static ImageCodecInfo GetEncoderInfo(String mimeType)
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
if (encoders[j].MimeType == mimeType)