FotoVision Desktop Application

.NET Compact Framework 1.0

Ralph Arvesen
Vertigo Software, Inc.

June 2004

Applies to:
   Windows Forms
   Microsoft® Visual Studio® .NET 2003

Download FotoVision.

Summary: The FotoVision sample allows you to easily share photos on the web and demonstrates a variety of .NET technologies including Windows Forms, ASP.NET, Web services, and the .NET Compact Framework. It consists of a desktop, web, and Pocket PC sample applications. This paper provides information so you can get the most from the FotoVision desktop application. The sample is written in VB.NET and the installation application installs pre-built binaries and source code for the desktop application. (16 printed pages)

FotoVision Overview
Desktop Application Overview
Album and Photo Storage
Displaying Photos
Setting Metadata for Multiple Photos
Manipulating Photos
Action List
Working with JPEG Images
Uploading Files to the Website
Other FotoVision Samples

FotoVision Overview

The figure below shows the main parts of the FotoVision application. There are two users: owner and viewers. The owner uses the desktop application to modify and manage photos on their local system. When the photos are ready to be published, the desktop application uses a Web service to synchronize files on the website; credentials are passed in a SOAP header to prevent unauthorized users from modifying photos on the site. Viewers view photos on the site through a web browser. The Pocket PC application uses another Web service to download and store photos on the device so they can be viewed offline.

Figure 1. Overview of the FotoVision application

Desktop Application Overview

The desktop application performs three main tasks: managing photos on the local system, manipulating photos, and synchronizing photos on the website. The application is shown below and contains three different panes: the left pane manages albums, the middle pane displays photos, and the right pane contains details and manipulation tools like contrast, brightness and crop. The panes are implemented in separate classes under the panes source folder and they all inherit from the base class BasePane.

Figure 2. The FotoVision desktop application

Album and Photo Storage

FotoVision uses the file system and XML files as a simple database. Each folder in the file system represents an album and each image file in a folder represents a photo. Optional album and photo metadata information (description for example) is stored in corresponding XML files.

A copy is made of any photo that is imported into the application so your original photos are never modified or deleted. Copies are made under the My Documents\FotoVision\My Albums folder.


Thumbnails are auto-generated by the FileManager class whenever new photos are detected or existing photos are modified. The class synchronizes thumbnails by removing dangling images (thumbnails whose parent photo has been deleted).

The PhotoListView class displays photo thumbnails in the middle pane. The .NET Framework does not support owner-draw list view controls so this class inherits from the ListView class and captures underlying messages to create an owner-draw list view control. It uses the helper class DropData to participate as a drag and drop target and source.

Figure 3. Custom owner-draw ListView control

Displaying Photos

The PhotoViewer class displays full size images as shown in the following figure. Images are resized to occupy the available drawing area.

Figure 4. Custom photo viewer control

Manipulating large images can be a time consuming operation. For better performance, the class contains a working image, which is a scaled down version of the full size photo. Actions are applied to the working image while viewing, and applied to the full size image when the photo is saved. There are several const values at the top of the class that define the quality and performance of the working image:

' these affect the quality of the working image and performance, they
' do not affect the quality of saved and published photos (affects 
' what you see on the screen but not what is saved to the file system)
Public Const WorkingInterpolationMode As _
  InterpolationMode = InterpolationMode.Bilinear
Public Const ViewingInterpolationMode As _
  InterpolationMode = InterpolationMode.Bilinear
Public Const WorkingScale As Single = 0.65F

Setting Metadata for Multiple Photos

One of the goals of the desktop application is to enter metadata information as quickly as possible. The DetailsPhoto class displays details for multiple photos in a scrollable list as shown in the following figure. This allows users to quickly update the metadata for multiple photos by tabbing through the fields in the list.

Figure 5. Custom control to quickly set photo details

The class is an owner-draw ListBox control. Each item in the list box contains the metadata for one photo. The list could contain hundreds of photos so you don't want to create three separate text controls for each item (the operating system could run out of handles). Instead, the class uses three text box controls and moves them around to the appropriate location at the appropriate time. That way, all of the items appear to have active text box controls but only three window handles are used. The inactive fields are custom drawn to look like text fields.

Manipulating Photos

Photos can be modified before they are uploaded to the website. The DetailsAction class contains sliders, updown controls and buttons to manipulate the photo.

