Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Direct2D Tutorial Part 2: Basic Shapes

5.00/5 (7 votes)
4 Jan 2021CPOL3 min read 20.8K   523  
Direct2D Tutorial on Drawing Basic Shapes
In this article, we'll look at how to draw lines and basic shapes in Direct2D.

Table of Contents

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().

C++
ComPtr<ID2D1StrokeStyle> CD2DShapesDlg::CreateStrokeStyle()
{
    ComPtr<ID2D1StrokeStyle> 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.

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

Line

Line

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

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

ComPtr<ID2D1StrokeStyle> stroke = CreateStrokeStyle();

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

Rectangle

Rectangle

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 third parameter of DrawRectangle() is the line thickness which is 10 pixels.

C++
ComPtr<ID2D1StrokeStyle> stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 210, 160);
m_Target->DrawRectangle(
    rect,
    m_StrokeBrush.Get(),
    16.0f,
    stroke.Get());

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

Rounded Rectangle

Rounded Rectangle

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 third parameter of DrawRoundedRectangle() is the line thickness which is 10 pixels.

C++
ComPtr<ID2D1StrokeStyle> stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 210, 160);

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

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

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

Triangle

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.

C++
ComPtr<ID2D1PathGeometry> CD2DShapesDlg::GenTriangleGeometry
      (D2D1_POINT_2F pt1, D2D1_POINT_2F pt2, D2D1_POINT_2F pt3)
{
    ID2D1GeometrySink* pSink = NULL;
    HRESULT hr = S_OK;
    ComPtr<ID2D1PathGeometry> 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.

C++
ComPtr<ID2D1StrokeStyle> stroke = CreateStrokeStyle();
ComPtr<ID2D1PathGeometry> geometry = GenTriangleGeometry(
    Point2F(110, 10), Point2F(210, 140), Point2F(10, 140));
m_Target->DrawGeometry(
    geometry.Get(),
    m_StrokeBrush.Get(),
    16.0f,
    stroke.Get());

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

Circle

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.

C++
ComPtr<ID2D1StrokeStyle> stroke = CreateStrokeStyle();
const D2D1_ELLIPSE ell = Ellipse(Point2F(100.0f, 100.0f), 90, 90);
m_Target->DrawEllipse(
    ell,
    m_StrokeBrush.Get(),
    16.0f,
    stroke.Get());

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

History

  • 5th January, 2021: Update all colors to crimson and yellow and bigger shapes.
  • 22nd August, 2020: Put brush creation inside CreateDeviceResource() for best practice
  • 20th August, 2020: First release

Other Articles in the Series

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)