65.9K
CodeProject 正在变化。 阅读更多。
Home

Direct2D 教程 第 1 部分:RenderTarget

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2020年5月25日

CPOL

4分钟阅读

viewsIcon

35525

downloadIcon

936

Direct2D RenderTarget 教程

示例代码托管在 Github 上。

目录

引言

Direct2D 于 2009 年推出,旨在取代过时的 GDI+,并支持 Windows 7 或更高版本。这是 introductory Direct2D 教程系列的第一篇。在本教程中,我们将了解各种 RenderTarget。将 RenderTarget 视为一个可以绘制的画布。我们将重点介绍下面列出的四种 RenderTarget 类型。每种都有其特定的用途。

  • HWND 渲染目标
  • 设备上下文 (DC) 渲染目标
  • 位图渲染目标
  • Windows 图像组件 (WIC) 渲染目标

HWND 渲染目标

第一个渲染目标是基于 HWND 的。在使用 Direct2D 之前,需要引入两个命名空间:D2D1Microsoft::WRL,用于访问 Direct2D 类和 ComPtr(COM 对象的智能指针)。

using namespace D2D1;
using namespace Microsoft::WRL;

为了简化工厂的创建和访问,工厂被 put 在 FactorySingleton 中。

class FactorySingleton
{
public:
    static ComPtr<ID2D1Factory> GetGraphicsFactory();
    static ComPtr<IWICImagingFactory> GetImageFactory();
    static void DestroyImageFactory();
private:
    static ComPtr<ID2D1Factory> m_GraphicsFactory;
    static ComPtr<IWICImagingFactory> m_ImageFactory;
};

如果 GetGraphicsFactory() 的指针是 nullptr,它将创建图形工厂。

ComPtr<ID2D1Factory> FactorySingleton::GetGraphicsFactory()
{
    if (!m_GraphicsFactory)
    {
        D2D1_FACTORY_OPTIONS fo = {};

#ifdef DEBUG
        fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

        HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
            fo,
            m_GraphicsFactory.GetAddressOf()));

    }
    return m_GraphicsFactory;
}

我们将使用 MFC 来演示 Direct2D 代码。一个 MFC 对话框类,用于持有名为 m_TargetID2D1HwndRenderTarget 对象,以及三个函数。

class CD2DHwndRTDlg : public CDialogEx
{
    ComPtr<ID2D1HwndRenderTarget> m_Target;

    void CreateDeviceResources();
    void CreateDeviceIndependentResources();
    void Draw();
};

在本教程中,我们不会使用任何资源,因此也不会创建任何资源,所以 CreateDeviceResources()CreateDeviceIndependentResources() 应该为空。Draw() 会将对话框窗口清除为玉米蓝色。

void CD2DHwndRTDlg::CreateDeviceResources()
{
}

void CD2DHwndRTDlg::CreateDeviceIndependentResources()
{
}

void CD2DHwndRTDlg::Draw()
{
    m_Target->Clear(ColorF(0.26f, 0.56f, 0.87f));
}

所有绘图都发生在重写的 OnPaint() 函数中。如果 m_Targetnullptr,我们使用图形工厂的 CreateHwndRenderTarget() 来创建它。在 m_Target 被创建之后,应该调用 CreateDeviceResources() 来创建与 m_Target 关联的设备相关资源。如前所述,在本教程中,我们没有任何资源。在进行任何绘图之前,我们首先检查我们的窗口是否被遮挡(意味着被其他窗口覆盖),如果被遮挡,则跳过绘图。所有绘图必须在 BeginDraw()EndDraw() 之间进行。当 EndDraw() 返回 D2DERR_RECREATE_TARGET 时,我们将 m_Target 重置为 nullptr 并调用 Invalidate(),这将触发 WM_PAINT 消息,进而再次调用 OnPaint()。当 m_Target 被发现是 nullptr 时,它将被再次创建。

void CD2DHwndRTDlg::OnPaint()
{
    // unrelated code generated from VC++ wizard not shown
    CDialogEx::OnPaint();

    if (!m_Target)
    {
        CRect rc;
        GetClientRect(rc);

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

        HR(FactorySingleton::GetGraphicsFactory()->CreateHwndRenderTarget(
            RenderTargetProperties(),
            HwndRenderTargetProperties(GetSafeHwnd(), size),
            m_Target.ReleaseAndGetAddressOf()));

        CreateDeviceResources();
    }

    if (!(D2D1_WINDOW_STATE_OCCLUDED & m_Target->CheckWindowState()))
    {
        m_Target->BeginDraw();

        Draw();

        if (D2DERR_RECREATE_TARGET == m_Target->EndDraw())
        {
            m_Target.Reset();
            Invalidate();
        }
    }
}

这是我们的玉米蓝色窗口。

设备上下文 (DC) 渲染目标

接下来,我们来看设备上下文渲染目标。读者可能会问,既然我们有了 HWND 渲染目标,为什么还需要 DC 渲染目标?这是一个非常好的问题。HWND 渲染目标在可滚动窗口上渲染不正确,而 DC 渲染目标在窗口滚动时渲染没有任何问题。除此之外,没有其他理由要使用 DC RT 而不是 HWND RT。这次,m_Target 是一个通用的 ID2D1DCRenderTarget 对象。