Figure 6. Modifying a photo before publishing to the website

All of the image manipulation routines are encapsulated in the PhotoHelper class. The class makes heavy use of the framework ColorMatrix class, which uses a 5 x 5 matrix to manipulate images instead of looping through and modifying each pixel in the image. In addition, matrixes can be combined and applied to the image in one call, which provides better performance. The class supports the following operations:

  • Adjusting brightness, contrast, saturation, and gamma.
  • Converting colors to grayscale and sepia.
  • Rotating and flipping.
  • Resizing, cropping and creating thumbnails.

The following are examples of using the PhotoHelper class:

' increase the brightness of an image
Dim image As New Bitmap("sample.jpg")
PhotoHelper.AdjustBrightness(image, 50)

' convert an image to grayscale
Dim image As New Bitmap("sample.jpg")

' do both operations in one call
Dim image As New Bitmap("sample.jpg")
Dim m1 As Single()() = PhotoHelper.GetBrightnessMatrix(50)
Dim m2 As Single()() = PhotoHelper.GetGrayscaleMatrix()
PhotoHelper.AdjustUsingCustomMatrix(image, _
   PhotoHelper.CombineMatrix(m1, m2))

Action List

ActionItem objects are appended to a list (ActionList class) every time a photo is modified, for example, changing gamma by 30%, converting to grayscale or flipping the image horizontally. This allows actions to be undone (remove the last action in the list) and enables the actions to be applied to different images. As the figure below shows, the action list is applied to the working image when viewing the photo on the screen, and applied to the original image when saving, printing, and copying to the clipboard.

Figure 7. Applying actions to different images

The OptimizeActions class optimizes the actions that are applied to the image by combining as many actions as possible into a single action. For example, the color matrix actions brightness, contrast, saturation, grayscale, and sepia can all be combined into a single action. Another example is multiple rotates; performing three left rotates is the same as one right rotate. You can view the current action list by right clicking a photo, selecting Properties, and then the Actions tab.

Figure 8. Displaying the actions for a photo

Working with JPEG Images

The FotoVision code contains two helper classes that make it easier to work with JPEG images.


The .NET Framework Image class does not allow you to specify a quality when saving images; at least directly, there is not a quality argument in any of the Save methods. You can use the framework class EncoderParameter along with the Image class to create JPEG images of different quality. The EncoderParameter class allows you to specify a quality between 1 (low quality, high compression) and 100 (high quality, low compression). The Image class appears to use a quality of 75 by default. The JpegQuality class encapsulates this so you can make a call like:

' save a high quality jpeg image
JpegQuality.Save("sample.jpg", image, 90)

' save a low quality jpeg image
JpegQuality.Save("sample.jpg", image, 30)

Exchangeable Image File Format

Another area that the .NET Framework does not support directly is Exchangeable Image File Format (EXIF) data. This is additional metadata information embedded inside of image files; almost all digital cameras store EXIF information.

This information is lost when you use the framework Image class to copy an image; you have to manually copy the items from the PropertyItems collection before saving the image. The Exif class encapsulates this functionality so you can maintain the EXIF data in your photos. In addition, the class extracts common EXIF tags such as camera model, exposure time and user comments as shown in the figure below. You can visit the site to learn more about the EXIF format.

Figure 9. Displaying EXIF information stored in JPEG photos


There are three levels of feedback in the desktop application: blip, intermediate, and lengthy (terms I made up). The goal is to provide sufficient feedback to the user in the least distracting way possible. Proper feedback can make the perceived time appear less, or put another way, the user is not irritated as much if they have something interesting to watch on the screen.

Blip feedback

Blip-feedback occurs when an operation only takes a couple of seconds. The wait cursor is displayed and the application is blocked during the operation. The user might not be aware that the application was ever busy.

Intermediate feedback

Intermediate-feedback occurs when the operation is longer than a blip, but shorter than a minute. A wait icon and message is displayed in the left pane of the status bar and a progress bar is displayed in the right pane as shown below. The application is blocked until the operation completes.

Figure 10. Display progress bar for intermediate operations

