Как стать автором
Обновить

Использование Direct2D и DirectWrite в .Net-среде

Время на прочтение7 мин
Количество просмотров10K
Несмотря что «нагуглить» в интернете можно все, для новых технологий это далеко не так. В частности, когда я захотел использовать достаточно новые технологии Direct2D (не бойтесь, это никак не связано с DirectX 7) и DirectWrite в своем .Net-приложении, то столкнулся с проблемой что примеров взаимодействия этих библиотек и .Net нет. Поэтому пришлось самому покопаться.
Upd.: переношу в C++ т.к. дотнетчикам явно не интересно.


Сначала следует пояснить что это за библиотеки. Direct2D – это новый API от Microsoft для быстрого, аппаратно-усторенного рисования двухмерной графики. Такое нововведение обрадовало бы тех, кто пока должен смиряться с тормозами GDI+ но, увы, на данный момент этот функционал доступен только из С++. Да, работа над оберткой ведется, например, командой SlimDX, но использовать хочется сегодня, поэтому снова лезем в P/Invoke.

Вторая библиотечка – DirectWrite – сделана для качественного отображения текста. Под «качественным» подразумевается поддержка ClearType и OpenType-фич, которые важны в основном для тех кто любит типографику. Естественно, DirectWrite намного быстрее чем другие кустарные и неэффективные методы получения аналогичного результата.

Warning: все это счастье доступно через DirectX 10.1, т.е. работает только в Висте и 7ке (а также в 2008 и 2008R2 соответственно). Да и еще, для того чтобы разрабатывать под эти фреймворки нужно скачать и установить последний Windows SDK (имеется ввиду тот, который для Windows 7/2008R2, я его в подписке долго искал) т.к. by default эти фичи с Visual Studio не поставляются.

Знакомство
Библиотека DirectDraw использует т.н. «легковесный COM» что в переводе на русский означает что собственно COM-конструкты à la QueryInterface() не будут появляться в вашем коде слишком часто. Единственное – останутся постоянные проверки возвращаемых результатов на корректность. Да, и еще нужно после использования интерфейсов релизить их, или использовать что-нть вроде CComPtr (хотя для этого вроде как нужно втыкать поддержку ATL – точно не знаю т.к. не пробовал).

Помимо DirectDraw, мы будем использовать некий компонент под названием Windows Imaging Component или просто WIC. Для наших (точнее моих) целей этот компонент актуален т.к. я планирую пробрасывать все тот же старый добрый System.Drawing.Bitmap из моего .Net-кода, и рисовать в него с помощью D2D/DWrite.

Ну что, попробуем чего-нибудь нарисовать. Я для начала сделаю прототип функции, которую буду вызывать из .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/>

Прошу прощения за многословность в опредении – это проблема 64-битной разработки и я об этом уже писал. Вот собственно аналог на С++. Как вы уже наверное догадались, функция просто рисует текст на предоставленной картинке. Для начального примера пойдет.

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

Углубляемся
Итак, суть всей затеи в том чтобы воспользоваться конструктами D2D и DW для того чтобы что-то там нарисовать. Первый этап – это создать фабрики, причем не одну а целых три – для Direct2D, DirectWrite и WIC соответственно. Тем кто работал в DirectX это будет знакомо:

 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/>

Здесь и далее я буду отделять троеточием декларацию интерфейсов от их инициализации дабы показать что декларации эти выполняются в любом случае, а инициализация – только если предыдущие шаги были правильно выполнены. Именно для этого и нужны постоянные проверки в стиле if (SUCCEEDED(hr)).

Самое время использовать фабрику WIC для того чтобы создать Bitmap на который мы будем рисовать. Позднее нам придется скопировать все данные из этого битмапа в наш, дотнетный – но только после того, как мы нарисуем все, что хотим.

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

Инициализоровав битмап (я потратил уйму времени прежде чем нашел тот PixelFormat который работает), нужно еще найти какой-нибудь объект, который будет на этом битмапе рисовать. В отличии от GDI+, где «рисованием» занимался класс Graphics, в DirectDraw рисовать умеет ID2D1RenderTarget.

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

Именно этот объект будет использован нами для рисования. Естественно, что собственно картинка будет храниться в pWICBitmap который мы создали ранее, а наш RenderTarget – это лишь промежуточный объект, который позволяет делать разные реализации рисования в зависимости от существующих ресурсов.

Текстовые манипуляции
Форматирование текста реализует интерфейс IDWriteTextFormat, конкретный инстанс которого нам предстоит создать. В примере ниже, мы используем дефолтные опции, а потом просим текст отображаться в левой верхней части битмапа.

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/>

В отличии от DirectDraw, DirectWrite без проблем работает не только с TrueType, но и с OpenType шрифтами. Проблемы когда вы знаете что шрифт существует но система исполользует MS Sans Serif больше нет.

Прежде чем нарисовать текст, нужно создать объект типа Brush (ну прям как в GDI+, не так ли?) который будет определять «кисть», которой будет нарисован наш текст.

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

Финишная прямая
У нас все готово. Смело используем наш render target для отрисовки текста. Точно так же как и в DirectX, процесс рисования происходит между Begin- и End-директивами:

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/>

Теперь, когда текст отрисован, можно скопировать его в наш System.Drawing.Bitmap

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

А дальше нужно просто «отпустить» все интерфейсы, и возвращаться поскорее в мир управляемого кода.

Заключение
Несмотря на то, что интерфейсы D2D пока остаются доступными только для С++ программистов, взаимодействие с .Net оказалось не таким сложным как я ожидал. Поэтому не бойтесь экспериментировать. Оставляю вас с примером текста, сгенерированного с использованием вышеописанного подхода:

Direct2D - это круто!
Теги:
Хабы:
Всего голосов 16: ↑11 и ↓5+6
Комментарии15

Публикации

Работа

Программист C++
97 вакансий
QT разработчик
8 вакансий
.NET разработчик
43 вакансии

Ближайшие события