创建一个简单的Direct2D应用程序

本文将带领您完成创建DemoApp类,该类将创建一个窗口,并使用Direct2D绘制一个网格和两个矩形。在本教程中,您将学习如何创建Direct2D资源和绘制基本图像。您还将学习如何构建应用程序,并通过最小化资源的创建来提高程序的性能。

Demo下载路径:https://download.csdn.net/download/xiaoyafang123/17507434

本教程包含以下部分:

  • 第1部分:创建DemoApp类的声明部分
  • 第2部分:实现DemoApp类的方法
  • 第3分部:创建Direct2D所需的资源
  • 第4部分:通过Direct2D绘制图形
  • 总结

完成以上的操作后,你将得到如下所示的图形:

第1部分:创建DemoApp类的声明部分

在本部分中,首先你将添加Direct2D所依赖的头文件和必须宏定义。然后你还需要声明DemoApp类的数据成员和方法。

1.添加所必须的头文件

// Windows Header Files:
    #include <windows.h>

    // C RunTime Header Files:
    #include <stdlib.h>
    #include <malloc.h>
    #include <memory.h>
    #include <wchar.h>
    #include <math.h>

    #include <d2d1.h>
    #include <d2d1helper.h>
    #include <dwrite.h>
    #include <wincodec.h>

2.定义释放函数和必要的宏

template<class Interface>
    inline void SafeRelease(
        Interface **ppInterfaceToRelease
        )
    {
        if (*ppInterfaceToRelease != NULL)
        {
            (*ppInterfaceToRelease)->Release();

            (*ppInterfaceToRelease) = NULL;
        }
    }


    #ifndef Assert
    #if defined( DEBUG ) || defined( _DEBUG )
    #define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
    #else
    #define Assert(b)
    #endif //DEBUG || _DEBUG
    #endif



    #ifndef HINST_THISCOMPONENT
    EXTERN_C IMAGE_DOS_HEADER __ImageBase;
    #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
    #endif

3.声明用于初始化类、创建和丢弃资源、处理消息循环、呈现内容和windows过程的方法。

class DemoApp
    {
    public:
        DemoApp();
        ~DemoApp();

        // Register the window class and call methods for instantiating drawing resources
        HRESULT Initialize();

        // Process and dispatch messages
        void RunMessageLoop();

    private:
        // Initialize device-independent resources.
        HRESULT CreateDeviceIndependentResources();

        // Initialize device-dependent resources.
        HRESULT CreateDeviceResources();

        // Release device-dependent resource.
        void DiscardDeviceResources();

        // Draw content.
        HRESULT OnRender();

        // Resize the render target.
        void OnResize(
            UINT width,
            UINT height
            );

        // The windows procedure.
        static LRESULT CALLBACK WndProc(
            HWND hWnd,
            UINT message,
            WPARAM wParam,
            LPARAM lParam
            );

    
};

4.声明一个ID2D1Factory对象、一个ID2D1HwndRenderTarget对象和两个ID2D1SolidColorBrush对象的指针作为类成员。

private:
    HWND m_hwnd;
    ID2D1Factory* m_pDirect2dFactory;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
    ID2D1SolidColorBrush* m_pCornflowerBlueBrush;

第2部分:实现DemoApp类的方法

在本部分中,您将实现DemoApp构造函数和析构函数、它的初始化和消息循环方法以及WinMain函数。这些方法中的大多数看起来与其他Win32应用程序中的方法相同。惟一的例外是初始化方法,该方法调用CreateDeviceIndependentResources方法(您将在下一部分中看到该方法),该方法创建几个Direct2D资源。

1.实现类的构造函数和析构函数。构造函数将其成员初始化为NULL。析构函数释放作为类成员的任何接口。

DemoApp::DemoApp() :
        m_hwnd(NULL),
        m_pDirect2dFactory(NULL),
        m_pRenderTarget(NULL),
        m_pLightSlateGrayBrush(NULL),
        m_pCornflowerBlueBrush(NULL)
    {
    }

    
DemoApp::~DemoApp()
    {
        SafeRelease(&m_pDirect2dFactory);
        SafeRelease(&m_pRenderTarget);
        SafeRelease(&m_pLightSlateGrayBrush);
        SafeRelease(&m_pCornflowerBlueBrush);

    }

