Click here to Skip to main content
15,880,503 members
Articles / Programming Languages / C++

Direct2D Tutorial Part 3: Affine Transforms

Rate me:
Please Sign up or sign in to vote.
4.82/5 (7 votes)
27 Jan 2021CPOL4 min read 10.5K   279   12  
Direct2D Tutorial on Applying Affine Transforms on Drawings
In this article, we'll look at how to do affine transform using matrices in Direct2D.

Table of Contents

The example code is hosted at Github.

Introduction

In this article, we'll look at how to do affine transform using matrices in Direct2D. The transformation has to be set up before any drawing that is to be transformed. 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.

Text Format

To display the text in Direct2D, IDWriteTextFormat has to be created first. IDWriteTextFormat is a device-independent resource that is created in CreateDeviceIndependentResources() which is in turn called once by OnInitDialog() or in any initialization function you may have. A device-independent resource does not need to be recreated when the Direct2D target is lost. IDWriteTextFormat and its creation function, CreateTextFormat() shall be examined in more detail in a future text drawing tutorial.

C++
ComPtr<IDWriteTextFormat> m_TextFormat;

void CD2DAffineTransformDlg::CreateDeviceIndependentResources()
{
    HR(FactorySingleton::GetDWriteFactory()->CreateTextFormat(L"Arial Black",
        nullptr, DWRITE_FONT_WEIGHT_ULTRA_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 40, L"",
        m_TextFormat.ReleaseAndGetAddressOf()));
}

Translation

Translated Text

In this section, the text is translated (or moved) downward along the y-axis by 50 pixels. Before SetTransform() is set with our translation matrix, it has to be called with IdentityMatrix() to reset whatever matrix set prior to this stage. The translate matrix is created from Matrix3x2F::Translation(). The Matrix3x2F::Translation()'s 2 parameters are the amount of X and Y coordinates to 'move' from the current point. Then m_FillBrush is set to a Direct2D predefined black color. You can choose other predefined or custom colors. To define a custom color to be used in SetColor(), give ColorF() 4 floating-point numbers in RGBA order. Then to draw the text, DrawTextW is called on the m_Target.

C++
CRect rectClient;
GetClientRect(&rectClient);

auto rect = RectF(0.0f, 0.0f, rectClient.Width(), rectClient.Height());

m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);

m_Target->SetTransform(trans);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), 
          m_TextFormat.Get(), &rect, m_FillBrush.Get());

Skew

Skewed Text

Next, we'll see how to do a skew to make the text look italic and then followed by the same translation. GetClientRect() code from the previous section is not shown as they are unchanged. The transformations are chained by the matrix multiplication. You see the order of matrix multiplication is very important here. The order of matrix multiplication must be the opposite of the order of the desired transformation. For instance, we want the skew to be the first transformation, then in the matrix multiplication, then the skew matrix has to be the last operand. Vice-versa for the translation matrix. See the skew matrix is created from Matrix3x2F::Skew(). The Matrix3x2F::Skew()'s first 2 parameters are the angle(degree) to skew the along the x-axis and y-axis and the last parameter is the center point of skew.
Note: The translation matrix creation is discussed above.

C++
m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew
      (-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));

m_Target->SetTransform(trans * skew);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), 
          m_TextFormat.Get(), &rect, m_FillBrush.Get());

Rotation

Rotated Text

In this section, the text is rotated upward by -10 degrees. The skew and rotation has to be performed before translation, therefore the matrix multiplication is trans * rotate * skew. The rotate matrix is created from Matrix3x2F::Rotation() which has an angle as its first parameter. The optional center point parameter defaults to 0,0 when left out.

C++
m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(-10.0f);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew
(-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));

m_Target->SetTransform(trans * rotate * skew);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), 
                     m_TextFormat.Get(), &rect, m_FillBrush.Get());

Scale

Skewed Text

Scaling is normally used to implement zoom-in and zoom-out in computer drawing. In this last section, we'll scale down the text drawing (make smaller). Scale has to be performed before all other transformation, so it is the last operand in the matrix multiplication. See the scale matrix is created from Matrix3x2F::Scale() which has 2 parameters to specify the scaling in the x-axis and y-axis.

C++
m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(-10.0f);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew
      (-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));
D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(0.6f, 0.6f);

m_Target->SetTransform(trans * rotate * skew * scale);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), 
                     m_TextFormat.Get(), &rect, m_FillBrush.Get());

Order of Matrix Multiplication

The skew section mentions the order of matrix multiplication must be the opposite of the order of the desired transformation. A section with a more complete explanation should follow that. Take a look at the two transformations represented by two matrices T and S where T transform is applied first to V followed by S transform. V is a vector consisted of the cartesian coordinates of x and y to be transformed.

S(T(V))

Note: matrix multiplication is non-commutative, so the order of multiplication must be respected. Though T is applied first, S appears in the equation first.

ST != TS

History

Articles in the Series

License

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


Written By
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. His interest lies primarily in computer graphics, software optimization, concurrency, security, and Agile methodologies.

In recent years, he shifted focus to software safety research. His hobby is writing a free C++ DirectX photo slideshow application which can be viewed here.

Comments and Discussions

 
-- There are no messages in this forum --