Introduction
ImageFan is a lightweight image viewer for .NET 2.0, supporting multi-core processing. At the moment it is in beta stage and stable on the implemented functionality.
Background
I have always wanted to exploit the capabilities of .NET to create an image viewer offering a managed (possibly portable due to Mono) counterpart to the existing C / C++ solutions. Also, the lack of 64 bit image viewers has enticed me to pursue a .NET solution that would implicitly run as a 64 bit application, hardware and software platform permitting.
Visual Layout and Functionality
The application is designed in the traditional style of contemporary image viewers as follows:
- A drives list and corresponding directories tree on the left-hand side of the window
- A thumbnails list on the right-hand side, revealing thumbnails to the images contained in the selected drive and directory
Selecting a drive refreshes the list of directories and reveals the thumbnails of the images contained on the root drive. Selecting a specific directory fills the thumbnails list with the appropriate thumbnails. The thumbnails list can be navigated on by using the arrow keys and the scroll bar.
When the left mouse button is clicked or the Enter key pressed, while on a thumbnail, a new window is opened, containing the image in full size, scrollable if at least one dimension of the image is greater than the corresponding screen size dimension. The user can traverse the images list using this window, by employing the Space and Backspace keys and the arrow keys. The window can be closed by pressing the Esc key or the closing button on the window.
If the image window is clicked on again or pressed Enter upon, it will set the image to full screen view, resizing it if necessary to fit on the screen. In the full screen mode, the thumbnails list is navigable with the Space and Backspace keys, the arrow keys and the mouse wheel. The user can exit this mode by pressing the Esc key or the left mouse button.
Implementation Challenges and Constraints
The project structure is revealed in the diagram below. I will explain each relevant source code artifact in turn.
Class TasksDispatcher
This class is a general tasks dispatcher that partitions an array of tasks on the number of available processor cores. In the previous versions, the class used raw threads. In this release, it relies on the ThreadPool
class of .NET and, thus, on pooled threads, minimizing thread creation and destruction costs.
For the problem at hand, I use the tasks dispatcher class to partition (by their indexes) the collection of image thumbnails, which is to be displayed asynchronously when the user selects a directory.
namespace ImageFan
{
class TasksDispatcher
{
static TasksDispatcher()
{
ProcessorCount = Environment.ProcessorCount;
IndividualTaskEvents = new AutoResetEvent[ProcessorCount];
for (int i = 0; i < ProcessorCount; i++)
IndividualTaskEvents[i] = new AutoResetEvent(false);
}
public TasksDispatcher(WaitCallback globalTask, WaitCallback individualTask, int tasksCount)
{
this.globalTask = globalTask;
this.individualTask = individualTask;
this.tasksCount = tasksCount;
}
public void Start()
{
dispatcherIsActive = true;
ThreadPool.QueueUserWorkItem(WorkerThreadLoopMethod);
}
public void Stop()
{
dispatcherIsActive = false;
}
#region Private
private static readonly int ProcessorCount;
private static AutoResetEvent[] IndividualTaskEvents;
private WaitCallback globalTask;
private WaitCallback individualTask;
private int tasksCount;
private volatile bool dispatcherIsActive;
private void WorkerThreadLoopMethod(object state)
{
try
{
for (int i = 0; (i < tasksCount) && (dispatcherIsActive); i += ProcessorCount)
{
int j;
for (j = 0; (j < ProcessorCount) && (i + j < tasksCount) && (dispatcherIsActive); j++)
globalTask(i + j);
for (j = 0; (j < ProcessorCount) && (i + j < tasksCount) && (dispatcherIsActive); j++)
ThreadPool.QueueUserWorkItem(IndividualTaskWrapper, new TaskParameter() { mainIndex = i + j, threadIndex = j });
for (j = 0; (j < ProcessorCount) && (i + j < tasksCount) && (dispatcherIsActive); j++)
IndividualTaskEvents[j].Set();
}
}
catch
{
}
}
private void IndividualTaskWrapper(object state)
{
try
{
TaskParameter taskParam = ((TaskParameter)state);
IndividualTaskEvents[taskParam.threadIndex].WaitOne();
individualTask(taskParam.mainIndex);
}
catch
{
}
}
#endregion
}
}
Class FolderTreeView
This class is a Windows Form custom control, extending the TreeView
control. It displays the folders (directories) on the selected drive in a tree-like manner and, when a directory node is selected, triggers the display of thumbnails in the ThumbnailsSequence
control.
Class ThumbnailBox
This type is a Windows Form custom control, inheriting from the UserControl
class. It is a variable-size control that displays an image thumbnail box, containing the image thumbnail itself, decorated with the image file name. Its largest dimension, whether width or height, is scaled to ThumbnailSize (200 pixels), while the other dimension is scaled proportionally.
Class ThumbnailsSequence
The class ThumbnailsSequence
is derived from a FlowLayout
panel and used as a container for the thumbnails generated for the selected directory on the disc.
Class ImageForm
ImageForm
is a Windows Form that is shown as a dialog, when the user clicks on a particular image thumbnail, as the current image, when keyboard-navigating on the images inside the directory, or after escaping the full screen mode. The form preserves the image size, enabling scrolling in case the image exceeds the screen size.
Class FullScreenImage
This class is a Windows Form shown as a dialog without a Form border, occupying the full screen size and having a black background. As such, it gives the optical illusion of being an unusual GUI artifact featuring a full screen mode. It also resizes the image, if it is larger than the full screen dimensions available, while maintaining the initial aspect ratio.
Class ImageFile
ImageFile
is a flyweight-pattern class, having the image thumbnail stored as its intrinsic state and the full-sized image as its extrinsic state, retrieved only on demand.
Class ImageFolder
This class extracts and manages the image files within a given folder (directory).
Class ImageResizer
This type features two operations: resizing an image extracted from a file (for the generation of thumbnails) and resizing an image taken from memory (for the full-screen mode).
Class GlobalData
This class contains references to the resource images LoadingImage
and InvalidImage
, as well as to their respective thumbnails.
Lessons Learned
Although this image viewer is a managed application, there is no disparity in browsing or viewing speed (besides specific optimizations) between this solution and native executable ones, such as IrfanView
, XnView
and AcdSee
. This is because the .NET System.Drawing
and System.Windows.Forms
classes are rather tight wrappers over the WinAPI
functionality.
The Image
class, inherited by the Bitmap
class, implements the IDisposable
interface, making the flyweight design pattern implementation straightforward, due to being able to manage the image memory footprint in a deterministic manner.
The improved presence of 64 bit operating systems makes managed programming runtimes truly shine. This is because the applications written in managed languages (such as .NET CLS compliant ones) inherently support a 32 to 64 bit switch using the same binary package, while offering the full advantages of the running platform.
Source Code and Application Download
The complete source code of the ImageFan
application (a Google Code project) can be accessed here. If one is only interested in the binaries, they can be downloaded from this link.
I would gladly welcome contributions and feedback to this ImageFan open-source (GPL v3) project.
References
- [1] The Microsoft Developer Network (MSDN) pages
History
- Version 0.1 - Initial submission - 24/02/2010
- Version 0.2 - Added download links at the head of the article - 28/02/2010
- Version 0.3 - Code optimizations and bug-fixes - 18/04/2010
- Version 0.4 - Updated content, sources and binaries - 21/09/2011
- Version 0.5 - Reengineered code with significant bug fixes - 09/04/2012
- Version 0.6 - Many code changes and enhancements. Removed some of the code samples from the article, as they have become bloated and, thus, distracting. Cleaned up the article text - 04/01/2013
- Version 0.7 - Some bug fixes. Made the thumbnail box variable-size to increase the number of thumbnails on the screen - 09/04/2013