为方便,将D2D的一个基本类DesktopWindow写成如下所示:
#include "Precompiled.h"
#include <d2d1.h>
#include <wrl.h>
#pragma comment(lib, "d2d1.lib")
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();
}