Thursday, July 30, 2009

Preloading and Caching Images in Silverlight

One thing I found with our image-intensive Silverlight application was that the screens sometimes painted a little slowly due to our decision to store images externally. Instead of having a bloated XAP file, we decided to map a path to the server with the images. Because the application is enterprise and therefore behind SSL, this can sometimes result in a noticeable load time for the images.

After doing some research, I found that Silverlight actually uses the browser's own image caching mechanisms. In other words, when you point to an external image on the web, the browser will cache it just like an ordinary image stored in a web page. The first time, it will pull down, but after that it should be fast.

Knowing this, it was simple to make a little image helper to help me preload images so they are ready before the user is. The class looks like this:

public static class ImageHelper
{
   public static void PreLoad(string imagePath)
   {
      const string JS = "var img = new Image(); img.src='{0}';";
      HtmlPage.Window.Eval(string.Format(JS, AppUriHelper.GetRelativeUri(imagePath)));
   }
}

That's it ... in my App.cs I can now preload images easily, by doing this:

...
ImageHelper.PreLoad("Images/mycachedimage.jpg");
...

The application hooks into the JavaScript of the DOM for the browser page, then creates an image object that triggers a load into your browser of the image, even if you haven't shown it within the Silverlight application yet.

Oh, what is AppUriHelper? It's just a simple utility to help me build uris that follow the application wherever it is installed:

public static class AppUriHelper
{        
   private static readonly Uri _baseUri;
   private static readonly string _uriRoot;

   static AppUriHelper()
   {
      _uriRoot = System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri;
      int lastSlash = _uriRoot.LastIndexOf("/");

      // we do this twice to get the "second to last" slash - leave this out if
      // your app is hosted in the root, this assumes /subdir/mypage.aspx
      _uriRoot = _uriRoot.Substring(0, lastSlash);
      lastSlash = _uriRoot.LastIndexOf("/");
      _uriRoot = _uriRoot.Substring(0, lastSlash + 1);

      _baseUri = new Uri(_uriRoot, UriKind.Absolute);
   }

   public static Uri GetBaseUri()
   {
      return _baseUri; 
   }

   public static Uri GetRelativeUri(string relativePath)
   {
      string newUri = string.Format("{0}{1}", _uriRoot, relativePath);
      return new Uri(newUri, UriKind.Absolute);
   }
}

Of course, GetRelativeUri is probably not named well because it returns an absolute Uri ... it really is "Get Absolute Uri from Relative Path".

Jeremy Likness

6 comments:

  1. Hi there,

    I have a silverlight application in vs2010 and iam using silverlight 4.0. I have to show a videoppt in which a video is synchronised with images and it runs as a video powerpoint presentation. Is it possible to preload the images or cache as u have discussed, in my case. If there is a way out, plz guide me.

    ReplyDelete
  2. Does this also work in OOB mode?

    ReplyDelete
  3. It should work the same way if you are OOB but you would need to extend the method to compute the Uri by saving the original source Uri (where the app was originally pulled from) to make an absolute Uri back to the web.

    ReplyDelete
  4. When you do "ImageHelper.PreLoad("Images/mycachedimage.jpg");"

    How would you ImageViewer load this preloaded image from the cache?

    Surely you would not bind your controls image source in the `JS`, or would you?

    ReplyDelete
  5. No, we are using JavaScript to pull the image into the browser cache. When the Silverlight request for the image is served, the image is already in cache and is therefore delivered instead of downloaded.

    ReplyDelete
  6. Something I found: you can use the CreateOptions of the BitmapImage to indicate the image should loaded immediately without delay. Loading with delay is default. So basically you could load all the images at startup.


    BitmapImage img = new BitmapImage();
    img.CreateOptions = BitmapCreateOptions.None;
    img.UriSource = source;
    ImageBrush brush = new ImageBrush();
    brush.ImageSource = img;

    cheers,
    Herre

    ReplyDelete