The trick is to update the user interface without introducing reentrancy problems. The DoEvents method is often used for this situation, which is perfectly fine, but you should be aware that all messages in the message queue are processed (not just the paint messages). This means mouse clicks, menu selections, and other messages are all processed and dispatched to your code, which might not be expecting to be called at that time. The desktop application calls DoEvents so it can repaint and overrides the WndProc method so only paint messages are processed.

Lengthy feedback

The last level is lengthy-feedback that occurs when tasks take a long time to complete. There are two lengthy operations in the application: when photos are published (resized), and when the files are uploaded to the website.

A progress bar, along with additional information, is displayed during these operations as shown below. That way the user knows exactly what is going on and has an idea of how much time remains. The application is blocked when resizing photos but is not when uploading files to the website (the user is free to delete photos, modify photos, etc.).

Figure 11. Displaying additional feedback during lengthy operations

Uploading Files to the Website

All of the code for uploading is under the upload source folder. There are two parts to uploading photos to the website: publishing files and uploading files.

The Publish class prepares photos to be uploaded by making copies of the photos and saving them to a specific size and JPEG quality (specified in the SettingsForm class). Hash values are used to uniquely identify each file so only photos that have been modified are resized. The operation executes in a worker thread and raises events to update the user interface.

After the publishing operation is complete, the Upload class uses a Web service to synchronize files on the website with files on the local system. This includes albums, photos and metadata XML files. It downloads hash values from the website, which uniquely identify files on the site, so it can determine the minimum amount of data to upload. This operation also executes in a worker thread and raises events to update the user interface.

The UploadForm class displays progress information from the two worker threads and marshals the data by calling Control.Invoke before updating the user interface elements. A password is required when using the Web service. The password is stored in the application config file as an encrypted value. The Upload class reads the value from the config file (Settings class), decrypts the value using the Data Protection API (DataProtection class), hashes the password (Hash class), and stores the value in a custom SOAP header. The following figure shows the upload process:

Figure 12. Publishing and uploading files to the website


Photos are printed using the Windows XP Photo Printing Wizard as shown below. The wizard is implemented in the system file photowiz.dll but the custom interface is not exposed. Instead, the Windows Image Acquisition Library (WIA) from Microsoft is used that provides a COM wrapper for the printing wizard. The printing code is encapsulated in the Print class.

Figure 13. Using the XP Photo Printing Wizard


The following are various tidbits used throughout the desktop application that may be useful in your own projects.

Set compiler options

Turn on the project options Option Explicit and Option Strict; this enables compiler options that force well-defined code to be written.

Show all class members

Uncheck the Visual Studio option Text Editor / Basic / Hide advanced members; this allows all class members to be displayed with IntelliSense.

Use short-circuiting operators

Use the short-circuit (or lazy evaluation) operators AndAlso and OrElse to improve performance and make the code easier to read:

' using the OrElse operator, the second expression is not
' evaluated if the first expression is true
If (files Is Nothing) OrElse (files.Length = 0) Then
End If

Declare variables as part of the loop

The code is easier to read if you declare loop variables as part of the For and For Each loop as shown in the following snippet:

' declare photo as part of the For Each loop
For Each photo As Photo In list

Use your own code regions

Using regions in the source code helps organize the code and makes it easier to read. The Visual Basic language supports the #Region and #End Region directives:

' put all drawing methods in a collapsible block
#Region " drawing methods "
   ' methods
#End Region

Dispose bitmap objects

Call Dispose to release Bitmap objects, otherwise they remain locked by the framework and an exception is thrown if you try to delete or overwrite the bitmap file.

' rotate an image, call Dispose when done
Dim image As New Bitmap("sample.jpg")
image.Save("new sample.jpg", Imaging.ImageFormat.Jpeg)

Use ellipsis when displaying long strings

Use the StringFormat class to display an ellipsis in long strings. The ellipsis can be placed at the end of the string (EllipsisCharacter), at the end of the nearest word (EllipsisWord), or the center of a long path (EllipsisPath). The desktop application uses this throughout the code.

Figure 14. Longs strings are drawn with the ellipsis character

ControlPaint class

The ControlPaint class contains methods to paint common Windows elements and can be quite useful. The desktop application uses the DrawReversibleFrame and DrawFocusRectangle methods to draw a bounding rectangle around the crop area:

' draw border around the cropping area
ControlPaint.DrawFocusRectangle(g, _
   _cropHelper.SelectedArea, Color.White, Color.Black)

