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()
.
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.
void CD2DShapesDlg::CreateDeviceResources()
{
HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Crimson),
m_StrokeBrush.ReleaseAndGetAddressOf()));
HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Yellow),
m_FillBrush.ReleaseAndGetAddressOf()));
}
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<ID2D1StrokeStyle> stroke = CreateStrokeStyle();
m_Target->DrawLine(
Point2F(10.0f, 40.0f),
Point2F(110.0f, 40.0f),
m_StrokeBrush.Get(),
8.0f,
stroke.Get());
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.
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
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.
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
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<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;
if (SUCCEEDED(hr))
{
hr = FactorySingleton::GetGraphicsFactory()->CreatePathGeometry
(m_pPathGeometry.ReleaseAndGetAddressOf());
if (SUCCEEDED(hr))
{
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.
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
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.
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