Introduction
GDI+ has many features all right, but it is somewhat slow. Microsoft introduced Direct2D in Windows 7, a featured hardware-accelerated API that you can now use for applications. My first usage of Direct2D was in my Turbo Play project, which without it takes 28ms to draw screen. With it, it takes < 1 ms!
The good thing about Direct2D is that it can be easily combined with GDI, GDI+, or Direct3D.
You Need
Overview
Direct2D is an ActiveX object that can draw to an HWND
or to an HDC
. For this to work, the steps are:
- Instantiate an
ID2D1Factory
by creating D2D1CreateFactory()
from D2D1.DLL (I suggest you link to this dynamically using LoadLibrary
/GetProcAddress
so your code runs in any Windows version).
- Call
ID2D1Factory :: CreateHwndRenderTarget()
to target an HWND
, or call ID2D1Factory :: CreateDCRenderTarget()
to target an HDC
.
- Use the returned
ID2D1RenderTarget
interface to draw:
- Call
ID2D1RenderTarget::BeginDraw()
- Draw stuff
- Call
ID2D1RenderTarget::EndDraw()
Selecting a Brush to Draw
The following function creates a Direct2D Brush from a given ARGB color:
ID2D1SolidColorBrush* GetD2SolidBrush(ID2D1RenderTarget* pTR,unsigned long c)
{
if (!pRT)
return 0;
ID2D1SolidColorBrush* b = 0;
D2D1_COLOR_F cc;
cc.a = GetAValue(c)/255.0f;
if (cc.a == 0)
cc.a = 1.0f;
cc.r = GetRValue(c)/255.0f;
cc.g = GetGValue(c)/255.0f;
cc.b = GetBValue(c)/255.0f;
pRT->CreateSolidColorBrush(cc,&b);
return b;
}
Draw Lines, Rectangles, Ellipses
Use the functions exported from ID2D1RenderTarget
, like DrawEllipse()
, DrawRectangle()
, DrawLine()
, FillEllipse()
, and FillRectangle()
.
Draw Polygons
The following function demonstrates how to create a polygon from a given set of POINT*
s. You simply create a path geometry (ID2DFactory :: CreatePathGeometry()
), open it (ID2D1PathGeometry::Open()
) to get its sink, call ID2D1GeometrySink ::BeginFigure
, add items (Line, Lines, Bezier curves etc.), call ID2D1GeometrySink::EndFigure()
, then ID2D1PathGeometry::Close()
, and finally, you pass the geometry object to ID2D1RenderTarget::DrawGeometry
.
void Polygon(POINT*p,int n,bool Close)
{
D2D1_POINT_2F* pt = new D2D1_POINT_2F[n];
for(int i = 0 ; i < n ; i++)
{
pt[i].x = (FLOAT)p[i].x;
pt[i].y = (FLOAT)p[i].y;
}
ID2D1SolidColorBrush* b = GetD2SolidBrush(c);
ID2D1PathGeometry* pg = 0;
ID2D1GeometrySink* pgs = 0;
pD2DFactory->CreatePathGeometry(&pg);
if (pg)
{
pg->Open(&pgs);
if (pgs)
{
D2D1_POINT_2F fb;
fb.x = (FLOAT)pt[0].x;
fb.y = (FLOAT)pt[0].y;
D2D1_FIGURE_BEGIN fg = D2D1_FIGURE_BEGIN_HOLLOW;
D2D1_FIGURE_END fe;
if (Close)
fe = D2D1_FIGURE_END_CLOSED;
else
fe = D2D1_FIGURE_END_OPEN;
pgs->BeginFigure(fb,fg);
for(int i = 1 ; i < n ; i++)
{
D2D1_POINT_2F fu;
fu.x = pt[i].x;
fu.y = pt[i].y;
pgs->AddLine(fu);
}
pgs->EndFigure(fe);
pgs->Close();
pgs->Release();
}
if (b)
pRT->DrawGeometry(pg,b,1);
pg->Release();
if (b)
b->Release();
delete[] pt;
}
Draw Images
Direct2D loads a bitmap from a Windows Imaging component. The simple function below will use an existing HBITMAP
(which must be a 32-bit one!) to draw:
void Image(int x1,int y1,HBITMAP hB,float Op = 1.0f);
{
BITMAP bo;
GetObject(hB,sizeof(bo),&bo);
WICBitmapAlphaChannelOption ao = WICBitmapUseAlpha;
IWICBitmap* wb = 0;
pImageFactory->CreateBitmapFromHBITMAP(hB,0,ao,&wb);
if (!wb)
return;
ID2D1Bitmap* b = 0;
pRT->CreateBitmapFromWicBitmap(wb,0,&b);
if (!b)
{
IWICFormatConverter* spConverter = 0;
pImageFactory->CreateFormatConverter(&spConverter);
if (spConverter)
{
spConverter->Initialize(wb,GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,NULL,0.f,
WICBitmapPaletteTypeMedianCut);
pRT->CreateBitmapFromWicBitmap(spConverter,0,&b);
spConverter->Release();
}
wb->Release();
}
if (b)
{
D2D1_RECT_F r;
r.left = (FLOAT)x1;
r.top = (FLOAT)y1;
r.right = (FLOAT)(x1 + bo.bmWidth);
r.bottom = (FLOAT)(y1 + bo.bmHeight);
pRT->DrawBitmap(b,r,Op);
b->Release();
}
}
Draw Text
Text in Direct2D is written through DirectWrite
(to be continued...), another new Direct* API in Windows 7. The steps are:
- Have the
IDWriteFactory
(DWriteCreateFactory
from DWRITE.DLL).
- Set the font by calling
IDWriteFactory::CreateTextFormat()
with font parameters, to get the IDWriteTextFormat*
. (See my SetFont()
function.)
- Call
IDWriteTextFormat
members to set the format - my code calls SetTextAlignment
/SetParagraphAlignment
to set the alignment.
- Call
ID2D1RenderTarget::DrawText
.
To measure the text dimensions, you have to create IDWriteTextLayout*
(IDWriteFactory::CreateTextLayout
) which represents the formatted text's attributes (for more, see my TextSize()
function).
The Code
The code is part of my cross-platform drawing library, which is not complete (GDI/GDI+ stuff has been stripped from it to focus on the D2D code). You can use the mydraw.h functions:
void Line(int x1,int y1,int x2,int y2,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID);
void Rect(RECT&ar,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID,bool Elp = false);
void FilledRect(RECT&ar,unsigned long c = AXRGB(0xFF,0,0,0),bool Elp = false);
void Polygon(POINT*p,int n,bool Close,int th = 1,
unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID);
void FilledPolygon(POINT*p,int n,bool Close,
unsigned long c = AXRGB(0xFF,0,0,0));
void Ellipse(RECT&ar,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID);
void FilledEllipse(RECT&ar,unsigned long c = AXRGB(0xFF,0,0,0));
void Rect(int x1,int y1,int wi,int he,int th = 1,
unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID,bool Elp = false);
void Ellipse(int x1,int y1,int wi,int he,int th = 1,
unsigned long c = AXRGB(0xFF,0,0,0),
unsigned int PenStyle = PS_SOLID);
void FilledRect(int x1,int y1,int wi,int he,
unsigned long c = AXRGB(0xFF,0,0,0),bool Elp = false);
void FilledEllipse(int x1,int y1,int wi,int he,
unsigned long c = AXRGB(0xFF,0,0,0));
unsigned long TextSize(const wchar_t* txt,int l,
unsigned long al,unsigned long lal);
void DrawText(const wchar_t* txt,int l,int x,int y,int wi,int he,
unsigned long al,unsigned long lal,
unsigned long c = AXRGB(0xFF,0,0,0),int BreakMode = 1);
void DrawText(const wchar_t* txt,int l,RECT&,unsigned long al,
unsigned long lal,unsigned long c = AXRGB(0xFF,0,0,0),
int BreakMode = 1);
void SetFont(HFONT hF1);
void Image(int x1,int y1,LPWSTR fil,float Op = 1.0f);
void Image(int x1,int y1,HINSTANCE h,LPWSTR n,
LPWSTR typ = RT_BITMAP,float Op = 1.0f);
void Image(int x1,int y1,HBITMAP hB,float Op = 1.0f, bool HasAlpha = 0);
void Image(int x1,int y1,Gdiplus::Bitmap* b,bool HasAlpha = 0);
HRESULT LoadResourceImage(HINSTANCE h,PCWSTR resourceName,
PCWSTR resourceType,void**ppBitmap);
HRESULT LoadFileImage(const LPWSTR f,void**ppBitmap);
to draw stuff. Load test32.sln and compile it, and select the drawing mode from the menu. A performance counter is also included.
Todo...
- Add interoperability between Direct2D, Direct3D , GDI+, and GDI
- Add brush strokes
- Add complex geometry
History
- 08 - 5 - 2009: Update for RC1.
- 25 - 4 - 2009: First release.