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

An Image Viewer with Lossless Rotation, EXIF and Other Goodies

0.00/5 (No votes)
27 Aug 2003 1  
This article demonstrates a simple viewer for JPEG images.

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"); 
 
    // load the image to change 

    Pic = Image.FromFile(fileName); 
 
    // we cannot store in the same image, so use a temporary image instead 

    FileNameTemp = fileName + ".temp"; 

    // for rewriting without recompression we must rotate the image 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); 
    Pic.Dispose(); 
    Pic = null; 

    // delete the original file, will be replaced later 

    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)) 
{ 
    // Compiler will call Dispose on 'pic' for us on exit from this block

}

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

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