为方便,将D2D的一个基本类DesktopWindow写成如下所示:
#include "Precompiled.h" #include <d2d1.h> #include <wrl.h> #pragma comment(lib, "d2d1") using namespace D2D1; using namespace Microsoft::WRL; template<typename T> struct DesktopWindow : CWindowImpl<T, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW | WS_VISIBLE>> { ComPtr<ID2D1Factory> m_factory; ComPtr<ID2D1HwndRenderTarget> m_target; DECLARE_WND_CLASS_EX(L"My D2D1 Window", CS_HREDRAW | CS_VREDRAW, -1); BEGIN_MSG_MAP(DesktopWindow) MESSAGE_HANDLER(WM_PAINT, PaintHandler) MESSAGE_HANDLER(WM_DESTROY, DestroyHandler) MESSAGE_HANDLER(WM_SIZE, SizeHandler) MESSAGE_HANDLER(WM_DISPLAYCHANGE, DisplayChangeHandler) END_MSG_MAP() LRESULT DisplayChangeHandler(UINT, WPARAM, LPARAM, BOOL&) { Invalidate(); return 0; } LRESULT SizeHandler(UINT, WPARAM, LPARAM lparam, BOOL&) { if (m_target) { if (m_target->Resize(SizeU(LOWORD(lparam), HIWORD(lparam))) != S_OK) { m_target.Reset(); } } return 0; } LRESULT PaintHandler(UINT, WPARAM, LPARAM, BOOL&) { PAINTSTRUCT ps; VERIFY(BeginPaint(&ps)); Render(); EndPaint(&ps); return 0; } LRESULT DestroyHandler(UINT, WPARAM, LPARAM, BOOL&) { PostQuitMessage(0); return 0; } void Invalidate() { VERIFY(InvalidateRect(nullptr, false)) } void Render() { if (!m_target) { RECT rect; VERIFY(GetClientRect(&rect)); auto size = SizeU(rect.right, rect.bottom); m_factory->CreateHwndRenderTarget(RenderTargetProperties(), HwndRenderTargetProperties(m_hWnd, size), m_target.GetAddressOf()); static_cast<T *>(this)->CreateDeviceResources(); } if (!(m_target->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)) { m_target->BeginDraw(); static_cast<T *>(this)->Draw(); if (m_target->EndDraw() == D2DERR_RECREATE_TARGET) { m_target.Reset(); } } } int Run() { D2D1_FACTORY_OPTIONS fo = {}; #ifdef DEBUG fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, fo, m_factory.GetAddressOf()); CreateDeviceIndependentResources(); VERIFY(__super::Create(nullptr, 0, L"title")); MSG msg; BOOL result; while (result = GetMessage(&msg, 0, 0, 0)) { if (result != -1) { DispatchMessage(&msg); } } return msg.wParam; } virtual void CreateDeviceIndependentResources() {} };
所有的画笔(Brush)都有SetOpacity和SetTransform两个方法,所有的单色画笔(SolidColorBrush)都有SetColor方法,可以设置颜色。
画笔是可修改的(mutable),因此,可以只声明一个画笔,然后在使用的时候,不断地更换颜色。
最简单的单色画笔(SolidColorBrush)示例如下所示:
#include "Precompiled.h" #include "DesktopWindow.h" D2D1_COLOR_F const COLOR_BLUE = { 0.26f, 0.56f, 0.87f, 1.0f }; D2D1_COLOR_F const COLOR_YELLOW = { 0.99f, 0.85f, 0.0f, 1.0f }; D2D1_COLOR_F const COLOR_BLACK = { 0.0f, 0.0f, 0.0f, 1.0f }; D2D1_COLOR_F const COLOR_WHITE = { 1.0f, 1.0f, 1.0f, 1.0f }; struct SampleWindow : DesktopWindow { ComPtr<ID2D1SolidColorBrush> m_brush; void CreateDeviceResources() { m_target->CreateSolidColorBrush(COLOR_BLUE, m_brush.ReleaseAndGetAddressOf()); } void Draw() { m_target->Clear(COLOR_BLUE); auto size = m_target->GetSize(); m_brush->SetOpacity(1.0f); m_brush->SetColor(COLOR_BLACK); auto br = RectF(100.0f, 100.0f, size.width - 100.0f, 200.0f); m_target->FillRectangle(br, m_brush.Get()); m_brush->SetColor(COLOR_WHITE); auto bw = RectF(100.0f, 300.0f, size.width - 100.0f, 400.0f); m_target->FillRectangle(bw, m_brush.Get()); m_brush->SetOpacity(.5f); m_brush->SetColor(COLOR_YELLOW); auto ry = RectF(150.0f, 150.0f, size.width - 150.0f, 350.0f); m_target->FillRectangle(ry, m_brush.Get()); } }; int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { SampleWindow window; window.Run(); }
线性渐变画笔(LinearGradientBrush)稍微麻烦,需要先定义关键点(D2D1_GRADIENT_STOP),然后将关键点加入渐变集合(ID2D1GradientStopCollection),最后将渐变集合定义为渐变画笔,其示例如下所示:
#include "Precompiled.h" #include "DesktopWindow.h" D2D_COLOR_F const COLOR_BLUE = { 0.26f, 0.56f, 0.87f, 1.0f }; D2D_COLOR_F const COLOR_WHITE = { 1.0f, 1.0f, 1.0f, 1.0f }; struct SampleWindow : DesktopWindow<SampleWindow> { ComPtr<ID2D1LinearGradientBrush> m_brush; void CreateDeviceResources() { D2D1_GRADIENT_STOP stops[] = { { 0.0f, COLOR_WHITE }, { 1.0f, COLOR_BLUE } }; ComPtr<ID2D1GradientStopCollection> collection; m_target->CreateGradientStopCollection(stops, _countof(stops), collection.GetAddressOf()); D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = {}; m_target->CreateLinearGradientBrush(props, collection.Get(), m_brush.ReleaseAndGetAddressOf()); } void Draw() { auto size = m_target->GetSize(); m_brush->SetEndPoint(Point2F(size.width, size.height)); auto r = RectF(0.0f, 0.0f, size.width, size.height); m_target->FillRectangle(r, m_brush.Get()); } }; int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { SampleWindow window; window.Run(); }
可以使用环形渐变画笔(RadialGradientBrush),其定义方法和线性渐变画笔类似,也是定义关键点、渐变集合(COM),然后再定义渐变画笔,其救命如下所示:
#include "Precompiled.h" #include "DesktopWindow.h" D2D1_COLOR_F const COLOR_BLUE = { 0.26f, 0.56f, 0.87f, 1.0f }; D2D_COLOR_F const COLOR_WHITE = { 1.0f, 1.0f, 1.0f, 1.0f }; struct SampleWindow : DesktopWindow<SampleWindow> { ComPtr<ID2D1RadialGradientBrush> m_brush; void CreateDeviceResources() { D2D1_GRADIENT_STOP stops[] = { { 0.0f, COLOR_WHITE }, { 1.0f, COLOR_BLUE } }; ComPtr<ID2D1GradientStopCollection> collection; m_target->CreateGradientStopCollection(stops, _countof(stops), collection.GetAddressOf()); D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = {}; m_target->CreateRadialGradientBrush(props, collection.Get(), m_brush.ReleaseAndGetAddressOf()); } void Draw() { auto size = m_target->GetSize(); m_brush->SetCenter(Point2F(size.width / 2.0f, size.height / 2.0f)); m_brush->SetRadiusX(size.width / 2.0f); m_brush->SetRadiusY(size.height / 2.0f); auto r = RectF(0.0f, 0.0f, size.width, size.height); m_target->FillRectangle(r, m_brush.Get()); } }; int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { SampleWindow window; window.Run(); }
在创建渐变集合时(CreateGradientStopCollection),可以设置D2D1_GAMMA_1_0或者D2D1_GAMMA_2_2,默认是D2D1_GAMMA_2_2,具体区别如下图所示(上图为2.2),直观上的区别就是D2D1_GAMMA_2_2的渐变性更好。
最后,还可以通过设置边框样式(ID2D1StrokeStyle)来显示各种边框,注意边框样式是独立于设备的资源,因此,只需将边框样式的定义放在CreateDeviceIndependentResources方法中即可:
#include "Precompiled.h" #include "DesktopWindow.h" D2D1_COLOR_F const COLOR_BLUE = { 0.26f, 0.56f, 0.87f, 1.0f }; D2D_COLOR_F const COLOR_WHITE = { 1.0f, 1.0f, 1.0f, 1.0f }; struct SampleWindow : DesktopWindow<SampleWindow> { ComPtr<ID2D1SolidColorBrush> m_brush; ComPtr<ID2D1StrokeStyle> m_style; void CreateDeviceIndependentResources() { D2D1_STROKE_STYLE_PROPERTIES props = {}; props.lineJoin = D2D1_LINE_JOIN_ROUND; props.dashStyle = D2D1_DASH_STYLE_DASH_DOT; props.dashCap = D2D1_CAP_STYLE_ROUND; m_factory->CreateStrokeStyle(props, nullptr, 0, m_style.GetAddressOf()); } void CreateDeviceResources() { m_target->CreateSolidColorBrush(COLOR_WHITE, m_brush.ReleaseAndGetAddressOf()); } void Draw() { m_target->Clear(COLOR_BLUE); auto size = m_target->GetSize(); auto r = RectF(100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f); m_target->DrawRectangle(r, m_brush.Get(), 20.0f, m_style.Get()); } }; int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { SampleWindow window; window.Run(); }