Using Direct2D and DirectWrite in a .Net Environment
Despite the fact that you can “google” everything on the Internet, this is far from the case for new technologies. In particular, when I wanted to use fairly new Direct2D technologies (don't be afraid, this is not related to DirectX 7) and DirectWrite in my .Net application, I ran into the problem that there are no examples of interaction between these libraries and .Net. Therefore, I had to dig myself.
Upd .: I transfer to C ++ since subsidiaries are clearly not interested.
First, it’s worth explaining what kind of libraries it is. Direct2D is Microsoft's new API for fast, hardware-based drawing of two-dimensional graphics. Such an innovation would please those who so far have to put up with the brakes of GDI + but, alas, at the moment this functionality is available only from C ++. Yes, work on the wrapper is being carried out, for example, by the SlimDX team , but I want to use it today, so again we climb into P / Invoke.
The second library - DirectWrite - is made for high-quality display of text. By "quality" is meant support for ClearType and OpenType features, which are important mainly for those who like typography . Naturally, DirectWrite is much faster than other artisanal and inefficient methods of obtaining a similar result.
Warning: all this happiness is available through DirectX 10.1, i.e. only works in Vista and 7ke (as well as in 2008 and 2008R2, respectively). And yet, in order to develop for these frameworks, you need to download and install the latest Windows SDK (meaning the one for Windows 7 / 2008R2, I searched for it for a long time in the subscription) because by default, these features are not shipped with Visual Studio.
DirectDraw library uses the so-called "Lightweight COM", which is translated into Russian, means that actually à la COM constructs
In addition to DirectDraw, we will use a component called Windows Imaging Component or just WIC. For our (more precisely my) goals, this component is relevant because I plan to forward all the same good old
Well, try to draw something. First, I will make a prototype of the function that I will call from .Net ...
I apologize for the verbosity in the definition - this is a problem of 64-bit development and I already wrote about this . Here is the actual analogue in C ++. As you probably already guessed, the function simply draws text on the provided image. For an initial example will do.
So, the essence of the whole idea is to use the D2D and DW constructs in order to draw something there. The first step is to create factories, and not just one but three, for Direct2D, DirectWrite and WIC, respectively. Those who worked in DirectX will be familiar with this:
Hereinafter, I will separate the declaration of the interfaces with their ellipsis from their initialization in order to show that these declarations are executed in any case, and the initialization - only if the previous steps were correctly performed. It is for this that constant checks in style are needed
It's time to use the WIC factory to create a Bitmap on which we will draw. Later we will have to copy all the data from this bitmap into ours, which is detailed - but only after we draw everything we want.
After initializing the bitmap (I spent a lot of time before finding the PixelFormat that works), you still need to find some object that will draw on this bitmap. Unlike GDI +, where the class was “drawing”
This object will be used by us for drawing. Naturally, the actual image will be stored in
Text formatting implements an interface
Unlike DirectDraw, DirectWrite works without problems not only with TrueType, but also with OpenType fonts. Problems when you know that the font exists but the system uses MS Sans Serif no longer.
Before you draw text, you need to create an object of type Brush (well, just like in GDI +, right?) That will determine the “brush” with which our text will be drawn.
We are all set. Feel free to use our render target to render text. Just like in DirectX, the drawing process takes place between
Now that the text is drawn, you can copy it to our
And then you just need to "release" all the interfaces, and return as soon as possible to the world of managed code.
Despite the fact that D2D interfaces are still available only for C ++ programmers, interacting with .Net was not as complicated as I expected. Therefore, do not be afraid to experiment. I leave you with an example of the text generated using the above approach:
Upd .: I transfer to C ++ since subsidiaries are clearly not interested.
First, it’s worth explaining what kind of libraries it is. Direct2D is Microsoft's new API for fast, hardware-based drawing of two-dimensional graphics. Such an innovation would please those who so far have to put up with the brakes of GDI + but, alas, at the moment this functionality is available only from C ++. Yes, work on the wrapper is being carried out, for example, by the SlimDX team , but I want to use it today, so again we climb into P / Invoke.
The second library - DirectWrite - is made for high-quality display of text. By "quality" is meant support for ClearType and OpenType features, which are important mainly for those who like typography . Naturally, DirectWrite is much faster than other artisanal and inefficient methods of obtaining a similar result.
Warning: all this happiness is available through DirectX 10.1, i.e. only works in Vista and 7ke (as well as in 2008 and 2008R2, respectively). And yet, in order to develop for these frameworks, you need to download and install the latest Windows SDK (meaning the one for Windows 7 / 2008R2, I searched for it for a long time in the subscription) because by default, these features are not shipped with Visual Studio.
DirectDraw library uses the so-called "Lightweight COM", which is translated into Russian, means that actually à la COM constructs
QueryInterface()
will not appear in your code too often. The only thing is that there will be constant checks of the returned results for correctness. Yes, and you also need to release them after using the interfaces, or use something like CComPtr(although for this it’s kind of like sticking ATL support - I don’t know exactly because I haven’t tried it). In addition to DirectDraw, we will use a component called Windows Imaging Component or just WIC. For our (more precisely my) goals, this component is relevant because I plan to forward all the same good old
System.Drawing.Bitmap
from my .Net code, and draw into it using D2D / DWrite. Well, try to draw something. First, I will make a prototype of the function that I will call from .Net ...
[DllImport("Typografix.Bitmap.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true,
EntryPoint = "?RenderMarkup@@YAXPEAEPEB_WHHH1M@Z", CharSet = CharSet.Unicode)]
public static extern void RenderMarkup(IntPtr dst, string markup, int width,
int height, int stride, string fontFamily, float fontSize);
I apologize for the verbosity in the definition - this is a problem of 64-bit development and I already wrote about this . Here is the actual analogue in C ++. As you probably already guessed, the function simply draws text on the provided image. For an initial example will do.
MYAPI void RenderMarkup(BYTE* dst, LPCWSTR markup, int width, int height,
int stride, LPCWSTR fontFamily, float fontSize)
{
⋮
}
So, the essence of the whole idea is to use the D2D and DW constructs in order to draw something there. The first step is to create factories, and not just one but three, for Direct2D, DirectWrite and WIC, respectively. Those who worked in DirectX will be familiar with this:
IWICImagingFactory *pWICFactory = NULL;
ID2D1Factory *pD2DFactory = NULL;
IDWriteFactory *pDWriteFactory = NULL;
⋮
hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_IWICImagingFactory,
reinterpret_cast(&pWICFactory));
if (SUCCEEDED(hr))
{
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
}
if (SUCCEEDED(hr))
{
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(pDWriteFactory),
reinterpret_cast(&pDWriteFactory));
}
Hereinafter, I will separate the declaration of the interfaces with their ellipsis from their initialization in order to show that these declarations are executed in any case, and the initialization - only if the previous steps were correctly performed. It is for this that constant checks in style are needed
if (SUCCEEDED(hr))
. It's time to use the WIC factory to create a Bitmap on which we will draw. Later we will have to copy all the data from this bitmap into ours, which is detailed - but only after we draw everything we want.
IWICBitmap *pWICBitmap = NULL;
⋮
if (SUCCEEDED(hr))
{
hr = pWICFactory->CreateBitmap(width, height,
GUID_WICPixelFormat32bppBGR,
WICBitmapCacheOnLoad, &pWICBitmap);
}
After initializing the bitmap (I spent a lot of time before finding the PixelFormat that works), you still need to find some object that will draw on this bitmap. Unlike GDI +, where the class was “drawing”
Graphics
, DirectDraw can draw ID2D1RenderTarget
.if (SUCCEEDED(hr))
{
hr = pD2DFactory->CreateWicBitmapRenderTarget(
pWICBitmap, D2D1::RenderTargetProperties(), &pRT);
}
This object will be used by us for drawing. Naturally, the actual image will be stored in
pWICBitmap
which we created earlier, and ours RenderTarget
is only an intermediate object that allows you to make different implementations of drawing, depending on existing resources. Text formatting implements an interface
IDWriteTextFormat
whose specific instance we are to create. In the example below, we use the default options, and then we ask the text to appear in the upper left of the bitmap.IDWriteTextFormat *pTextFormat = NULL;
⋮
if (SUCCEEDED(hr))
{
hr = pDWriteFactory->CreateTextFormat(
fontFamily,
NULL,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
fontSize,
L"",
&pTextFormat);
}
if (SUCCEEDED(hr))
{
pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
}
Unlike DirectDraw, DirectWrite works without problems not only with TrueType, but also with OpenType fonts. Problems when you know that the font exists but the system uses MS Sans Serif no longer.
Before you draw text, you need to create an object of type Brush (well, just like in GDI +, right?) That will determine the “brush” with which our text will be drawn.
ID2D1SolidColorBrush *pBlackBrush = NULL;
⋮
if (SUCCEEDED(hr))
{
hr = pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush);
}
We are all set. Feel free to use our render target to render text. Just like in DirectX, the drawing process takes place between
Begin-
and End-
directives:if (SUCCEEDED(hr))
{
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = pRT->GetSize();
pRT->DrawText(
markup,
wcslen(markup),
pTextFormat,
D2D1::RectF(0, 0, rtSize.width, rtSize.height),
pBlackBrush);
hr = pRT->EndDraw();
}
Now that the text is drawn, you can copy it to our
System.Drawing.Bitmap
WICRect r;
r.X = r.Y = 0;
r.Width = width;
r.Height = height;
hr = pWICBitmap->CopyPixels(&r, stride, sizeof(Pixel) * width * height, dst);
And then you just need to "release" all the interfaces, and return as soon as possible to the world of managed code.
Despite the fact that D2D interfaces are still available only for C ++ programmers, interacting with .Net was not as complicated as I expected. Therefore, do not be afraid to experiment. I leave you with an example of the text generated using the above approach: