| While playing
around with some of the GDI+ classes to manipulate pictures from my digital
camera, I found that if you want to add or modify an image description
of an EXIF image file (EXIF
= JPEG
plus additional information) you can read
and write the image description with the PropertyItem data
structure. These PropertyItems are very useful to avoid the bit manipulating
in
the file structure of an exif file.
But the problem is:
when writing the image with the changed or new description the picture
part becomes recompressed. You can notice this from the file
size; you add information to the file and the file size decreases. When
you
repeat
changing the description, the image become more and more poor, because
jpg is a lossy compression. So how do you load and save an jpg or exif
file without recompressing the bitmap?
The trick
is to rotate the picture by 90 degrees. In this case the framework supplies
a lossless rewriting of a jpeg file:
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;
}
private void WriteNewDescriptionInImage(string Filename,string NewDescription)
{
Image Pic;
PropertyItem[] PropertyItems;
byte[] bDescription=new Byte[NewDescription.Length];
int i;
string FilenameTemp;
Encoder Enc=Encoder.Transformation;
EncoderParameters EncParms=new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo=GetEncoderInfo("image/jpeg");
// copy description into byte array
for (i=0;i<NewDescription.Length;i++) bDescription[i]=(byte)NewDescription[i];
// load the image to change
Pic=Image.FromFile(Filename);
// put the new description into the right property item
PropertyItems=Pic.PropertyItems;
PropertyItems[0].Id=0x010e; // 0x010e as specified in EXIF standard
PropertyItems[0].Type=2;
PropertyItems[0].Len=NewDescription.Length;
PropertyItems[0].Value=bDescription;
Pic.SetPropertyItem(PropertyItems[0]);
// we cannot store in the same image, so use a temporary image instead
FilenameTemp=Filename+".temp";
// for lossless rewriting must rotate the image by 90 degrees!
EncParm=new EncoderParameter(Enc,(long)EncoderValue.TransformRotate90);
EncParms.Param[0]=EncParm;
// now write the rotated image with new description
Pic.Save(FilenameTemp,CodecInfo,EncParms);
// for computers with low memory and large pictures: release memory now
Pic.Dispose();
Pic=null;
GC.Collect();
// delete the original file, will be replaced later
System.IO.File.Delete(Filename);
// now must rotate back the written picture
Pic=Image.FromFile(FilenameTemp);
EncParm=new EncoderParameter(Enc,(long)EncoderValue.TransformRotate270);
EncParms.Param[0]=EncParm;
Pic.Save(Filename,CodecInfo,EncParms);
// release memory now
Pic.Dispose();
Pic=null;
GC.Collect();
// delete the temporary picture
System.IO.File.Delete(FilenameTemp);
}
|
When saving to JPEG images, you can also control the compression
ratio of the algorithm. You must use a different overload of the Save method:
public void Save(
string filename,
ImageCodecInfo encoder,
EncoderParameters encoderParams
);
The first step is to get the ImageCodecInfo structure for the JPEG
image. The GDI+ interface provides no direct method to get this object. You
must resort
to a little trick—enumerate all the image encoders and check their
MIME type properties against the JPEG MIME type string (image/jpeg). The
ImageCodecInfo structure contains information inherent in the encoding and
decoding of the image.
The EncoderParameters argument represents an array of encoding parameters.
Each element of the array is an EncoderParameter type. Possible parameters
are listed as members of the static class Encoder. For example, the parameter
Compression lets you choose a compression engine for TIFF images. The Quality parameter lets you choose the desired quality of the JPEG compression. This
code compresses a JPEG with a 40:1 ratio:
// Set the quality to 40 (must be a long)
Encoder qualityEncoder = Encoder.Quality;
EncoderParameter ratio = new EncoderParameter(qualityEncoder, 40L);
// Add the
quality parameter to the list
codecParams = new EncoderParameters(1);
codecParams.Param[0] = ratio;
// Save to JPG
bmp.Save(fileName, jpegCodecInfo, codecParams);
Additionally, this MSDN page details more information, specifically that JPEG images must have a width and height in multiples of 16 in order to have completely "lossless" rotation:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/usingGDIPlus/usingimageencodersanddecoders/transformingajpegimagewithoutlossofinformation.asp
Hope this is useful to others!
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  | Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|