2.实现DemoApp::RunMessageLoop方法

void DemoApp::RunMessageLoop()
    {
        MSG msg;

        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

3.现Initialize方法来创建窗口,显示窗口,并调用DemoApp::CreateDeviceIndependentResources方法。您将在下一节中实现CreateDeviceIndependentResources方法。

HRESULT DemoApp::Initialize()
    {
        HRESULT hr;

        // Initialize device-indpendent resources, such
        // as the Direct2D factory.
        hr = CreateDeviceIndependentResources();

        if (SUCCEEDED(hr))
        {
            // Register the window class.
            WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
            wcex.style         = CS_HREDRAW | CS_VREDRAW;
            wcex.lpfnWndProc   = DemoApp::WndProc;
            wcex.cbClsExtra    = 0;
            wcex.cbWndExtra    = sizeof(LONG_PTR);
            wcex.hInstance     = HINST_THISCOMPONENT;
            wcex.hbrBackground = NULL;
            wcex.lpszMenuName  = NULL;
            wcex.hCursor       = LoadCursor(NULL, IDI_APPLICATION);
            wcex.lpszClassName = L"D2DDemoApp";

            RegisterClassEx(&wcex);


            // Because the CreateWindow function takes its size in pixels,
            // obtain the system DPI and use it to scale the window size.
            FLOAT dpiX, dpiY;

            // The factory returns the current system DPI. This is also the value it will use
            // to create its own windows.
            m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY);


            // Create the window.
            m_hwnd = CreateWindow(
                L"D2DDemoApp",
                L"Direct2D Demo App",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
                static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
                NULL,
                NULL,
                HINST_THISCOMPONENT,
                this
                );
            hr = m_hwnd ? S_OK : E_FAIL;
            if (SUCCEEDED(hr))
            {
                ShowWindow(m_hwnd, SW_SHOWNORMAL);
                UpdateWindow(m_hwnd);
            }
        }

        return hr;
    }

4.创建作为应用程序入口点的WinMain方法。初始化DemoApp类的一个实例并开始其消息循环。

int WINAPI WinMain(
        HINSTANCE /* hInstance */,
        HINSTANCE /* hPrevInstance */,
        LPSTR /* lpCmdLine */,
        int /* nCmdShow */
        )
    {
        // Use HeapSetInformation to specify that the process should
        // terminate if the heap manager detects an error in any heap used
        // by the process.
        // The return value is ignored, because we want to continue running in the
        // unlikely event that HeapSetInformation fails.
        HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

        if (SUCCEEDED(CoInitialize(NULL)))
        {
            {
                DemoApp app;

                if (SUCCEEDED(app.Initialize()))
                {
                    app.RunMessageLoop();
                }
            }
            CoUninitialize();
        }

        return 0;
    }

第3分部:创建Direct2D所需的资源

在本部分中,您将创建用于绘图的Direct2D资源。Direct2D提供两种类型的资源:设备无关资源,可以在整个应用程序生命周期保留;设备相关资源,需要和具体的设备绑定,如果设备被移除,资源失效。

1.实现DemoApp::CreateDeviceIndependentResources方法。在该方法中,创建一个设备无关资源(ID2D1Factory),该资源用于创建其他的Direct2D资源。使用m_pDirect2DdFactory类成员来存储。

HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;

        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);

        return hr;
    }

2.实现DemoApp::CreateDeviceResources方法。此方法创建窗口的设备相关资源:一个渲染目标和两个画刷。通过已知的窗口句柄获取到窗口的客户端区域,然后创建一个相同大小的 ID2D1HwndRenderTarget  渲染目标对象,并用m_pRenderTarget 成员变量保存该渲染目标对象。

RECT rc;
GetClientRect(m_hwnd, &rc);

D2D1_SIZE_U size = D2D1::SizeU(
  rc.right - rc.left,
  rc.bottom - rc.top
  );

// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
  D2D1::RenderTargetProperties(),
  D2D1::HwndRenderTargetProperties(m_hwnd, size),
  &m_pRenderTarget
  );

3.使用渲染目标对象创建一个灰色的ID2D1SolidColorBrush对象和一个菊蓝的ID2D1SolidColorBrush对象。

