Overview
This article demonstrates a simple viewer for JPEG images. Several interesting coding techniques are demonstrated, including threads, doubly linked lists, and image handling. The program will show all images in a folder, including subfolders, and will dynamically resize the pictures to use the whole screen. In addition, rotating the image through 90 degrees is supported, as is access to the extended information within the image, allowing the user to display or change comments and other information stored in the picture.
Introduction
I got interested in digital photography and came home from a holiday in Western Australia with 500 or so photographs, most of which were taken on a 2 Megapixel camera. Many were taken in portrait mode, and so needed rotation through 90 degrees. I wanted to rotate these without recompression, a job even Photoshop is apparently unable to do. But once I discovered that the .NET Framework classes could do the job easily, I decided to knock together an app to do the job. At the same time, it could allow me to simply look at the photos one after the other by pressing the spacebar. I know that there are plenty of programs around that do this sort of thing, including the ability to create virtual albums, but what the heck, I think mine is simple and easier to use. Some of the viewers I have looked are actually fairly poor. I don't want to name names, but one commercial product that came with a friend's camera refused to display some of my pictures, so .NET to the rescue is what I say!
One of the nifty things I wanted to do was to get my viewer to display EVERYTHING under the initial folder, irrespective of what subfolder they are in. Now there are various ways to navigate a hierarchy. The easiest way to do so is of course to use recursion. In my case I wanted to view all files within a folder (including all subfolders of that folder).
For example:
void showFiles(string path)
{
foreach (string fn in Directory.GetFiles(path) )
{
Console.WriteLine(fn);
}
foreach (string folder in Directory.GetFolders(path) )
{
showFiles(folder);
}
}
It turns out that there are classes of problems that don�t lend themselves too well to this approach � it all depends on who is �in the drivers seat�. I also I didn�t want to pollute my viewer with file navigation code, so I decided to arrange for all the files to present themselves as though they are in a simple list. Of course I could have simply created such a list, using recursion, but I wanted to play with threads, so I wrote a �producer� thread that delivers up the files one at a time. The code in this thread is presented as a separate submission � look for the article titled �Flattening a Hierarchy � a producer thread to get all files in a folder and subfolders�. (Of course if your hierarchy is very large, putting the items into a list first, is not always possible due to lack of memory, which was another motivation for developing the technique).
Resizing the image to use the whole screen
When I look at a picture, I want to see all of it. I just cannot understand why JPEGs open up in Internet Explorer showing only a part of the image. So first off I created a form and placed a PictureBox
on it with the property SizeMode
set to StretchImage
.
And then I needed to ensure the aspect ratio didn�t get messed up, so I wrote this code.
public static void ShowPicture(PictureBox picBox,
Size intendedSize, string fileName)
{
bool tooSmallToResize = true;
etc...
Size newSize = new Size(intendedSize.Width, intendedSize.Height);
if ( (double)intendedSize.Width/intendedSize.Height > aspectRatio)
{
newSize.Width = (int)(intendedSize.Height * aspectRatio);
}
else
{
newSize.Height = (int)(intendedSize.Width / aspectRatio);
}
...
}
I also messed with the location so that if necessary, the picture is centered in the screen. If it is too small, I don't resize it at all.
Rotating an image
I wanted it to be REAL easy to rotate an image, so I wrote a handler for keyboard events. When the user presses �R� (for Rotate), I rotate it 90 degrees. If this is not the right orientation, just press it a couple more times!
To test that no recompression happens, I wrote some temporary code to rotate the image 2,000 times, and compared the result with the original. They were the same size and I couldn�t tell any difference even after zooming right in close.
public static void Rotate90(string fileName)
{
Image Pic;
string FileNameTemp;
Encoder Enc = Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
Pic = Image.FromFile(fileName);
FileNameTemp = fileName + ".temp";
EncParm = new EncoderParameter(Enc,(long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
Pic.Save(FileNameTemp,CodecInfo,EncParms);
Pic.Dispose();
Pic = null;
System.IO.File.Delete(fileName);
System.IO.File.Move(FileNameTemp, fileName);
}
Extended Information for JPEG images
EXIF information is useful to allow you to record commentary about your picture, for example the names of the people in the picture, or where the picture was taken. Press I or use the right mouse to bring up the extended properties for an image.
One small tricky bit of code, results from the fact that the date & time the photo was taken is in the form yyyy:mm:dd hh:mm:ss, with hours in 24 hour notation. The following code snippet shows how I solved the parsing problem for dates in this format.
string d = "2003:01:03 23:30:02";
DateTimeFormatInfo info = new DateTimeFormatInfo();
info.ShortDatePattern = "yyyy:MM:dd HH:mm:ss";
DateTime dt = DateTime.ParseExact(d, "d", info);
Browse for folder
In version 1.0 of the .NET framework, you need to derive a class from FolderNameEditor
. In version 1.1 however, there is a new class, the FolderBrowser
class, which is much easier. Look for #if FRAMEWORK11 in MainPropertiesForm.css.
Doubly linked list with capacity limit
To keep a history of the pictures you have viewed, (to allow you to go back instead of just forwards), I kept a list of them - but as a twist, I put a capacity limit on the doubly linked list - check the code in DoublyLinkedList.cs.
Moving to the next picture
You may notice that the form uses the MoveNext
function of the enumerator interface to move to the next picture. By using this interface, the code does not need to concern itself with the kind of list we are moving through. By adding support for albums, we do not need to change the code at all. Instead we just ensure that we have an enumerator that can iterate through the pictures in the albums.
Album support
A work in progress! There is support for creating an album, but not for selecting one and using it. I have made it easy to implement though, by making the form use IEnumerator
, which means you only need to change some infrastructure in the main form. If anyone wants to write that, send me the code!
Making thumbnails and web-size piccies
There is some support for creating thumbnails � press �T� and it will silently do it. Web size reductions? - more work in progress, but shouldn�t be too dissimilar to making thumbnails.
FxCop
Use it! Its mighty hard to get rid of all the messages, but its a worthy goal! If you haven't got a copy, go here http://www.gotdotnet.com/team/fxcop.
Garbage collector and dispose
I had loads of trouble getting calls to dispose in the right place, to avoid random "file in use" errors. For a while, I even had to resort to using the garbage collector. Even this wasn�t reliable as I have since found out that, the garbage collector runs on a separate thread! Anyway, you wont find any calls to Dispose()
or to GC.Collect()
in the code now, since I found a better way:
using (Image Pic = Image.FromFile(fileName))
{
}
Command line tools for manipulation of images
For those of you who are interested in command line tools to manipulate images, I can recommend the following article by my learned and sometimes annoying colleague, Michael Still - http://www-106.ibm.com/developerworks/linux/library/l-graf/.
Possible enhancements
It would be rather cool if, after selecting pictures to make an album, you could automatically prepare a website.
Acknowledgements
Thanks to Vernon Zhou who helped write this program, and to all the other excellent CodeProject contributors who gave me a head start, and especially Doug Hanhart http://www.dotnet247.com/247reference/msgs/28/144569.aspx