class CD2DDeviceContextRTDlg : public CDialogEx
{
    ComPtr<ID2D1DCRenderTarget> m_Target;

    void CreateDeviceResources();
    void CreateDeviceIndependentResources();
    void Draw();
};

DC RT 使用图形工厂的 CreateDCRenderTarget() 创建。在 DC RT 上进行任何绘图之前,必须使用 BindDC() 绑定一个有效的 DC。DC 不会检查是否被遮挡,因为通用的 m_Target 不提供 CheckWindowState()

void CD2DDeviceContextRTDlg::OnPaint()
{
    //CDialogEx::OnPaint();
    if (!m_Target)
    {
        // Create a pixel format and initial its format
        // and alphaMode fields.
        D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(
            DXGI_FORMAT_B8G8R8A8_UNORM,
            D2D1_ALPHA_MODE_PREMULTIPLIED
        );

        D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
        props.pixelFormat = pixelFormat;

        HR(FactorySingleton::GetGraphicsFactory()->CreateDCRenderTarget(&props,
            m_Target.ReleaseAndGetAddressOf()));

        CreateDeviceResources();
    }

    CPaintDC dc(this);
    CRect rc;
    GetClientRect(rc);
    m_Target->BindDC(dc.GetSafeHdc(), &rc);

    m_Target->BeginDraw();

    Draw();

    if (D2DERR_RECREATE_TARGET == m_Target->EndDraw())
    {
        m_Target.Reset();
        Invalidate();
    }
}

位图渲染目标

我们可以使用 Bitmap RT 为 DC RT 提供双缓冲。m_BmpTarget 是一个额外的 RT(将进行所有绘图),而 m_Target 将其位图blit(复制)到窗口。

class CD2DBmpRTDlg : public CDialogEx
{
    ComPtr<ID2D1DCRenderTarget> m_Target;
    ComPtr<ID2D1BitmapRenderTarget> m_BmpTarget;

    void CreateDeviceResources();
    void CreateDeviceIndependentResources();
    void Draw();
};

m_BmpTargetClear() 必须在其 BeginDraw()EndDraw() 之间调用。

void CD2DBmpRTDlg::Draw()
{
	m_BmpTarget->BeginDraw();
	m_BmpTarget->Clear(ColorF(0.26f, 0.56f, 0.87f));
	m_BmpTarget->EndDraw();
}

m_Target 创建 m_BmpTarget 之前,其 DC 必须使用 BindDC() 绑定。在 m_BmpTarget 上调用 GetBitmap 以获取其内部位图,供 m_Target 使用 DrawBitmap() 绘制到窗口。

void CD2DBmpRTDlg::OnPaint()
{
    //CDialogEx::OnPaint();
    CRect rc;
    GetClientRect(rc);
    CPaintDC dc(this);

    if (!m_Target)
    {
        // Create a pixel format and initial its format
        // and alphaMode fields.
        D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(
            DXGI_FORMAT_B8G8R8A8_UNORM,
            D2D1_ALPHA_MODE_PREMULTIPLIED
        );

        D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
        props.pixelFormat = pixelFormat;

        HR(FactorySingleton::GetGraphicsFactory()->CreateDCRenderTarget(&props,
            m_Target.ReleaseAndGetAddressOf()));

        m_Target->BindDC(dc.GetSafeHdc(), &rc);

        HR(m_Target->CreateCompatibleRenderTarget(m_BmpTarget.ReleaseAndGetAddressOf()));

        CreateDeviceResources();
    }

    Draw();

    ComPtr<ID2D1Bitmap> bitmap;
    m_BmpTarget->GetBitmap(bitmap.GetAddressOf());

    m_Target->BindDC(dc.GetSafeHdc(), &rc);

    m_Target->BeginDraw();

    m_Target->DrawBitmap(bitmap.Get());

    if (D2DERR_RECREATE_TARGET == m_Target->EndDraw())
    {
        m_Target.Reset();
        Invalidate();
    }
}

Windows 图像组件 (WIC) 渲染目标

最后,我们有 Windows 图像组件位图渲染目标,用于将绘图保存为 JPEG 和 PNG 等图像格式。在 FactorySingleton 中,我们有 GetImageFactory(),它在检查到为 nullptr 时创建图像工厂。我们还有 DestroyImageFactory() 来销毁图像工厂。

ComPtr<IWICImagingFactory> FactorySingleton::GetImageFactory()
{
    if (!m_ImageFactory)
    {
        CreateInstance(CLSID_WICImagingFactory, m_ImageFactory);
    }
    return m_ImageFactory;
}

void FactorySingleton::DestroyImageFactory()
{
    if (m_ImageFactory)
    {
        m_ImageFactory.Reset();
    }
}

WIC 需要 COM 运行时,所以我们必须调用 CoInitialize()CoUninitialize() 来初始化和反初始化 COM 运行时。由于图像工厂是一个单例,而它本身又是一个全局变量:它可能在 COM 运行时反初始化后才被销毁。为了防止这种情况,我们在 CoUninitialize() 之前调用 DestroyImageFactory(),以确保全局 COM 对象(图像工厂)首先被销毁。

