Direct2D 教程 第 1 部分:RenderTarget





5.00/5 (13投票s)
Direct2D RenderTarget 教程
示例代码托管在 Github 上。
目录
引言
Direct2D 于 2009 年推出,旨在取代过时的 GDI+,并支持 Windows 7 或更高版本。这是 introductory Direct2D 教程系列的第一篇。在本教程中,我们将了解各种 RenderTarget
。将 RenderTarget
视为一个可以绘制的画布。我们将重点介绍下面列出的四种 RenderTarget
类型。每种都有其特定的用途。
HWND
渲染目标- 设备上下文 (DC) 渲染目标
- 位图渲染目标
- Windows 图像组件 (WIC) 渲染目标
HWND 渲染目标
第一个渲染目标是基于 HWND
的。在使用 Direct2D 之前,需要引入两个命名空间:D2D1
和 Microsoft::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_Target
的 ID2D1HwndRenderTarget
对象,以及三个函数。
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_Target
是 nullptr
,我们使用图形工厂的 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_BmpTarget
的 Clear()
必须在其 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日:初始版本