Click here to Skip to main content
Click here to Skip to main content

An Image Viewer with Lossless Rotation, EXIF and Other Goodies

, 27 Aug 2003 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Frank Eden
Web Developer
Australia Australia
Frank has been in computing since the age of the dinosaurs. He prefers playing table tennis to writing C#, C++, VB, or any of the other dozen or more languages he has written since the Jurasic.

Comments and Discussions

 
GeneralMy vote of 2 PinmemberMarek.T2-Feb-10 4:24 
GeneralMy vote of 1 Pinmembermorrisjames6-Nov-09 22:19 
GeneralRe: My vote of 1 PinmemberMember 414098423-Nov-09 4:29 
GeneralNot that impressed PinmemberCode4Life0118-Apr-07 5:52 
GeneralRe: Not that impressed PinmemberFrank Downunder18-Apr-07 14:34 
Questionhow can user Question Pinmembersahelearamesh10-Feb-07 22:38 
AnswerRe: how can user Question PinstaffChristian Graus10-Feb-07 23:32 
GeneralSave is not lossless Pinmemberchaldon7-Nov-03 5:45 
GeneralRe: Save is not lossless PinmemberFrank Eden5-Jan-04 20:00 
Yes Id be interested in getting a copy of those jpegs that are lossy. Could you stick them on an FTP site somewhere please?
 
What i noticed is that I got a reduction in the file size after one 90 degree rotation but after three more it was back to original size. I made a loop that rotated a great many times, then used Photoshop to zoom right in looking for artefacts or differences and didnt see any. So I felt safe in saying that it was lossless, but i am interested to hear anything to the contrary.
GeneralRe: Save is not lossless PinmemberPaul Doidge28-Feb-04 3:51 
GeneralRe: Save is not lossless Pinmemberchaldon28-Feb-04 6:03 
GeneralRe: Save is not lossless PinmemberDavid Crowell29-Jun-04 12:37 
GeneralThanks Pinmemberbillbrob4-Nov-03 5:19 
GeneralVery nice! PineditorMarc Clifton28-Aug-03 13:11 
GeneralRe: Very nice! Pinmemberjuefu29-Aug-03 0:52 
GeneralRe: Very nice! PinmemberFrank Downunder30-Aug-03 23:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 28 Aug 2003
Article Copyright 2003 by Frank Eden
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid