Direct2D Tutorial Part 2: Basic Shapes

Table of Content

The example code is hosted at Github.

Introduction

In this article, we’ll look at how to draw lines and basic shapes in Direct2D. The prerequisite of the article is the knowledge to set up the RenderTarget. If you haven’t got a clue what RenderTarget is, please go read the RenderTarget article first and then come back to read this article.

Stroke Style

In order to draw shapes, a helper function, CreateStrokeStyle(), is needed to create a ID2D1StrokeStyle to define the cap and join style. ID2D1StrokeStyle is a device-independent resource. In the StrokeStyleProperties constructor, the style of the start cap, end cap, and dash cap is round as indicated by 3 D2D1_CAP_STYLE_ROUND arguments. The specified dash cap style is unused because the stroke style does not have any dash as specified by D2D1_DASH_STYLE_SOLID and the line join style as round as indicated by D2D1_LINE_JOIN_ROUND. The reader can experiment with other cap and join styles by modifying the CreateStrokeStyle().

ComPtr CD2DShapesDlg::CreateStrokeStyle()
{
    ComPtr strokeStyle;

    HR(FactorySingleton::GetGraphicsFactory()->CreateStrokeStyle(
        D2D1::StrokeStyleProperties(
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_LINE_JOIN_ROUND,
            0.0f,
            D2D1_DASH_STYLE_SOLID,
            0.0f),
        nullptr,
        0,
        strokeStyle.GetAddressOf()
    ));

    return strokeStyle;
}

Solid Color Brush

The two solid color brushes used for stroke and fill are created, using CreateSolidColorBrush(). Because brush is a device-dependent resource, so they should be created inside CreateDeviceResources(). Its color can be changed on the fly with SetColor() member function, as you shall see later.

void CD2DShapesDlg::CreateDeviceResources()
{
    HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Red),
        m_StrokeBrush.ReleaseAndGetAddressOf()));
    HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Yellow),
        m_FillBrush.ReleaseAndGetAddressOf()));
}

Line

d2d_line

A line is drawn with DrawLine() from one start point and one end point. The line thickness is specified as 8 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::Black));

ComPtr stroke = CreateStrokeStyle();

m_Target->DrawLine(
    Point2F(10.0f, 40.0f),
    Point2F(110.0f, 40.0f),
    m_StrokeBrush.Get(),
    8.0f,
    stroke.Get());

Rectangle

d2d_rect

The stroke brush and fill brush are set to red and yellow colors respectively. A rect variable is used to specify the rectangle boundary. The rectangle whose outline is drawn with DrawRectangle() and filled with yellow color from FillRectangle(). The 3rd parameter of DrawRectangle() is the line thickness which is 10 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::Red));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 110, 60);
m_Target->DrawRectangle(
    rect,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillRectangle(
    rect,
    m_FillBrush.Get());

Rounded Rectangle

d2d_round_rect

I often used rounded rectangles to draw the block diagrams used in my articles because the rectangles look more pleasing this way. Creators of Direct2D also knew the rounded rectangle is a common drawing primitive and decided to provide this convenience to its user. A roundedRect variable is used to specify the rectangle boundary (from rect variable), and x and y radius of the rounded corner. The outline and fill is done with DrawRoundedRectangle() and FillRoundedRectangle(). The 3rd parameter of DrawRoundedRectangle() is the line thickness which is 10 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::LightSeaGreen));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 110, 60);

const D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
    rect,
    10.f,
    10.f
);

m_Target->DrawRoundedRectangle(
    roundedRect,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillRoundedRectangle(
    roundedRect,
    m_FillBrush.Get());

Triangle

d2d_triangle

Unfortunately, there is no function in Direct2D to draw a triangle. We can always make use of ID2D1PathGeometry to create any shapes we desired. GenTriangleGeometry() is a helper function to create triangle geometry which is then drawn. ID2D1PathGeometry variable is created with CreatePathGeometry(). Then that ID2D1PathGeometry variable, m_pPathGeometry, is used to open a ID2D1GeometrySink variable, pSink. BeginFigure() is called on pSink to specify the first point and drawing to be filled. 2 line primitives are added with AddLine from 2 points. D2D1_FIGURE_END_CLOSED tells EndFigure() to close the first point and last point with a line. If you have already closed the first point and last point yourself by specifying the last point to be the same as the first point and you may not want EndFigure() to draw a line between them, then D2D1_FIGURE_END_OPEN should be specified instead.

ComPtr CD2DShapesDlg::GenTriangleGeometry(D2D1_POINT_2F pt1, D2D1_POINT_2F pt2, D2D1_POINT_2F pt3)
{
    ID2D1GeometrySink* pSink = NULL;
    HRESULT hr = S_OK;
    ComPtr m_pPathGeometry;
    // Create a path geometry.
    if (SUCCEEDED(hr))
    {
        hr = FactorySingleton::GetGraphicsFactory()->CreatePathGeometry(m_pPathGeometry.ReleaseAndGetAddressOf());

        if (SUCCEEDED(hr))
        {
            // Write to the path geometry using the geometry sink.
            hr = m_pPathGeometry->Open(&pSink);

            if (SUCCEEDED(hr))
            {
                pSink->BeginFigure(
                    pt1,
                    D2D1_FIGURE_BEGIN_FILLED
                );

                pSink->AddLine(pt2);


                pSink->AddLine(pt3);

                pSink->EndFigure(D2D1_FIGURE_END_CLOSED);

                hr = pSink->Close();
            }
            SafeRelease(&pSink);
        }
    }
    return m_pPathGeometry;
}

The triangle outline and fill are drawn with DrawGeometry() and FillGeometry() respectively.

m_StrokeBrush->SetColor(ColorF(ColorF::Purple));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
ComPtr geometry = GenTriangleGeometry(
    Point2F(60, 10), Point2F(110, 70), Point2F(10, 70));
m_Target->DrawGeometry(
    geometry.Get(),
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillGeometry(
    geometry.Get(),
    m_FillBrush.Get());

Circle

d2d_circle

A circle can be drawn with ellipse drawing functions. In the code below, an ellipse variable, ell, is created with a center point and x and y radius. The circle outline and fill are drawn with DrawEllipse() and FillEllipse() respectively.

m_StrokeBrush->SetColor(ColorF(ColorF::BlueViolet));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_ELLIPSE ell = Ellipse(Point2F(50.0f, 50.0f), 40, 40);
m_Target->DrawEllipse(
    ell,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillEllipse(
    ell,
    m_FillBrush.Get());

 

Other articles in the series

 

2 thoughts on “Direct2D Tutorial Part 2: Basic Shapes

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close