Direct2D Bitmaps
Bitmap display is media compatible with dot matrix or laser printers.
– Bill Joy
A bitmap is a map from an arbitrary domain, for example
The more general term pixmap refers to a similar map from an arbitrary domain into a finite field
A bitmap can be imagined as a format to store data. It commonly refers to the similar concept of a spatially mapped array of pixels. Raster images in general may be referred to as bitmaps or pixmaps, whether synthetic or photographic.
The native Windows format to store bitmaps is the BMP format. Another interesting format, though not purely a bitmap, is the Portable Networks Graphics, or PNG format, which uses compressed formats internally.
In this tutorial, we will learn how to use bitmaps in Direct2D to load images into our games. As an example for bitmap file formats, we will have a closer look at the Windows native BMP format.
BMP File Format
The BMP file format, also known as device independent bitmap (DIB) file format, is a raster graphics image file format used to store bitmap digital images, independently of the display device (such as a graphics adapter), especially on Microsoft Windows and OS/2 operating systems. The BMP file format is capable of storing two-dimensional digital images both monochrome and in colour, in various colour depths, and optionally with data compression, alpha channels, and colour profiles.
In the Dark Ages of game programming, i.e. during the time of DirectX 8, working with BitMaps was quite tedious, as there are about a trillion different bitmap file formats. To load a bitmap, one has to study the bitmap file format and write some code to load the data from a bitmap file. Let us examine the Windows native BMP file format.
As you can see, a BMP file consists of seven parts or blocks, four of which are optional. We will cover each of those.
Bitmap file header
The bitmap file header is a block of bytes that is always at the start of a bitmap file and holds general information about the bitmap. You would read this block first to make sure the file is actually a valid BMP file.
WORD bfType
This specifies the file type and must be BM (0x4D42) for a valid .BMP file.
DWORD bfSize
This member stores the size in bytes of the bitmap file.
WORD bfReserved1
WORD bfReserved2
Those are reserved fields that must be zero.
DWORD bfOffBits
This specifies the offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the actual bitmap bits.
DIB header
Also known as the the bitmap information section, this block of bytes stores detailed information about the image, namely its dimensions and colour information:
BITMAPINFOHEADER bmiHeader
This structure contains detailed colour information.
RGBQUAD bmiColors[1]
This member holds the colour palette, if there actually is one. More precisely, it either contains an array of RGBQUADs whose elements made up the colour palette, or an array of 16-bit unsigned integers that specifies indexes into the currently realized logical palette. The number of entries in this array depends on the biBitCount and biClrUsed members of the BITMAPINFOHEADER.
Let us thus have a look at this info header:
DWORD biSize
This member specifies the number of bytes required by the structure.
LONG biWidth
This member specifies the width of the bitmap, in pixels.
If biCompression is BI_JPEG or BI_PNG, the biWidth member specifies the width of the decompressed JPEG or PNG file, respectively.
LONG biHeight
This member specifies the height of the bitmap, in pixels. We have already noticed that if biHeight is positive, the bitmap is bottom-up bitmap with the origin in the lower-left corner, else it is a top-down bitmap with the origin in the upper-left corner.
Furthermore, if this member is negative, biCompression must either be BI_RGB or BI_BITFIELDS, as top-down bitmaps cannot be compressed.
If biCompression is BI_JPEG or BI_PNG, the biHeight member specifies the height of the decompressed JPEG or PNG image file, respectively.
WORD biPlanes
This member specifies the number of colour planes and must be set to 1.
WORD biBitCount
This member specifies the number of bits per pixel.
The following pixel formats are possible (remember that bit maps are actually maps from
-
0 bits-per-pixel means that the number of bits-per-pixel is specified or implied by the JPEG or PNG format.
-
1 bit-per-pixel means that the bitmap is monochrome. The 1-bit-per-pixel format supports 2 distinct colours. Each bit is an index into a table of 2 colours. An unset bit will refer to the first colour table entry, and a set bit will refer to the second colour table entry.
-
4 bits-per-pixel means that the bitmap has a maximum of
distinct colours and stores 2 pixels per 1 byte. Each pixel value is a 4-bit index into a table of up to 16 colours. -
The 8-bits-per-pixel format supports
distinct colours and stores 1 pixel per 1 byte. Each byte is an index into a table of up to 256 colours. -
The 16-bits-per-pixel format supports
distinct colours and stores 1 pixel per 2-byte WORD. Each WORD can define the alpha, red, green, and blue samples of the pixel. -
The 24-bits-per-pixel format supports
distinct colours and stores 1 pixel value per 3 bytes. Each pixel value defines the red, green, and blue samples of the pixel. -
The 32-bits-per-pixel format supports
distinct colours and stores 1 pixel per 4-byte DWORD. Each DWORD can define the alpha, red, green and blue samples of the pixel.
DWORD biCompression
This member specifies the type of compression used. Check the MSDN for all possible values, but note that BI_RGB stands for an uncompressed format, and BI_JPG and BI_PNG indicate that the image is a JPEG or PNG respectively.
DWORD biSizeImage
This member specifies the size of the image in bytes. This may be set to zero for BI_RGB bitmaps.
If biCompression is BI_JPEG or BI_PNG, biSizeImage indicates the size of the JPEG or PNG image buffer, respectively.
LONG biXPelsPerMeter
LONG biYPelsPerMeter
These members specify the pixels per meter on the x-axis and y-axis respectively.
DWORD biClrUsed
This member specifies the number of colours used by the bitmap.
DWORD biClrImportant
This member specifies the important colours that are required to display the bitmap. If this value is zero, all colours are required.
Bitmap Data Area
The bits representing the bitmap pixels are packed in rows. The size of each row is rounded up to a multiple of 4 bytes by padding. For images with height
The total number of bytes necessary to store one row of pixels
The actual pixel array is a block of 32-bit DWORDs, that describes the image pixel by pixel. Usually pixels are stored upside-down with respect to normal image raster scan order, starting in the lower-left corner, going from left to right, and then row by row from the bottom to the top of the image, unless BITMAPCOREHEADER is used, uncompressed Windows bitmaps also can be stored from the top to bottom, resulting in negative image height (c.f. above).
Loading Bitmaps
As you can see, writing a method to load bitmaps isn’t too easy, there are so many details to be wary of. We have three possibilities now, we can write our own method, we can use an open-source method like the stb library, or use the Windows Imaging Component (WIC) that Microsoft provides.
Let us see what the WIC can do.
The Windows Imaging Component
The Windows Imaging Component (WIC) is a Component Object Model-based imaging codec framework for working with digital images and image metadata. It allows applications supporting the framework to automatically get support of installed codecs for graphics file formats.
WIC enables image processing operations on any image format through a single set of common APIs, without requiring prior knowledge of specific image formats. Which is super nice, as you have seen how messy those image formats can be. By default, WIC supports JPEG, TIFF, GIF, PNG, BMP, DDS and HD Photo encoders and decoders, and an .ICO decoder (see below for more information on encoders and decoders).
What’s new in Windows Imaging Content
The Windows Imaging Component has been updated with new releases of Windows. This section provides a brief introduction to these new features.
Windows 7
TIFF Codec
With Windows 7 came an update to the TIFF codec to support large TIFF files.
Progressive Decoding
Progressive decoding provides the ability to incrementally decode and render portions of an image before the entire image has finished downloading.
Extended Metadata Support
In Windows 7, the WIC provides extended support for JPEGs, PNGs, and GIFs.
Multithreading
Objects within a multithreaded apartment may be called concurrently by any number of threads within the same apartment, allowing for better performance on multicore systems. In addition, WIC codecs that live in an apartment can call other objects that live within the same apartment without the marshalling cost associated of calling between threads that live in different singlethread apartments.
In Windows 7, all in-box WIC codecs have been updated to support multithreaded apartments, including JPEG, TIFF, PNG, GIF, ICO, and BMP.
It is highly recommended to use codecs that support multithreaded apartments, as other codecs will cause significant performance degradation in multithreaded applications due to marshalling.
Metadata Working Group
There are currently various metadata storage formats that contain overlapping properties, without any clear industry standard or guidance on consistent methods for reading and writing these metadata formats. To help with this variety of formats and properties, the Metadata Working Group (MWG) was formed.
The aim of the MWG is to provide guidelines that ensure interoperability among a wide variety of platforms, applications, and devices. The guidelines established by the MWG apply to the XMP, Exif, and IPTC metadata fields, and to the JPEG, TIFF, and PSD image formats.
In Windows 7, the photo metadata handler and the metadata policy layer have been updated to read and write image metadata according to the guidelines established by the MWG.
Windows 8
Improved Direct2D Integration
A new factory, the IWICImagingFactory2 is capable of creating IWICImageEncoders.
Changes to the BMP codec alpha support
WIC in Windows 8 supports loading BITMAPV5HEADER image files as WICPixelFormat32bppBGRA-formatted images.
New Pixel Formats
WIC in Windows defines and supports various new pixel formats.
Windows 8.1
JPEG Y’CbCr
Starting in Windows 8.1, WIC provides support for decoding, transforming and encoding JPEG Y’CbCr image data in its native format, resulting in a significant decrease in the processing time and memory consumption for certain imaging operations when working with Y’CbCr encoded JPEGs.
Block Compressed Formats
WIC now supports DDS images encoded in the following formats: DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC2_UNORM, and DXGI_FORMAT_BC3_UNORM. DDS block compressed data can be accessed in a decoded form using standard WIC interfaces, or directly accessed using new DDS-specific interfaces.
Windows 10
Low-Level JPEG
Starting in Windows 10, version 1507, WIC provides access to low-level JPEG data structures, including Huffman and quantization tables.
JPEG Indexing
JPEG indexing is a technique that significantly improves the performance of randomly accessing small sub regions of a large JPEG image, at the cost of some additional memory usage. The ID2D1ImageSourceFromWic interface is designed to leverage JPEG indexing if it is turned on.
Initializing the Windows Imaging Component
First we have to initialize COM, as WIC relies on COM to create its objects. Each thread that needs COM, needs to initialize COM, and uninitialize it when it is done. We can do this in the constructor and destructor of the Direct2D class.
To initialize COM, we simply call the CoInitialize method in the constructor:
And to end COM, we simply call the CoUnitialize method in the desctructor:
Now with COM properly running, we can create the Windows Imaging Component Factory using the CoCreateInstance function, which creates a single uninitialized object of a specified class:
REFCLSID rclsid
The CLSID associated with the data and code that will be used to create the object. The ID for a new WIC Factory is CLSID_WICImagingFactory2.
LPUNKNOWN pUnkOuter
We can set this to NULL to indicate that the object is not being created as part of an aggregate.
DWORD dwClsContext
This member specifies the context in which the code that manages the newly created object will run. The values are taken from the enumeration CLSCTX. We will use CLSCTX_INPROC_SERVER, indicating that the code that creates and manages objects of this class is a DLL that runs in the same process as the caller of the function specifying the class context.
REFIID riid
This parameter is a reference to the identifier of the interface to be used to communicate with the object.
LPVOID *ppv
This is the address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer. Upon failure, *ppv contains NULL.
Here is the C++-code to create the Windows Imaging Component Factory:
We can now use the WICFactory to handle all our imaging needs!
Loading Images
We will learn how to load images from the hard disk by an example using a BMP file.
In WIC images are not loaded directly, instead the factory is used to create a decoder, which is capable of reading a certain image format first.
Let us create a decoder for the following BMP image of little Cosmo.
To create a bitmap decoder from a file on the hard drive, Microsoft offers the IWICImagingFactory::CreateDecoderFromFilename method:
Let us go over the parameters rapidly:
LPWSTR wzFilename
This parameter is a pointer to a null-terminated string that specifies the name of the image to load.
GUID *pguidVendor
This is the ID for the preferred decoder vendor. We will use NULL as we have no special wishes so far.
DWORD dwDesiredAccess
The access to the object, which can be read, write, or both.
WICDecodeOptions metadataOptions
This parameter specifies when to load the metadata, we will set this to WICDecodeMetadataCacheOnLoad
IWICBitmapDecoder **ppIDecoder
When the function returns, this pointer stores a pointer to the new bitmap decoder object.
This isn’t so bad, let us see how this looks in C++:
Some formats allow multiple frames in the same image, and thus we have to tell WIC which frame we want. Most of the time we will only have one frame, and thus we will request the 0-th frame using the GetFrame method:
UINT index
This specifies the particular frame to retrieve.
IWICBitmapFrameDecode **ppIBitmapFrame
A pointer that receives a pointer to the newly created bitmap with the retrieved frame.
This sounds easy, so let us try it out:
Format Conversion
So far, this was surprisingly, even if a bit cumbersome just to load one image. Nevertheless, we shall march on!
The next thing to do is to finally create the actual bitmap image. To do so, we have to create a converter, which is capable to convert images into whatever format Direct2D desires. To create the format converter, we use the CreateFormatConverter method:
The only parameter is a pointer that receives a pointer to a new IWICFormatConverter.
Before being able to finally create the desired Direct2D bitmap image, this newly created WIC image must be initialized. This is necessary because we have to tell the converter how the pixels of the image should appear on the render target. We would like to work with the 32-bits-per-pixel format, more specifically, Direct2D requires bitmap sources to be in the 32bppPBGRA format for rendering, but there are many variants of this format, and we have thus to make sure that we get the right data layout.
To initialize the WIC image, a call to the IWICFormatConverter::Initialize method is sufficient:
IWICBitmapSource *pISource
This parameter specifies the bitmap to convert. We can simply input our frame here.
REFWICPixelFormatGUID dstFormat
This specifies the GUID of the destination pixel format. We will use GUID_WICPixelFormat32bppPBGRA, according to our render target.
WICBitmapDitherType dither
This parameter specifies the dither type, basically an intentionally applied form of noise used to randomize quantization errors, to use when converting between image formats. We will set this to WICBitmapDitherTypeNone, which means that we want to use a solid colour algorithm without dither.
IWICPalette *pIPalette
The palette to use for format conversion. We can pass NULL here.
double alphaThresholdPercent
This specifies the alpha threshold to use for conversion. We will set this to 0.
WICBitmapPaletteType paletteTranslate
This specifies the palette translation type to use for conversion. We will set this to WICBitmapPaletteTypeCustom, indicating that a custom palette is provided.
Phew, this sounds complicated, and I must admit that it is a bit of an overkill, but actually using the method is rather easy:
Bitmap Creation
There is only one more step to complete before we can finally render our image. Obviously, we still have to create a render target-specific bitmap resource to ensure that the bitmap will physically reside in memory as close to the GPU as possible.
To create the bitmap, we use the ID2D1DeviceContext::CreateBitmapFromWicBitmap method:
IWICBitmapSource *wicBitmapSource
This parameter specifies the WIC bitmap source to copy from.
D2D1_BITMAP_PROPERTIES1 *bitmapProperties
This parameter is optional and we do not need it. When it is not specified, the created bitmap inherits the pixel format and alpha mode from wicBitmapSource.
ID2D1Bitmap1 **bitmap
When the function returns, this parameters holds the address of the newly created bitmap object.
This is easy to use:
We have to remember, though, that resources depending on the render target must be recreated when the render target changes, although Microsoft states the following:
DXGI surface render targets do not support the ID2D1RenderTarget::Resize method. To resize a DXGI surface render target, the application must release and re-create it.
This operation can potentially create performance issues. The render target might be the last active Direct2D resource that keeps a reference to the Direct3D device associated with the render target’s DXGI surface. If the application releases the render target and the Direct3D reference is destroyed, a new one must be recreated.
You can avoid this potentially expensive operation by keeping at least one Direct2D resource that was created by the render target while you re-create that render target. The following are some Direct2D resources that work for this approach:
- ID2D1Bitmap (which may be held indirectly by an ID2D1BitmapBrush)
- ID2D1Layer
- ID2D1Mesh
To accommodate this approach, the resize method should test to see whether the Direct3D device is available. If it is available, release and re-create the DXGI surface render targets, but keep all the resources that they created previously and reuse them. This works because, as described in the Resources Overview, resources created by two render targets are compatible when both render targets are associated with the same Direct3D device.
Now all that is left to do is to draw the bitmap. Cosmo will finally come to live! To draw Cosmo, we can use the ID2D1DeviceContext::DrawBitmap method:
ID2D1Bitmap *bitmap
This is the bitmap to draw.
D2D1_RECT_F *destinationRectangle
This specifies the destination rectangle. The default is the size of the bitmap and the location is the upper-left corner of the render target.
FLOAT opacity
This parameter sets the opacity of the bitmap.
D2D1_INTERPOLATION_MODE interpolationMode
This parameter specifies the interpolation mode to use.
D2D1_RECT_F sourceRectangle
This parameter specifies an optional source rectangle. The sourceRectangle parameter defines the sub-rectangle of the source bitmap (in DIPs). DrawBitmap will clip this rectangle to the size of the source bitmap, thus making it impossible to sample outside of the bitmap. If NULL is specified, then the source rectangle is taken to be the size of the source bitmap.
D2D1_MATRIX_4X4_F perspectiveTransform
This parameter specifies an optional perspective transformation. If a perspectiveTransform is specified, it is applied to the rectangle in addition to the transform set on the render target.
And here is the C++-code to finally render Cosmo:
Bitmap Brushes
Instead of saving the image into a Direct2D Bitmap, we can also create a brush with a bitmap image. The code to load the image from the hard drive does not change, but we now store the image in a Direct2D Bitmap Brush.
A bitmap brush is used to fill a geometry with a bitmap. Like all brushes, it defines an infinite plane of content. Because bitmaps are finite, the brush relies on an extend mode to determine how the plane is filled horizontally and vertically.
To create bitmap brushes, we use the ID2D1DeviceContext::CreateBitmapBrush method:
ID2D1Bitmap *bitmap
This is a pointer to the desired bitmap to use as a brush.
D2D1_BITMAP_BRUSH_PROPERTIES1 *bitmapBrushProperties
This structure specifies the properties of the bitmap:
D2D1_EXTEND_MODE extendModeX
This value specifies how the brush horizontally tiles those areas that extend past its bitmap.
D2D1_EXTEND_MODE extendModeY
A value that describes how the brush vertically tiles those areas that extend past its bitmap.
D2D1_INTERPOLATION_MODE interpolationMode
This member specifies how the bitmap is interpolated when it is scaled or rotated.
D2D1_BRUSH_PROPERTIES *brushProperties
We already know how to use this properties structure from previous tutorials. Simply put in the desired opacity and transformation matrix.
ID2D1BitmapBrush1 **bitmapBrush
After the function finishes, this holds a pointer to the newly created bitmap brush.
This function is really easy to use; the two property structures are optional.
Let us see the code in action. To see the power of the WIC, we will load a Tetronimo, saved as a PNG, and create a bitmap brush:
This is a lot of information to process, nonetheless, we now have a robust framework to load images, in various formats, and to render them to the screen.
References
- Microsoft Developer Network (MSDN)
- Wikipedia
Photos
- Donald Acker
- Mr. Tetronimo