if (SUCCEEDED(hr))
{
    // Create a gray brush.
   hr = m_pRenderTarget->CreateSolidColorBrush(
           D2D1::ColorF(D2D1::ColorF::LightSlateGray),
            &m_pLightSlateGrayBrush
            );
}
if (SUCCEEDED(hr))
{
    // Create a blue brush.
    hr = m_pRenderTarget->CreateSolidColorBrush(
          D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
          &m_pCornflowerBlueBrush
          );
}

4.因为这个方法会被反复调用,所以添加一个if语句来检查渲染目标对象(m_pRenderTarget)是否已经存在。下面的代码显示了完整的CreateDeviceResources方法。

HRESULT DemoApp::CreateDeviceResources()
    {
        HRESULT hr = S_OK;

        if (!m_pRenderTarget)
        {
            RECT rc;
            GetClientRect(m_hwnd, &rc);

            D2D1_SIZE_U size = D2D1::SizeU(
                rc.right - rc.left,
                rc.bottom - rc.top
                );

            // Create a Direct2D render target.
            hr = m_pDirect2dFactory->CreateHwndRenderTarget(
                D2D1::RenderTargetProperties(),
                D2D1::HwndRenderTargetProperties(m_hwnd, size),
                &m_pRenderTarget
                );


            if (SUCCEEDED(hr))
            {
                // Create a gray brush.
                hr = m_pRenderTarget->CreateSolidColorBrush(
                    D2D1::ColorF(D2D1::ColorF::LightSlateGray),
                    &m_pLightSlateGrayBrush
                    );
            }
            if (SUCCEEDED(hr))
            {
                // Create a blue brush.
                hr = m_pRenderTarget->CreateSolidColorBrush(
                    D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
                    &m_pCornflowerBlueBrush
                    );
            }
        }

        return hr;
    }

5.实现DemoApp::DiscardDeviceResources方法。在这个方法中,释放在DemoApp::CreateDeviceResources方法中创建的渲染目标对象和两个画刷对象。

    void DemoApp::DiscardDeviceResources()
    {
        SafeRelease(&m_pRenderTarget);
        SafeRelease(&m_pLightSlateGrayBrush);
        SafeRelease(&m_pCornflowerBlueBrush);
    }

第4部分:通过Direct2D绘制图形

在本部分中,您将实现窗口过程,绘制内容的OnRender方法,以及当窗口大小改变时调整渲染目标对象大小的OnResize方法。

