📜 ⬆️ ⬇️

Using Direct2D and DirectWrite in .Net-environment

Despite the fact that it is possible to “google” everything on the Internet, this is far from being so for new technologies. In particular, when I wanted to use fairly new Direct2D technologies (do not 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 ++. it is obviously not interesting.


First you need to clarify what these libraries are. Direct2D is Microsoft's new API for fast, hardware-based 2D graphics. Such an innovation would please those who, for the time being, should accept the GDI + brakes, but, alas, at the moment this functionality is available only from C ++. Yes, the work on the wrapper is being done, for example, by the SlimDX team, but I want to use it today, so we climb into P / Invoke again.

The second library - DirectWrite - is made for high-quality text display. By “quality” is meant support for ClearType and OpenType features, which are important mainly for those who love 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 also, in order to develop 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 a subscription) because by default these features are not shipped with Visual Studio.

Acquaintance
The DirectDraw library uses so-called. “Lightweight COM” which translates into Russian means that the actual COM constructs à la QueryInterface() will not appear in your code too often. The only thing - there will be constant checks of the returned results for correctness. Yes, and you still need to release them after using the interfaces, or use something like CComPtr (although for this it seems like you need to stick ATL support, I don’t know for sure, because I haven’t tried it).

In addition to DirectDraw, we will use a component called the Windows Imaging Component or simply WIC. For our (more precisely my) goals, this component is relevant since I plan to forward all the same good old System.Drawing.Bitmap from my .Net code, and draw into it using D2D / DWrite.

Well, let's try to draw something. I will first make a prototype of the function that I will call from .Net ...

[DllImport( "Typografix.Bitmap.dll" , CallingConvention = CallingConvention.Cdecl, ExactSpelling = true ,<br/>
EntryPoint = "?RenderMarkup@@YAXPEAEPEB_WHHH1M@Z" , CharSet = CharSet.Unicode)]<br/>
public static extern void RenderMarkup(IntPtr dst, string markup, int width, <br/>
int height, int stride, string fontFamily, float fontSize);<br/>

I apologize for the verbosity in the definition - this is a problem of 64-bit development and I already wrote about it. Here actually analog on With ++. As you probably already guessed, the function simply draws the text on the provided picture. For the initial example will go.

MYAPI void RenderMarkup( BYTE * dst, LPCWSTR markup, int width, int height, <br/>
int stride, LPCWSTR fontFamily, float fontSize)<br/>
{<br/>
⋮<br/>
}<br/>

Go deep
So, the whole idea is to use the D2D and DW constructs to draw something there. The first stage is to create factories, and not just one but three for Direct2D, DirectWrite and WIC, respectively. For those who worked in DirectX, this will be familiar:

IWICImagingFactory *pWICFactory = NULL;<br/>
ID2D1Factory *pD2DFactory = NULL;<br/>
IDWriteFactory *pDWriteFactory = NULL;<br/>
⋮<br/>
hr = CoCreateInstance(<br/>
CLSID_WICImagingFactory,<br/>
NULL,<br/>
CLSCTX_INPROC_SERVER,<br/>
IID_IWICImagingFactory,<br/>
reinterpret_cast < void **>(&pWICFactory));<br/>
<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);<br/>
}<br/>
<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
hr = DWriteCreateFactory(<br/>
DWRITE_FACTORY_TYPE_SHARED,<br/>
__uuidof(pDWriteFactory),<br/>
reinterpret_cast <IUnknown **>(&pDWriteFactory));<br/>
}<br/>

Hereinafter, I will separate in three points the declaration of interfaces from their initialization in order to show that these declarations are performed in any case, and initialization only if the previous steps have been correctly executed. This is what constant if (SUCCEEDED(hr)) type checks 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 to ours, full-time - but only after we draw everything we want.

IWICBitmap *pWICBitmap = NULL;<br/>
⋮<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
hr = pWICFactory->CreateBitmap(width, height,<br/>
GUID_WICPixelFormat32bppBGR,<br/>
WICBitmapCacheOnLoad, &pWICBitmap);<br/>
}<br/>

Having initialized bitmap (I spent a lot of time before I found the PixelFormat that works), I also need to find some object that will be drawn on this bitmap. Unlike GDI +, where the Graphics class was involved in drawing, ID2D1RenderTarget able to draw in DirectDraw.

if (SUCCEEDED(hr))<br/>
{<br/>
hr = pD2DFactory->CreateWicBitmapRenderTarget(<br/>
pWICBitmap, D2D1::RenderTargetProperties(), &pRT);<br/>
}<br/>

This object will be used by us for drawing. Naturally, the actual image will be stored in pWICBitmap that we created earlier, and our RenderTarget is only an intermediate object that allows you to create different implementations of drawing depending on existing resources.

Text manipulations
Text formatting implements the IDWriteTextFormat interface, the concrete instance of which we will create. In the example below, we use the default options, and then we ask the text to be displayed in the upper left part of the bitmap.

IDWriteTextFormat *pTextFormat = NULL;<br/>
⋮<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
hr = pDWriteFactory->CreateTextFormat(<br/>
fontFamily,<br/>
NULL,<br/>
DWRITE_FONT_WEIGHT_NORMAL,<br/>
DWRITE_FONT_STYLE_NORMAL,<br/>
DWRITE_FONT_STRETCH_NORMAL,<br/>
fontSize,<br/>
L "" ,<br/>
&pTextFormat);<br/>
}<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);<br/>
pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);<br/>
}<br/>

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 anymore.

Before you draw the text, you need to create an object of type Brush (well, just like in GDI +, isn’t it?) That will define the “brush” with which our text will be drawn.

ID2D1SolidColorBrush *pBlackBrush = NULL;<br/>
⋮<br/>
if (SUCCEEDED(hr))<br/>
{<br/>
hr = pRT->CreateSolidColorBrush(<br/>
D2D1::ColorF(D2D1::ColorF::Black),<br/>
&pBlackBrush);<br/>
}<br/>

Finish line
Everything is ready with us. Feel free to use our render target for text rendering. In the same way as in DirectX, the drawing process takes place between the Begin- and End- directives:

if (SUCCEEDED(hr))<br/>
{<br/>
pRT->BeginDraw();<br/>
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));<br/>
D2D1_SIZE_F rtSize = pRT->GetSize();<br/>
pRT->DrawText(<br/>
markup,<br/>
wcslen(markup),<br/>
pTextFormat,<br/>
D2D1::RectF(0, 0, rtSize.width, rtSize.height),<br/>
pBlackBrush);<br/>
hr = pRT->EndDraw();<br/>
}<br/>

Now that the text is drawn, you can copy it into our System.Drawing.Bitmap

WICRect r;<br/>
rX = rY = 0;<br/>
r.Width = width;<br/>
r.Height = height;<br/>
hr = pWICBitmap->CopyPixels(&r, stride, sizeof (Pixel) * width * height, dst);<br/>

And then you just need to "let go" of all interfaces, and return as soon as possible to the world of managed code.

Conclusion
Despite the fact that D2D interfaces are still available only for C ++ programmers, the interaction with .Net was not as complicated as I expected. So do not be afraid to experiment. I leave you with an example of the text generated using the above approach:

Direct2D is awesome!

Source: https://habr.com/ru/post/70240/


All Articles