CHAPTER 4
The first graph objects we define will be backgrounds. The background of a chart acts as the canvas upon which the other objects are rendered. It can be a simple single color, a gradient, or even an image. Charts are usually meant to clearly portray information, and the background should not obscure the data.
The simplest chart background is a single solid color, usually white or some other unsaturated pigment. These are common because they do not tend to draw the attention of the viewer away from the data being represented, and they are quick and easy to render.
Note: We could change the color in the call to Clear from CornflowerBlue to something else. Instead, we will encapsulate the rendering of the background in a separate class. Once our chart is clearing the screen, you can remove the clear to CornflowerBlue.
The following code defines a class that renders a solid color background.
// SolidBackground.h #pragma once #include "DirectXBase.h" // Defines a background consisting of a solid color class SolidBackground { private: D2D1::ColorF color; // The color of this background public: // Creates a new SolidBackground set to the specified color SolidBackground(D2D1::ColorF color); // Draw the background void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); }; |
// SolidBackground.cpp #include "pch.h" #include "SolidBackground.h" SolidBackground::SolidBackground(D2D1::ColorF col): color(col) { } void SolidBackground::Render( Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { context->Clear(color); // Clear the screen to the color } |
This class takes a color parameter in its constructor, saves it to a member variable, and uses this to clear the screen in its render method. To create an instance of our solid background, we need to add it to the GraphRenderer class. Open GraphRenderer.h and add an #include for the SolidBackground.h file (I have highlighted the lines which have been added or changed in blue).
// GraphRenderer.h #pragma once #include "DirectXBase.h" // // Additional headers for graph objects here // #include "SolidBackground.h" |
Declare a member variable at the bottom of the GraphRenderer.h file.
private: // Global pan value for moving the chart with the mouse Windows::Foundation::Point m_pan; SolidBackground* m_solidBackground; }; |
Open the GraphRenderer.cpp file and use “new” to create the instance of m_solidBackground in the GraphRenderer class constructor.
GraphRenderer::GraphRenderer() { m_solidBackground = new SolidBackground(D2D1::ColorF::Bisque); } |
And finally, the GraphRenderer’s render method must be changed to call the new m_solidBackground’s render method. The call to the m_d2dContext’s Clear method is no longer needed and can be removed.
void GraphRenderer::Render(){ m_d2dContext->BeginDraw(); // Clear to some color other than blank // m_d2dContext->Clear(D2D1::ColorF(ColorF::CornflowerBlue)); // Pan the chart Matrix3x2F panMatrix = Matrix3x2F::Translation(m_pan.X, m_pan.Y); m_d2dContext->SetTransform(panMatrix*m_orientationTransform2D); // // Draw objects here // m_solidBackground->Render(m_d2dContext); // Ignore D2DERR_RECREATE_TARGET error HRESULT hr = m_d2dContext->EndDraw(); if (hr != D2DERR_RECREATE_TARGET) DX::ThrowIfFailed(hr); } |
You can remove the call to m_d2dContext::Clear() in the render method, since it is no longer needed. Now is a good time to compile and run your application.
Colors can be specified using the D2D1::ColorF enumeration, which defines a list of around 140 predefined colors.
D2D_COLOR_F copyOfPredefinedColor = D2D1::ColorF(D2D1::ColorF::AliceBlue); // Alternative syntax using the derived helper class would be: D2D1::ColorF copyOfPredefinedColor2 = D2D1::ColorF(D2D1::ColorF::AliceBlue); |
Note: For the complete list of predefined colors available in the D2D1::ColorF enumeration, right-click on AliceBlue or another color identifier and select Go To Definition from the context menu. This will open the Direct2DHelper.h file where the list of predefined colors is defined
Note: The D2D1::ColorF class inherits from the D2D_COLOR_F class. It is the same but it defines some useful functions and an enumeration of predefined colors.
You can also create your own colors by specifying the amount of red, green, and blue the color has as floating point values:
D2D1::ColorF brightMagenta = D2D1::ColorF(1.0f, 0.0f, 1.0f); |
The constructor for the ColorF class takes three parameters with an optional fourth (which defaults to 1.0f and represents the opacity or alpha channel). The first three arguments are the amount of red, green, and blue in the color. Here I have defined 100% red, 0% green, and 100% blue. This combines to create a bright magenta color. In this color model, the range for the components is from 0.0f to 1.0f, where 0.0f means none at all and 1.0f means full saturation or 100%.
You will often see colors initialized with something like the following:
D2D1::ColorF myColor = D2D1::ColorF(D2D1::ColorF::PredefinedColor);
The PredefinedColor is one of the colors from the ColorF enum defined in the D2D1Helper.h file.This is a call to the copy constructor of the ColorF class. The nested reference to the predefined color is the value to copy.
You can also define colors in a style similar to HTML colors using their hexadecimal representation.
D2D1::ColorF coffee = 0xCEAA7A; |
Here, the value is an unsigned integer usually written as six hexadecimal digits, which represent three unsigned bytes ranging from 0 to 255 in decimal each. The lowest two digits represent the amount of blue (the 7A in the example), and they can range from 00 (none) to FF (255 or 100% saturation). The next two digits (the AA in the example) represent the amount of green in a similar fashion, and the highest two digits (the CE in the example) are the amount of red.
The example color model is called RGB for red, green, and blue. Sometimes there is an additional channel called the alpha channel, which is usually used to represent the opacity of the color. An alpha value of 0% means completely transparent, and 100% means completely opaque. The RGB color system with the additional alpha channel is called the ARGB color system, because the topmost bits of a 32-bit unsigned integer are used to store the alpha channel.
Tip: The RGB color model on little-endian systems (like x86 and ARM) results in the byte order for the color of a pixel actually being BGR, the reverse, when stored in memory. The blue byte is the lowest in memory and the red byte is the highest. When using the ARGB model, the byte order is BGRA.
The solid background introduced clearing the screen; the next background will introduce Direct2D’s Gradient Brush. Almost everything that we draw in Direct2D we do so using a brush. There are several different types of brush. We saw previously the use of a solid color brush to render text. Gradient backgrounds can be created by coloring the whole render target with a linear gradient brush prior to rendering the data. To create a GradientBackground class, add two files to the project, GradientBackground.h and GradientBackground.cpp.
// GradientBackground.h #pragma once #include "DirectXBase.h" // Gradient background class GradientBackground { private: D2D1_COLOR_F *colors; // The colors in the gradient float *stops;// Positions of the colors int count; // The number of different colors used D2D1_RECT_F m_ScreenRectangle; // The size of the rectangle we're filling // The linear gradient brush performs the painting Microsoft::WRL::ComPtr<ID2D1LinearGradientBrush> m_linearGradientBrush; public: // Creates a new gradient background GradientBackground(D2D1_COLOR_F colors[], float stops[], int count); // Release dynamic memory ~GradientBackground(); void CreateWindowSizeDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); }; |
// GradientBackground.cpp #include "pch.h" #include "GradientBackground.h" GradientBackground::GradientBackground(D2D1_COLOR_F colors[], float stops[], int count) { // The constructor just saves the colors and stops this->count = count; this->colors = new D2D1_COLOR_F[count]; this->stops = new float[count];
for(int i = 0; i < count; i++) { this->colors[i] = D2D1_COLOR_F(colors[i]); this->stops[i] = stops[i]; } } void GradientBackground::CreateWindowSizeDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // Create a gradient stops array from the colors and stops D2D1_GRADIENT_STOP *gradientStops = new D2D1_GRADIENT_STOP[count]; for(int i = 0; i < count; i++) { gradientStops[i].color = colors[i]; gradientStops[i].position = stops[i]; } // Create a Stop Collection from this using the // context's create method: ID2D1GradientStopCollection *gradientStopsCollection; DX::ThrowIfFailed( context->CreateGradientStopCollection ( gradientStops, // Stops count, // How many? &gradientStopsCollection // Output object )); // Create a linear gradient brush from this: DX::ThrowIfFailed( context->CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties ( D2D1::Point2F(0, 0),// Start point of gradient D2D1::Point2F( // Finish point of gradient context->GetSize().width, context->GetSize().height )), gradientStopsCollection, &m_linearGradientBrush)); // Also save the rectangle we're filling m_ScreenRectangle = D2D1::RectF(0, 0, context->GetSize().width, context->GetSize().height); } void GradientBackground::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // The fill the whole screen with the gradient context->FillRectangle(&m_ScreenRectangle, m_linearGradientBrush.Get()); } GradientBackground::~GradientBackground() { delete[] colors; delete[] stops; } |
The constructor of this class does nothing more than save the values passed as parameters to a member variable, such that they can be used in the resources allocation methods to create a brush. The most important method is the creation of window size dependent resources. We want our gradient to fill the whole render target so we should place it in the window size dependent resources method. In the code sample, you will see two points specified in the method call to CreateLinearGradientBrush.
D2D1::Point2F(0, 0), // Start point of gradient D2D1::Point2F( // Finish point of gradient context->GetSize().width, context->GetSize().height )), |
When we create a gradient brush, we first create a collection of the stops, positions at which the colors are blended, and from this we create the gradient brush itself. I have also saved the render target size to a member variable, so that it isn't calculated in the Render method.
I have used the screen coordinates (0, 0) and (width, height) such that the gradient stretches from the top left corner to the lower right. If you want a straight vertical gradient blending from the top of the screen to the bottom, you could use 0 instead of the screen's width as the first parameter of the second point. Likewise, if you want a gradient that stretches horizontally, you could replace the context->GetSize().height with 0.
Creating a linear gradient brush requires specifying a list of colors and a list of positions where the colors change. If the colors were red, green, and blue, and the stops were 0.0f, 0.2f, and 1.0f, the gradient's colors would be blended like the following:
![]()
At 0% across the gradient it is red, and at 20% across it is green, and at 100% it is blue. You can add as many stops and colors as needed. The stops can be higher than 100% (1.0f). This means the gradient's blend extends further than the visible area. If the stops do not begin at 0.0f, the first color will be assumed for the start of the gradient. Likewise, if the stops do not end with 1.0f, the remainder of the gradient brush will use the last color.
To make an instance and render the gradient background we follow a similar pattern to that of the solid background. However, the solid background did not need any resources, whereas the gtradient background creates a brush on the device, so we need to call the CreateWindowSizeDependentResources method to have the gradient background create this brush.
Note: We are examining the linear gradient, but there are also radial gradient brushes available in Direct2D. These radiate the gradient outwards from a central point in concentric circles.
Replace the reference to the SolidBackground.h header with a reference to the new GradientBackground.h header in the GraphRenderer.h file.
// // Additional headers for graph objects here // #include "GradientBackground.h" |
Replace the declaration of the SolidBackground member variable with a declaration of the new GradientBackground.
private: // Global pan value for moving the chart with the mouse Windows::Foundation::Point m_pan; GradientBackground* m_gradientBackground; }; |
Replace the call to the SolidBackground constructor with a call to the new GradientBackground constructor in the GraphRenderer’s constructor.
GraphRenderer::GraphRenderer() { D2D1_COLOR_F colors[] = { D2D1::ColorF(ColorF::PaleGoldenrod), D2D1::ColorF(ColorF::PaleTurquoise), D2D1::ColorF(0.7f, 0.7f, 1.0f, 1.0f) }; float stops[] = { 0.0f, 0.5f, 1.0f }; m_gradientBackground = new GradientBackground(colors, stops, 3); } |
Note: Standard colors, such as ColorF::Red, are declared as integer RGBA values in an enum. Our GradientBackground class expects an array of D2D1_COLOR_F structures, each of which represents a color as four distinct floating point values. This is the reason for wrapping the standard color inside a call to D2D1::ColorF().
Call the method to create the background's window size dependent resources in the GraphRenderer's CreateWindowSizeDependent resources method.
void GraphRenderer::CreateWindowSizeDependentResources() { DirectXBase::CreateWindowSizeDependentResources(); m_gradientBackground->CreateWindowSizeDependentResources(m_d2dContext); } |
And finally, replace the call to render the SolidBackground with the call to render our new GradientBackground.
void GraphRenderer::Render() { m_d2dContext->BeginDraw(); // Reset the transform matrix so our gradient does not pan m_d2dContext->SetTransform(m_orientationTransform2D); m_gradientBackground->Render(m_d2dContext); // Pan the chart Matrix3x2F panMatrix = Matrix3x2F::Translation(m_pan.X, m_pan.Y); m_d2dContext->SetTransform(panMatrix*m_orientationTransform2D); // // Draw objects here // // Ignore D2DERR_RECREATE_TARGET error HRESULT hr = m_d2dContext->EndDraw(); if (hr != D2DERR_RECREATE_TARGET) DX::ThrowIfFailed(hr); } |
I have assumed that the gradient background is not to be affected by the panning (translation) of the chart. This is only meant for panning the data. For this reason, I have added the call to render just after resetting the transformation matrix.
The number of gradients in the collection can be very large and generated rather than stored, or hard programmed into the code. The following creation of a gradient background produces a random pastel rainbow, and this could replace the code we placed into the GraphRenderer’s constructor:
const int count = 500; D2D1_COLOR_F *cols = new D2D1_COLOR_F[count]; float* stops = new float[count]; for(int i = 0; i < count; i++) { cols[i] = D2D1::ColorF( 0.75f+(float)(rand()%192)/192.0f, // Random pastels 0.75f+(float)(rand()%192)/192.0f, 0.75f+(float)(rand()%192)/192.0f); stops[i] = (float)i / (count - 1); }
m_gradientBackground = new GradientBackground(cols, stops, count); |
The example code produces a rather pleasing gradient which should look something like Figure 13.
![]()
Rainbow Gradient
Gradient backgrounds are excellent for charting, especially on devices with limited resources, because they are more appealing to look at than the standard solid background. They are faster to render and initialize than a bitmap background, and they do not take up any disk space or bloat the application by storing an image.
Next we will examine loading and displaying an image by creating a bitmap background. You can use the WIC (Windows Imaging Component) to load a bitmap image (or several other standard image formats) and display it as a background. Bitmap backgrounds provide the most flexibility, but are more costly in terms of rendering performance.
Note: Thanks to the flexibility of the WIC decoders, this class will be able to load many standard image file formats. Windows 8 ships with decoders for JPEG, TIFF, PNG, BMP, and others. With no change to the code, our charts should be able to load any of these image formats.
The first thing to do is add an image file to your project. Right-click the project's name in the Solution Explorer and click Add Existing item... as per Figure 14.

Adding an Existing Item
Locate the image file you wish to use in the Add Existing Item box that appears. I will use an image file called background5.jpg in this example. Once you have selected your file, click Add as per Figure 15.

Adding the Image
Now that we have added a bitmap to our project, we can create the BitmapBackground class by adding the BitmapBackground.h and BitmapBackground.cpp files.
// BitmapBackground.h #pragma once #include "DirectXBase.h" // Defines a background consisting of a bitmap image class BitmapBackground { private: ID2D1Bitmap * m_bmp; // The image to draw D2D1_RECT_F m_screenRectangle; // Destination rectangle
public: // Constructor for bitmap backgrounds BitmapBackground();
// Release dynamic memory ~BitmapBackground(); void CreateDeviceDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context, IWICImagingFactory2 *wicFactory, LPCWSTR filename); void CreateWindowSizeDependentResources( Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); void Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context); }; |
Tip: The ID2D1Bitmap is a device dependent resource. Many WinRT devices do not have dedicated GPU RAM, so the bitmap will be stored in the limited system memory. To reduce strain on system resources, it is best to load only relatively small bitmaps or load few of them if you intend your application to function smoothly on these devices..
// BitmapBackground.cpp #include "pch.h" #include "BitmapBackground.h" // This constructor must be called at some point after the // WIC factory is initialized! BitmapBackground::BitmapBackground() { } BitmapBackground::~BitmapBackground(){ m_bmp->Release(); } void BitmapBackground::CreateDeviceDependentResources (Microsoft::WRL::ComPtr<ID2D1DeviceContext> context, IWICImagingFactory2 *wicFactory, LPCWSTR filename) { // Create a WIC decoder IWICBitmapDecoder *pDecoder; // Decode a file, make sure you've added the file to the project first: DX::ThrowIfFailed(wicFactory->CreateDecoderFromFilename(filename, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &pDecoder)); // Read a frame from the file (png, jpg, bmp etc. images only have one frame so // the index is always 0): IWICBitmapFrameDecode *pFrame = nullptr; DX::ThrowIfFailed(pDecoder->GetFrame(0, &pFrame)); // Create format converter to ensure data is the correct format despite the // file's format. // It's likely the format is already perfect but we can't be sure: IWICFormatConverter *m_pConvertedSourceBitmap; DX::ThrowIfFailed(wicFactory->CreateFormatConverter(&m_pConvertedSourceBitmap)); DX::ThrowIfFailed(m_pConvertedSourceBitmap->Initialize( pFrame, GUID_WICPixelFormat32bppPRGBA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeCustom)); // Create a Direct2D bitmap from the converted source DX::ThrowIfFailed(context->CreateBitmapFromWicBitmap( m_pConvertedSourceBitmap, &m_bmp)); // Release the dx objects we used to create the bmp pDecoder->Release(); pFrame->Release(); m_pConvertedSourceBitmap->Release(); } void BitmapBackground::CreateWindowSizeDependentResources( Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { // Save a rectangle the same size as the area to draw the background m_screenRectangle = D2D1::RectF(0, 0, context->GetSize().width, context->GetSize().height); } void BitmapBackground::Render(Microsoft::WRL::ComPtr<ID2D1DeviceContext> context) { context->DrawBitmap(m_bmp, &m_screenRectangle); } |
The constructor is empty and the destructor just releases the bitmap. Most of the code for this background revolves around loading and decoding the image with the WIC factory and related components. This is done in the BitmapBackground::CreateDeviceDependentResources method. We first create a decoder using the wicFactory’s CreateDecoderFromFile method. Then we use that to read a frame from the file. There will only be one frame in a JPEG, PNG, or bitmap image, so the frame index we pass is 0 (in the call to pDecoder’s GetFrame method). There is a chance that the format of the frame we just read was something other than the standard RGB pixels we want to use. Use a WIC Format Converter to convert the data, and finally, create an ID2D1Bitmap object from this. We can then render the resulting converted frame to the screen.
The constructor for this class is empty, and the creation of the bitmap is delegated to the CreateDeviceDependentResources method because we require the use of the WIC decoder, but the WIC decoder is not initialized when the constructor of the graph renderer is called.
Note: The order of the calls to the constructor and methods to create the resources in the GraphRenderer class is Constructor, CreateDeviceIndependentResources, CreateDeviceResources, then CreateWindowSizeDependentResources. This order is specified in the DirectXBase.cpp file. DirectXPage also plays a role in this sequencing by calling the constructor.

Sequence of Resource Creation Methods
To make a new instance of our class and render a bitmap background, replace the #include of the GradientBackground.h header with an #include for the new BitmapBackground.h header in the GraphRenderer.h file.
// // Additional headers for graph objects here // #include "BitmapBackground.h" |
Replace the definition of the GradientBackground member variable with a definition for the new BitmapBackground member variable called m_bitmapBackground at the bottom of the GraphRenderer.h file.
private: // Global pan value for moving the chart with the mouse Windows::Foundation::Point m_pan; BitmapBackground* m_bitmapBackground; }; |
Replace the code in the GraphRenderer’s constructor, which was used to construct the gradient background, with a call to the constructor for our new class.
GraphRenderer::GraphRenderer() { m_bitmapBackground = new BitmapBackground(); } |
Call the new bitmap background's CreateDeviceResources method in the Graph Renderer’s method of the same name (remember to change the file name to the actual image you have used).
void GraphRenderer::CreateDeviceResources() { DirectXBase::CreateDeviceResources(); // Load the bitmap for our background m_bitmapBackground->CreateDeviceDependentResources( m_d2dContext, m_wicFactory.Get(), L"Background5.jpg"); } |
Replace the call to the m_gradientBackground->CreateWindowSizeDependentResources method in the GraphRenderer’s CreateWindowSizeDependentResources method with a call to the new class’s CreateWindowSizeDependentResources.
void GraphRenderer::CreateWindowSizeDependentResources() { DirectXBase::CreateWindowSizeDependentResources(); m_bitmapBackground->CreateWindowSizeDependentResources(m_d2dContext); } |
And finally, replace the call to the GradientBackground’s render method with a call to the new BitmapBackground's render method in the graph renderer's render method before the panning matrix is applied.
Note: The downside to using a bitmap background is that it increases the footprint of the application. An image takes space on the hard drive, but depending on the format this may be negligible. However, once the image is decoded and loaded into our application as an ID2D1Bitmap, it will no longer use the compression algorithm of the file format. For instance, a 2247 × 1345 pixel JPEG consumes less than 1 MB of disk space, but when loaded into the application it will add almost 9 MB to the memory usage. This 9 MB is due to there being 2247 × 1345 pixels in the image, each stored as red, green, and blue bytes. So the whole image is 2247 × 1345 × 3 bytes or about 8.6 MB.
void GraphRenderer::Render() { m_d2dContext->BeginDraw(); // Reset the transform matrix so our background does not pan m_d2dContext->SetTransform(m_orientationTransform2D); m_bitmapBackground->Render(m_d2dContext); // Pan the chart Matrix3x2F panMatrix = Matrix3x2F::Translation(m_pan.X, m_pan.Y); m_d2dContext->SetTransform(panMatrix*m_orientationTransform2D); // // Draw objects here // |