RSS
 

Posts Tagged ‘Image’

Viewing GIF’s in Windows Phone 7

04 May

To display an image in Windows Phone 7 we are given one control to handle this, called ‘Image’.  It is very simple to use, just simply define it and load up the object with either a URI of the data source or load it with some local image data.  One glaring issue with the Image object is its not capable of displaying GIF images.

APODViewer7 had to display GIF images.  APOD’s archive was loaded with them especially from its first few years.  After those first few years GIF’s continued to be used for its animated capability.  I got it working by using the WebBrowser object.  It worked surprisingly well, it even gave me pinch to zoom and manipulation for free!  However, it required some clunkly code to make it somewhat seamless to the user.

Before I released my 2.0 version of APODViewer7 Diane Leeper did a quick run through and critique of the user experience of my development version of the application.  She gave me some great some tips on how to improve the app.  Notably, the waiting thumbnail animations and how I handle image preloading in the background. Thanks again Diane!

Diane noted other developers were struggling with this issue and that I should blog about it to inform others on how I got GIF viewing working.  That said, on to the details on how I did it.

Below are the two objects defined in XAML, an Image and WebBrowser object.  We will alternate the visibility of these objects depending on what type of image we want to load.

  1.  <Image x:Name="FullImage"/>
  2.  
  3.  <phone:WebBrowser x:Name="wbViewer" Width="480" Height="696" Visibility="Collapsed" />

Three application bar buttons are defined to execute the three image loads.  Initially when the application executes nothing is rendered, pressing the application bar images loads its associated data. Pressing button 1 will render an animated GIF in the WebBrowser.  Button 2 is a JPG also loaded by the WebBrowser.  Button 3 is another JPG loaded into an Image object.  Give each call a few seconds to load, it has to download the complete image data off of the NASA web site.

When loading the GIF in the WebBrowser, we need to hide the Image object.  Then we need to create an HTML string to properly display the image.  Here is the method that formats the HTML string and loads the WebBrowser object:

  1. private void ViewImageInWebBrowser(string ImageURL)
  2. {
  3.     FullImage.Visibility = System.Windows.Visibility.Collapsed;
  4.  
  5.     wbViewer.IsEnabled = true;
  6.     wbViewer.Visibility = System.Windows.Visibility.Visible;
  7.     string backgroundColor;
  8.     
  9.     if (IsThemeDark() == true)
  10.     {
  11.         backgroundColor = "<body bgcolor=\"#000000\">";
  12.     }
  13.     else
  14.     {
  15.         backgroundColor = "<body bgcolor=\"#FFFFFF\">";
  16.     }
  17.  
  18.     string imageHTML = "<html><head><meta name=\"viewport\" "+
  19.         "content=\"width=480\" id=\"viewport\" />" +
  20.         "</head>"+ backgroundColor + "<IMG SRC=\"" +
  21.         ImageURL + "\"></body></html>";
  22.  
  23.     wbViewer.NavigateToString(imageHTML);
  24. }

Notice it initially hides the Image object and then makes the WebBrowser object visible.  The two objects are layered on top of each other, making the appropriate object visible and hiding the other. The HTML string uses a meta tag to prevent the image from being scaled incorrectly when initially rendered.  Note the IsThemeDark call.  This is used to determine how to set the WebBrowser background color based on what the user has the theme set to, light or dark. This is defined as:

  1. public static bool IsThemeDark()
  2. {
  3.     bool ThemeIsDark = false;
  4.  
  5.     Color themeColor = (Color)Application.Current.Resources["PhoneForegroundColor"];
  6.  
  7.     if (themeColor.ToString() == "#FFFFFFFF")
  8.     {
  9.         ThemeIsDark = true;
  10.     }
  11.     else if (themeColor.ToString() == "#DE000000")
  12.     {
  13.         ThemeIsDark = false;
  14.     }
  15.  
  16.     return ThemeIsDark;
  17. }

JPG in the WebBrowser is no different then GIF’s, just a different URL loaded into the ViewImageInWebBrowser method.  Loading a JPG in an Image object is much simpler.  First we hide the WebBrowser, then create a URI of the image location and load the Image object with it.

  1. private void JPG_Image_Click(object sender, EventArgs e)
  2. {
  3.     wbViewer.IsEnabled = false;
  4.     wbViewer.Visibility = System.Windows.Visibility.Collapsed;
  5.  
  6.     FullImage.Visibility = System.Windows.Visibility.Visible;
  7.  
  8.     Uri u = new Uri("http://apod.nasa.gov/apod/image/0605/redspot2_hst_c68.jpg", UriKind.Absolute);
  9.     FullImage.Source = new BitmapImage(u);
  10. }