Gradient fill

The framework LinearGradientBrush class makes it easy to draw gradients with linear or custom blending. This is used by the PaneCaption class when drawing the pane captions:

Figure 15. Drawing gradient filled areas

Set additional Graphics properties if necessary

Set properties on the Graphics object when you need more control over the default drawing surface. For example, the desktop application sets the TextRenderingHint to AntiAlias and InterpolationMode to Bilinear (to quickly scale images) and HighQualityBicubic (for high quality scaled images).

' turn on text antialiasing
g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias

Use pre-defined GDI objects if possible

The system GDI objects SystemBrushes and SystemPens should be used when possible. This prevents unnecessary GDI resources from being allocated and uses the colors defined by the user. Use the SystemColors class if you only need the color of a particular display element. The Brushes and Pens classes provide GDI objects for all of the standard colors (from CornflowerBlue to PeachPuff):

' use system brush instead of creating own brush
g.DrawString(_exif.Make, Me.Font, _ 
  SystemBrushes.ControlText, bounds, _format)

Display system icons in dialogs

Use the SystemIcons class to draw system icons in your own dialogs. The desktop application uses this class to display information, question and exclamation icons as shown below:

' draw the question system icon
e.Graphics.DrawIcon(SystemIcons.Question, 0, 0)

Figure 16. Display different system icons in dialogs

Specify the minimum size for sizable windows

If a window is sizable, make sure to set the MinimumSize property to prevent the window from being adjusted to an unexpected size.

Create windows with the XP drop shadow style

You can set the CS_DROPSHADOW (&H20000) style to enable the drop shadow effect for a window. This is usually used for short-lived windows such as menus and tooltips. The desktop application sets this style for the PropertiesForm class as shown below:

' create window with the XP drop shadow
Protected Overrides ReadOnly Property CreateParams() As CreateParams
      Dim cp As CreateParams = MyBase.CreateParams
      cp.ClassStyle = cp.ClassStyle Or Win32.CS_DROPSHADOW
      Return cp
   End Get
End Property

Extract version and other metadata from the assembly

You can access metadata information for the assembly at runtime through the Assembly class by calling the Reflection.Assembly.GetExecutingAssembly method. The Application class provides common metadata information such as version, company name, etc.

' display name and version
label.Text = String.Format("{0} version {1}", _
  Application.ProductName, Application.ProductVersion)

Embed resources in the application

You can easily embed resources such as bitmaps, WAV files and XML files in your assemblies. First, the BuildAction of the file is marked as Embedded Resource, and then the GetManifestResourceStream method is called to access the resource at runtime.

Figure 17. Compile resources into the assembly

Make it easy to read and write application settings

Most applications need to save and restore settings; either in the registry or a config file. The desktop application uses the Settings class to reads and write settings to an application XML config file. Setting names and default values are specified in the SettingValues source file.

' use Setting class to save values to app config file
Global.Settings.SetValue( _
   SettingKey.LastAlbum, paneAlbums.SelectedAlbum) 

Integrate custom controls into the property browser

Visual Studio automatically displays public properties in the property browser window. You can customize the appearance of your control by specifying class, property, and event attributes. The following snippet shows how the PaneCaption class uses attributes to control the appearance of the AntiAlias property.

<Description("If should draw the text as antialiased."), _
Category("Appearance"), DefaultValue(True)> _
Public Property AntiAlias() As Boolean
    Return _antiAlias
  End Get
  Set(ByVal value As Boolean)
    _antiAlias = value
  End Set
End Property

Figure 18. Integrate custom controls into the property browser

Analyze assemblies with FxCop

FxCop is a tool that analyzes assemblies for conformance to the Microsoft .NET Framework design guidelines. It was intended to control developers but it is useful to run against any .NET assembly. More information can be found at

Sign assemblies with a strong name

Assemblies are signed with a strong name when they are installed in the Global Assembly Cache (GAC). Signing a stand-alone executable can also be useful since the framework will report an error if malicious code tampers with the assembly. For example, the following figure shows the message that is displayed when the desktop application is modified with a hex editor (simulating a virus injecting itself into the executable):

Figure 19. Running a signed assembly that has been modified

Other FotoVision Samples

The desktop application is one of three parts of the FotoVision sample application. You can also install the web and Pocket PC applications.