1.实现处理窗口消息的DemoApp::WndProc方法:收到WM_SIZE消息时,调用DemoApp::OnResize方法并传递新的宽度和高度。收到WM_PAINT和WM_DISPLAYCHANGE消息时,调用DemoApp::OnRender方法来绘制窗口。您可以在后面的步骤中看到OnRender和OnResize方法的实现。

    LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM                                                     
    lParam)
    {
        LRESULT result = 0;

        if (message == WM_CREATE)
        {
            LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
            DemoApp *pDemoApp = (DemoApp *)pcs->lpCreateParams;

            ::SetWindowLongPtrW(
                hwnd,
                GWLP_USERDATA,
                reinterpret_cast<LONG_PTR>(pDemoApp)
                );

            result = 1;
        }
        else
        {
            DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>(
                ::GetWindowLongPtrW(
                    hwnd,
                    GWLP_USERDATA
                    )));

            bool wasHandled = false;

            if (pDemoApp)
            {
                switch (message)
                {
                case WM_SIZE:
                    {
                        UINT width = LOWORD(lParam);
                        UINT height = HIWORD(lParam);
                        pDemoApp->OnResize(width, height);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_DISPLAYCHANGE:
                    {
                        InvalidateRect(hwnd, NULL, FALSE);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_PAINT:
                    {
                        pDemoApp->OnRender();
                        ValidateRect(hwnd, NULL);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_DESTROY:
                    {
                        PostQuitMessage(0);
                    }
                    result = 1;
                    wasHandled = true;
                    break;
                }
            }

            if (!wasHandled)
            {
                result = DefWindowProc(hwnd, message, wParam, lParam);
            }
        }

        return result;
    }

2.实现DemoApp::OnRender方法。首先,定义一个HRESULT。然后调用CreateDeviceResource方法。每次绘制窗口时都会调用此方法。回想一下,在第3部分的第4步中,您添加了一个if语句,以便在渲染目标对象已经存在的情况下阻止该方法执行任何工作。

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;

        hr = CreateDeviceResources();

3.验证CreateDeviceResource方法是否调用成功。如果没有,就不要绘制任何图形。

        if (SUCCEEDED(hr))
        {

4.在刚刚的If语句里面,通过调用渲染目标对象的BeginDraw方法开始渲染,然后设置一个单位矩阵给渲染目标对象用于变换,并且清空窗口。

            m_pRenderTarget->BeginDraw();

            m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

            m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

5.获取绘图区域的大小。

D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();

6.通过一个for循环和渲染目标对象的DrawLine 方法绘制一个网格背景。

            // Draw a grid background.
            int width = static_cast<int>(rtSize.width);
            int height = static_cast<int>(rtSize.height);

            for (int x = 0; x < width; x += 10)
            {
                m_pRenderTarget->DrawLine(
                    D2D1::Point2F(static_cast<FLOAT>(x), 0.0f),
                    D2D1::Point2F(static_cast<FLOAT>(x), rtSize.height),
                    m_pLightSlateGrayBrush,
                    0.5f
                    );
            }

            for (int y = 0; y < height; y += 10)
            {
                m_pRenderTarget->DrawLine(
                    D2D1::Point2F(0.0f, static_cast<FLOAT>(y)),
                    D2D1::Point2F(rtSize.width, static_cast<FLOAT>(y)),
                    m_pLightSlateGrayBrush,
                    0.5f
                    );
            }

7.创建两个位于屏幕中心的矩形图像。

            // Draw two rectangles.
            D2D1_RECT_F rectangle1 = D2D1::RectF(
                rtSize.width/2 - 50.0f,
                rtSize.height/2 - 50.0f,
                rtSize.width/2 + 50.0f,
                rtSize.height/2 + 50.0f
                );

            D2D1_RECT_F rectangle2 = D2D1::RectF(
                rtSize.width/2 - 100.0f,
                rtSize.height/2 - 100.0f,
                rtSize.width/2 + 100.0f,
                rtSize.height/2 + 100.0f
                );

8.使用渲染目标对象的 FillRectangle 方法和灰色画刷对象填充第一个矩形的内部。

            // Draw a filled rectangle.
            m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);

9.使用渲染目标对象的DrawRectangle 方法和菊蓝色画刷绘制第二个矩形的轮廓线。

            // Draw the outline of a rectangle.
            m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);

10.调用渲染目标对象的EndDraw方法。EndDraw方法返回一个HRESULT来表示绘图操作是否成功。结束步骤3中开始的if语句。

    hr = m_pRenderTarget->EndDraw();
 }

11.检查EndDraw返回的HRESULT。如果返回D2DERR_RECREATE_TARGET值,表示需要重新创建渲染目标对象,调用DemoApp::DiscardDeviceResources方法来释放它;它将在下次窗口接收到WM_PAINT或WM_DISPLAYCHANGE消息时重新创建。

        if (hr == D2DERR_RECREATE_TARGET)
        {
            hr = S_OK;
            DiscardDeviceResources();
        }

12.返回HRESULT并结束该方法。

    return hr;
}

13.实现DemoApp::OnResize方法,使其将渲染目标对象调整为窗口的新大小。

    void DemoApp::OnResize(UINT width, UINT height)
    {
        if (m_pRenderTarget)
        {
            // Note: This method can fail, but it's okay to ignore the
            // error here, because the error will be returned again
            // the next time EndDraw is called.
            m_pRenderTarget->Resize(D2D1::SizeU(width, height));
        }
    }

您已经完成了本教程。

注意:要使用Direct2D,请确保您的应用程序包含d2d1.h头文件,并添加了对d2d1.lib库的依赖。你可以在Windows SDK中找到d2d1.h和d2d1.lib 。

总结

在本教程中,您学习了如何创建Direct2D资源和绘制基本图形。您还学习了如何构建应用程序,并通过最小化资源的创建来提高程序的性能。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值