Some negative aspects of using WebBrowser for images is we cant directly deal with the loaded image data.   With the image object we can load image data from memory locally to the object, give it a URI to load, or extract its currently loaded data to manipulate it.  WebBrowser only allows us to load a specific HTML string using the NavigateToString method a URL using the Navigate method with no means to touch its loaded data.

There you have it, a way to display GIF’s in a WP7 app.  I admit this is rather crude, but it works and its done completely with out any external dependencies.  I have seen other solutions such as Imagetools for Silverlight that have specific calls to handle GIF’s, but I discovered this project after I implemented the above.  I’ll play around with the ImageTools to see if its worth changing APODViewer7 and blog about the differences.

If interested in the code, click here to download the complete VS2010 solution for this example.

 
4 Comments

Posted in WP7

 

Windows Phone 7 Image caching on Tombstoning

07 Nov

A recent Windows Phone 7 project had me wanting to cache a Silverlight Image object data on tombstoning.  The reason for this is when the user returned to the page there would be a delay before the image was shown because it was out pulling the image from data connection sources.  I wanted the images to show immediately when returned.  Upon looking for solutions for this issue I was surprised at what little, if any, information I could find directly related to it.  I present a solution to loading and caching this data in Windows Phone 7.

For the Image object I define it in XAML simply as:

<Image x:Name="CachedImage"
       ImageOpened="CachedImage_ImageOpened"/>

 

Note there is no source defined for this image object.  Without any code behind it will load nothing.  Initially I setup the MainPage constructor to load up the image directly from a URI as show here:

public MainPage()
{
    InitializeComponent();

    Uri u =
      new Uri("http://antwrp.gsfc.nasa.gov/apod/image/0806/saturn6_cassini_big.jpg",
              UriKind.Absolute);
    CachedImage.Source = new BitmapImage(u);
}

 

This will now allow the Image object to download and show the picture.  The problem with this is each and every time we go to this page it has to redownload the image data.  The goal is to now cache this data so if the user locks the screen or hits the search button then back again, that it doesn’t have to download the entire image off of the data connection.  Upon completion the ImageOpened event is fired, this is were we want to cache the data into the state.

private void CachedImage_ImageOpened(object sender, RoutedEventArgs e)
{
    Image i = (Image)sender;

    WriteableBitmap wbm = new WriteableBitmap(i, null);

    IDictionary<string, object> state = PhoneApplicationService.Current.State;
    state["ImageCache"] = _GetSaveBuffer(wbm);
}

 

The _GetSaveBuffer is a helper method that changes a WriteableBitmap to a Byte array, it is taken from Jeremy Likness’s blog on saving bitmaps to isolated storage in Silverlight 3.  So now this image is in the transient application state.  We now need to determine on load of the page to either load from cache or load from the URI.  Our page constructor now looks like this:

public MainPage()
{
    InitializeComponent();

    bool DownloadImage = true;

    try
    {
        IDictionary<string, object> state = PhoneApplicationService.Current.State;

        byte[] LastCachedImage = (byte[])state["ImageCache"];

        if (LastCachedImage != null)
        {
            CachedImage.Source = _GetImage(LastCachedImage);
            DownloadImage = false;
        }
    }
    catch { }

    if(DownloadImage == true)
    {
        //Failed to pull image out of state, set to direct URL
        Uri u = new Uri(
            "http://antwrp.gsfc.nasa.gov/apod/image/0806/saturn6_cassini_big.jpg",
            UriKind.Absolute);
        CachedImage.Source = new BitmapImage(u);
    }
}

 

The method _GetImage is the counter to _GetSaveBuffer, it changes a Byte array into a WriteableBitmap.  Now when the page loads it attempts to load the image from the transient application state.  Failing to do that it loads it directly from the URI.  Now when the user goes back to the application on restoring the image is instantly displayed, no downloading is needed.  Because we are storing in transient application state, the cached image is only temporary.  Restarting the application, that is loading from directly from its icon, will start a new session and the transient application state will be erased. 

Other things that could be done with this is to store into persistent data.  This also works when the image object is bound to a datacontext.  We simply override the binding and use the cached data in the same way.

Grab the sample project with this code here.

 
5 Comments

Posted in WP7