Imaging more efficiently

When an image is displayed in a WPF application, it is loaded and decoded in its full size by default. If your application displays a number of thumbnails from the original images, then you can gain enhanced performance by copying your full-size images and then resizing them to the correct size for the thumbnails, rather than letting WPF do it for you.

Alternatively, you can request that WPF decodes your images to the size required by the thumbnails, although, if you want to display the full-size images, you would really need to decode each full-size image separately. Let's take a look at how we can achieve this by using a BitmapImage object as the source for an Image control:

<Image Width="64"> 
  <Image.Source> 
    <BitmapImage DecodePixelWidth="64" UriSource="pack://application:,,,/
      CompanyName.ApplicationName;component/Images/Image1.png" /> 
  </Image.Source> 
</Image> 

The important part of this example is the DecodePixelWidth property of the BitmapImage class, which specifies the actual size of the image to decode to. In this example, this would result in a smaller memory footprint as well as faster rendering.

Note that if the DecodePixelHeight and DecodePixelWidth properties of the BitmapImage class are both set, a new aspect ratio will be calculated from their values. However, if only one of these properties is set, then the image's original aspect ratio will be used. It is, therefore, customary to only set one of these properties in order to decode to a different size from the original, while maintaining its aspect ratio.

Normally, when images are used in a WPF application, they are all cached into memory at load time. Another benefit that can be gained if using code in the aforementioned scenario is to set the CacheOption property of the BitmapImage class to the OnDemand enumeration member, which postpones the caching of the relevant image until the image is actually requested to be displayed.

This can save a significant amount of resources at load time, although each image will take a tiny bit longer to display the first time they are displayed. Once the image is cached, however, it will work in exactly the same way as the images created in the default way.

There is one additional property in the BitmapImage class that can be used to improve the performance when loading multiple image files. The CreateOptions property is of the BitmapCreateOptions enumeration type and enables us to specify initialization options that relate to the loading of images. This enumeration can be set using bitwise combinations as it specifies the FlagsAttribute attribute in its declaration.

The DelayCreation member can be used to delay the initialization of each image until it is actually required, thereby speeding up the process of loading the relevant View, while adding a tiny cost to the process of requesting each image when it is actually required.

This would benefit a photo gallery type of application, for example, where the initialization of each full-size image could be delayed until the user clicks on the appropriate thumbnail. It is only at that point that the image would be created, but as there would only be a single image to create at that point, the initialization time would be negligible.

While it is possible to set more than one of these members to the CreateOptions property using the bitwise OR operator (|), care should be taken to not also set the PreservePixelFormat member, unless specifically required, as that can result in lower performance. When it is not set, the system will choose the pixel format with the best performance by default. Let's look at a short example:

private Image CreateImageEfficiently(string filePath) 
{ 
  Image image = new Image(); 
  BitmapImage bitmapImage = new BitmapImage(); 
  bitmapImage.BeginInit(); 
  bitmapImage.CacheOption = BitmapCacheOption.OnDemand; 
  bitmapImage.CreateOptions = BitmapCreateOptions.DelayCreation; 
  bitmapImage.UriSource = new Uri(filePath, UriKind.Absolute);
bitmapImage.Freeze();
bitmapImage.EndInit();
image.Source = bitmapImage;
return image;
}

When creating images in code, we need to initialize an instance of the BitmapImage class to use as the source for the actual Image object that will be displayed in the UI. When doing so, we need to call its BeginInit method before making changes to it and then call its EndInit method afterward. Note that all changes made after initialization will be ignored.

During initialization, we set the CacheOption property to the OnDemand member and the CreateOptions property to the DelayCreation member. Note that we do not set the DecodePixelWidth or DecodePixelHeight properties here, because this code example is setup for initializing the full-size images in our gallery example.

Additionally, note that, in this particular example, we initialize the Uri object using an absolute file path, by passing the Absolute member of the UriKind enumeration into the constructor. If you prefer to work with relative file paths, you can change this line to specify a relative file path by passing the Relative member to the constructor instead:

bitmapImage.UriSource = new Uri(filePath, UriKind.Relative); 

Returning to the end of the example now, we can see the call to the Freeze method, which ensures that the BitmapImage object will be unmodifiable and in its most efficient state. This line can be omitted if the images need to be modified later.

Finally, we call the EndInit method to signal the end of the BitmapImage object initialization, set the BitmapImage object as the Source property value of the Image object to return, and then return the Image object to the method caller.

Now that we've seen some tips on how to display our images more efficiently, let's investigate how we might do the same for our application's textual output.