
Nerd Level: High Today’s fractal is the result of playing around with the 4D color scheme I blogged about last week. The color scheme still requires way too much effort to get a reasonable result, but I have not given up on it yet.
Today’s topic is exif, jpeg, and .net.
Exif is a way of storing metadata inside jpeg files. I wanted to add exif or similar information to my images. I often lose track of the files on my hard drive, and then rediscover them later. It would be nice if these forgotten images had some information with them. The metadata will also stay with the image if it gets separated from the original post on the internet.
Adding exif metadata to the image should be a simple task. It is not. Well, to be accurate, it is simple, but not at all obvious. I will avoid boring you with the play by play, and just list some of the highlights.
My program is written in C# / .Net 4.0. Don’t bother looking in the msdn .net documentation. Exif is mentioned a few times, just enough to make you think you are on the right track, but it leads nowhere.
OK, perhaps I could post-process the image with a third party utility. I found several exif readers, but few writers. Almost all of the writers involved decompressing the jpeg image, adding the tags, and recompressing the image. With jpeg there is a loss of quality in every decode/encode cycle. I did some tests, the loss was not visually noticeable, but I knew it was there and that was unacceptable. It turns out you can change the exif information only if there is exif information in the original image, and the new information is “smaller”. That did not work for me.
OK, back to DIY. I can read the spec and twiddle the bits myself. exif spec. What? 150 pages just to attach a few strings to a jpeg? OK, exif is a lot more than a few strings and a copyright notice. It is obviously designed by a committee where everyone insisted that it be backwards compatible to many pre-existing non-standard “standards”. This is getting way too complicated. Maybe I should just give up on the idea and get better at keeping track of the files.
Back to .Net. Now you won’t find something clearly described as “Exif Tags”. However .Net does have a BitmapEncoder base class with many specific derived classes for the common image formats, jpeg, tiff, png, bmp, etc. It does make some sense to abstract out the format details. Bitmap.SaveAsJpg would be too obvious. The BitmapEncoder base class works with frames. Some formats have frames, so the concept is built into the base class. Jpg does not have frames, so it is just confusing and useless in that case. But that is the price to pay for the generalization.
Next there is a BitmapMetadata class. BitmapMetadata is also an abstraction which hides the details of how the data is formatted. OK, that is a nice design idea, but for me at least, it is not an obvious place to look first. The msdn documentation for the BitmapMetadata constructor lists four supported formats, gif, jpg, png, tiff. However there is an example which includes exif. I was unable to get the exif example to work, but specifying jpg format for the metadata got the job done.
Here is the source code. If you happen to be writing an application in .Net, and if you just happen to be wondering how to put exif information into a jpg, I hope this saves you some time. Or if you are a programmer, perhaps you can show me a better way to do this. (The code creates two files, full size and thumbnail. And obviously I would not hard-code my name if I ever share the program.)
void JpgSave( BitmapSource source, string file ) {
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
var scale = 100.0 / source.Width;
var thumb = new TransformedBitmap(source, new ScaleTransform(scale, scale));
var colorContext = new ReadOnlyCollection( new ColorContext[] {new ColorContext(source.Format)});
var metadata = new BitmapMetadata("jpg");
metadata.DateTaken = DateTime.Now.ToString();
metadata.Copyright = "2013 Earl Hinrichs";
metadata.ApplicationName = "ComputerArt";
metadata.Author = new ReadOnlyCollection( new List() {"Earl Hinrichs"} );
metadata.Subject = "Computer Art";
metadata.Comment = "Computer Generated Art";
metadata.Keywords = new ReadOnlyCollection( new List() {"Fractal", "Computer Generated" } );
encoder.Frames.Add(BitmapFrame.Create(source,thumb,metadata, colorContext ));
using(var stream = new FileStream(file, FileMode.CreateNew)) {
encoder.Save(stream);
stream.Close();
}
encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(BitmapFrame.Create(thumb,null, metadata,colorContext));
var thumbFile = file.Replace(".jpg", "_thumb.jpg");
using(var stream = new FileStream(thumbFile, FileMode.CreateNew)) {
encoder.Save(stream);
stream.Close();
}
}