CD2DWicRTApp::CD2DWicRTApp()
{
	HR(CoInitialize(nullptr));
}

CD2DWicRTApp::~CD2DWicRTApp()
{
	FactorySingleton::DestroyImageFactory();
	CoUninitialize();
}

在对话框类中,m_Target 是一个通用的 ID2D1RenderTarget 对象,它将由 WIC 位图支持。这次,我们不绘制到窗口,而是通过 m_Target 绘制到 WIC 位图,然后使用 SaveAs() 将图像保存到磁盘。

class CD2DWicRTDlg : public CDialogEx
{
    ComPtr<ID2D1RenderTarget> m_Target;
    ComPtr<IWICBitmap> m_WicBitmap; // WIC for above RT

    void CreateDeviceResources();
    void CreateDeviceIndependentResources();
    void Draw();

    void PaintAndSaveImage(PCWSTR filename);

    void SaveAs(ComPtr<IWICBitmap>& bitmap, PCWSTR filename);
};

我们调用 PaintAndSaveImage(),并提供新图像文件的路径。

BOOL CD2DWicRTDlg::OnInitDialog()
{
    // irrelevant code not displayed
    // TODO: Add extra initialization here
    PaintAndSaveImage(L"C:\\temp\\sample.PNG");

    return TRUE; 
}

m_WicBitmap 通过 CreateBitmap() 使用图像工厂创建,然后使用图形工厂的 CreateWicBitmapRenderTarget()m_WicBitmap 作为第一个参数创建 m_Target。绘图完成后,调用 SaveAs() 保存图像。

void CD2DWicRTDlg::PaintAndSaveImage(PCWSTR filename)
{
    CRect rc;
    GetClientRect(rc);

    if (!m_Target)
    {
        // Create a pixel format and initial its format
        // and alphaMode fields.
        D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(
            DXGI_FORMAT_B8G8R8A8_UNORM,
            D2D1_ALPHA_MODE_PREMULTIPLIED
        );

        D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
        props.pixelFormat = pixelFormat;

        HR(FactorySingleton::GetImageFactory()->CreateBitmap(rc.right, rc.bottom,
            GUID_WICPixelFormat32bppPBGRA,
            WICBitmapCacheOnLoad,
            m_WicBitmap.ReleaseAndGetAddressOf()));

        HR(FactorySingleton::GetGraphicsFactory()->CreateWicBitmapRenderTarget(m_WicBitmap.Get(),
            RenderTargetProperties(), m_Target.ReleaseAndGetAddressOf()));

        CreateDeviceResources();
    }

    m_Target->BeginDraw();

    Draw();

    if (D2DERR_RECREATE_TARGET == m_Target->EndDraw())
    {
        m_Target.Reset();
        Invalidate();
    }
    else
    {
        SaveAs(m_WicBitmap, filename);
    }
}

void CD2DWicRTDlg::SaveAs(ComPtr<IWICBitmap>& bitmap, PCWSTR filename)
{
    CString filename_lower = filename;
    filename_lower = filename_lower.MakeLower();
    CString ext = filename_lower.Right(4);
    GUID guid = GUID_ContainerFormatPng;
    if (ext == L".png")
        guid = GUID_ContainerFormatPng;
    else if (ext == L".jpg")
        guid = GUID_ContainerFormatJpeg;

    ext = filename_lower.Right(5);
    if (ext == L".jpeg" || ext == L".jpg+")
        guid = GUID_ContainerFormatJpeg;

    ComPtr<IStream> file;

    HR(SHCreateStreamOnFileEx(filename,
        STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
        FILE_ATTRIBUTE_NORMAL,
        TRUE, // create
        nullptr, // template
        file.GetAddressOf()));

    ComPtr<IWICBitmapEncoder> encoder;

    HR(FactorySingleton::GetImageFactory()->CreateEncoder(guid,
        nullptr, // vendor
        encoder.GetAddressOf()));

    HR(encoder->Initialize(file.Get(), WICBitmapEncoderNoCache));

    ComPtr<IWICBitmapFrameEncode> frame;
    ComPtr<IPropertyBag2> properties;

    HR(encoder->CreateNewFrame(frame.GetAddressOf(), properties.GetAddressOf()));

    HR(frame->Initialize(properties.Get()));

    UINT width, height;
    HR(bitmap->GetSize(&width, &height));
    HR(frame->SetSize(width, height));

    GUID pixelFormat;
    HR(bitmap->GetPixelFormat(&pixelFormat));

    auto negotiated = pixelFormat;
    HR(frame->SetPixelFormat(&negotiated));

    HR(frame->WriteSource(bitmap.Get(), nullptr));

    HR(frame->Commit());
    HR(encoder->Commit());
}

我们已经完成了第一个教程。我希望您能清楚地了解每种 Render Target 的用途。示例代码托管在 Direct2D tutorials

历史

  • 2020年5月25日:初始版本

系列文章

© . All rights reserved.