Dela via


Embedding Resources within an Avalon Application

I've been writing a game recently using Avalon, as part of which I wanted to load and display a number of sprites and other graphical objects. There were two specific problems I wanted to resolve: what was the best way to cache the sprite images for efficient reuse, and how should I store and load the images themselves? In both cases, WinFX provided quite a neat solution that I wanted to share here.

To start with the problem of storing and loading images, the most straightforward approach would have been to simply load images from a bitmap file. Of course, then you have to deploy a series of image files with your application, which can become painful. Instead, Visual Studio allows you to embed resources directly into the application assembly, allowing you to distribute the executable code and any dependent resources as a single file. To achieve this, you simply have to add the bitmap images into your project and mark them as "Embedded Resources" within the content property (use F4 to bring up the properties window if it's not currently active). The resources then get compiled into the application itself, which you can see if you inspect the assembly with ILDASM. How do you access the bitmap image itself? It's a little opaque, but you use a command such as the following:

  stream = this.GetType().Assembly.GetManifestResourceStream(file);

where file is the full filename that was embedded (e.g. "background.bmp"). You can now create an instance of BitmapImage based on this stream and use it for whatever you want: for example, you could create an ImageBrush and use it to paint the background of an element.

The second half of the problem reflects that the game I'm working on has an avatar moving around a grid of tiles. On each frame, many of the tiles need repainting, which involves loading the images again. Some kind of image cache is obviously the solution to ensuring that this gets handled with reasonable performance. Fortunately the BitmapImage class has a constructor parameter that allows exactly that:

  public BitmapImage( Stream bitmapStream, 
                    BitmapCreateOptions createOptions, 
                    BitmapCacheOption cacheOption );

where BitmapCacheOption can be one of OnLoad, OnDemand or None. OnLoad is actually the default, so you get this caching goodness for free unless you explicitly unset it.

So here's the working code snippet to grab an image from an assembly resource (corrected unreachable code based on comments below):

   // using System.Windows.Media;
stream = this.GetType().Assembly.GetManifestResourceStream("background.bmp");
if (stream != null)
{
// following line throws NotSupportedException if bitmap resource
// is corrupt or wrong type
BitmapImage bitmapImage = new System.Windows.Media.BitmapImage(
stream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.OnLoad);

ImageBrush ib = new ImageBrush(bitmapImage);

ib.TileMode = TileMode.Tile;
ib.Stretch = Stretch.None; // required for TileMode to take effect

MyWindow.Background = ib;
stream.Close();
}
else
{
throw new NullReferenceException("Stream is null - resource missing.");
}

Incidentally, the embedded resources capability has no dependency on Avalon - you can do it just as well from a WinForms solution. Hope that helps someone. More tomorrow...

Comments

  • Anonymous
    January 12, 2005
    A constructor can never return null, so the first 'else' will never be called.

    I haven't the docs on BitmapImage, but I assume that it throws an exception if the file is not a bitmap.
  • Anonymous
    January 12, 2005
    There is wrong place in your code. You check object for null after construction, whereas after construction object became initialized reference and there is no chance to be it null.

    So all your 'if' need to be dropped out and 'else/throw' part too.
  • Anonymous
    January 13, 2005
    Thanks guys - momentary brain-stumble there. Corrected code now posted.
  • Anonymous
    January 14, 2005
    I am unable to locate:

    System.Windows.Media.BitmapImage

    In the CTP SDK documentation. Where should I be looking for this?
  • Anonymous
    January 14, 2005
    That is the class formerly known as ImageData. Sorry - that's a change post-Nov CTP.
  • Anonymous
    May 30, 2005
    Two other interesting posts about Avalon:

    A guided tour
    Embedding/Reading resources
  • Anonymous
    May 30, 2005
    Just out of interest: which game is it that you're talking about? I'm planning/hoping to write a couple of programs about Hero Quest, which is basically a board game where figures move over squares ... sounds familiar, doesn't it?
  • Anonymous
    December 19, 2005
    I've been having the darndest time getting resource images into my code. Working with them in xaml works fine. But I can't seem to find a way to get the stream into something that can convert to a BitmapImage (ultimately to be placed into ImageSource).
    Any insights into the changes since you wrote this sample?
  • Anonymous
    December 20, 2005
    FYI, I figured out how to get the streamed image from resource into my XAML Image element:
    Assembly asm = this.GetType().Assembly;
    Stream strm = asm.GetManifestResourceStream( "picture.png" );
    if( strm != null )
    {
    myXAMLImageElement.Source = BitmapFrame.Create( strm );
    }
  • Anonymous
    March 09, 2006
    I have trying to get a BitmapImage or BitmapFrame I dont care which, with a web resource.  I can not work out a way to handle any underlying web exceptions when the BitmapImage is rendered.

    eg.

    BitmapImage bmp = new BitmapImage();
    bmp.BeginInit();
    bmp.UriSource = new Uri(url);
    bmp.EndInit();

    So when the bitmap is rendered there is no way to catch the exception.  A try catch block around the above code doesnt do a thing.  OK....so i thought I would build my own WebRequest and pass the response stream to the BitmapImage like this:

    bmp.BeginInit();
    bmp.StreamSource = response.GetResponseStream();
    bmp.EndInit();
    response.Close();

    But this doesnt work either because I then get a underlying connection closed exception because I called response.Close();  If I remove the response.Close(); I can catch the WebExceptions from the stream, but is the caller not response for closing the stream?

    I would love to be able to use the example in this post, but the constructor for passing a stream to BitmapImage no longer exists.  And if I use BitmapFrame i have the same problem when closing the stream.

    Am I missing something here?

    Cheers