Image codecs in XNA Game Studio 4.0

Originally posted to Shawn Hargreaves Blog on MSDN, Monday, May 10, 2010

Quick history lesson:

Once upon a time, we were designing XNA 1.0, and decided that instead of just exposing managed wrappers around the old D3DX functions for loading textures and models, it would be better to create a new thing called the Content Pipeline.

But when we came to release our 1.0 Beta, the Content Pipeline was not quite finished. Hmm. Doesn't seem like people are going to have much fun with a supposedly easy to use graphics API that has no built-in way to load textures! We needed a quick fix, so we slapped a hasty wrapper around the D3DX texture loader, and thus Texture2D.FromFile was born.

FromFile is only supported on Windows, because D3DX was not available to us on Xbox. We considered doing the extra work to port this to Xbox, but it seemed like a waste of effort to duplicate functionality that is also available through the Content Pipeline. We also considered removing FromFile from our final Windows version, but that seemed like an unnecessary breakage for our beta customers.

Game Studio 4.0 takes cross platform consistency more seriously, so this only-on-Windows situation was no longer acceptable. Plus, Windows Phone adds scenarios involving web services and picture library integration, where runtime image loading and saving are important. But the D3DX image code is large, full of obscure features, and would be expensive to port to all our target platforms.

We decided to replace our D3DX image loader with a simpler new API. The new methods are:

The new load method has two overloads:

    static Texture2D FromStream(GraphicsDevice GraphicsDevice, Stream stream);
    static Texture2D FromStream(GraphicsDevice GraphicsDevice, Stream stream, int width, int height, bool crop);

Because this reads from a Stream rather than a filename, it can be used with many data sources: FileStream, MemoryStream, NetworkStream, TitleContainer.OpenStream, etc. To read from a file on disk, use it like so:

    using (Stream stream = File.OpenRead("cat.png"))
    {
        texture = Texture2D.FromStream(graphicsDevice, stream);
    }

There are three save methods:

    void SaveAsPng(Stream stream, int width, int height);
    void SaveAsGif(Stream stream, int width, int height);
    void SaveAsJpeg(Stream stream, int width, int height);

Which are typically used like so:

    using (Stream stream = File.OpenWrite("cat.png"))
    {
        texture.SaveAsPng(stream, texture.Width, texture.Height);
    }

The first FromStream overload does minimal scaling. As long as the source fits within the maximum GPU texture size (2048 for Reach, or 4096 for HiDef) it will be returned exactly as-is. For instance this photo of my dog Kess is loaded into a 192x288 SurfaceFormat.Color texture:

source

If the source is larger than the supported maximum, it will be scaled down as little as possible to make it fit, while preserving aspect ratio (so width and height are always shrunk the same amount).

The second FromStream overload offers more control over how the image is scaled, which is useful for photo viewer applications, thumbnail displays, etc. When crop is set to false, the image is scaled to fit entirely within the specified size, while preserving aspect ratio. For instance if I load the above photo with width=256, height=256, crop=false, I get:

256x256-False

The height has been scaled to 256, while the width has been scaled down to 170 (to preserve aspect). If my source image was wider than it was tall, the width would come back exactly 256, while the height would be smaller.

If I load the same photo with width=256, height=256, crop=true, I get:

256x256-True

The image has been scaled to entirely fill the specified size, and cropped as necessary to preserve aspect ratio, so the leaves from the top of the original photo are no longer visible.

Another way to think of this:

Various places where we used to expose a Texture2D property now return a Stream instead. For instance, the Picture.GetTexture method (which returned a Texture2D) is now Picture.GetImage (which returns a Stream). Likewise for album art and gamer profile pictures. The returned stream contains a JPEG encoded image.

This change was neccessary to make these APIs compatible with Silverlight. By decoupling image sources from the XNA Texture2D class, it is now possible to use our media API both from an XNA game (which can use FromStream to convert the stream into an XNA Texture2D) and also from Silverlight (which can load that same stream into a Silverlight BitmapImage).

Blog index   -   Back to my homepage