Mgr inż. Witold BUŻANTOWICZ Dr inż. Jacek WARCHULSKI Dr inż. Marcin WARCHULSKI Wojskowa Akademia Techniczna PROGRAMOWANIE GRAFIKI 2D W ŚRODOWISKU EMBARCADERO RAD STUDIO XE4 Z WYKORZYSTANIEM BIBLIOTEKI DirectX Streszczenie: W artykule przedstawiono praktyczne aspekty programowania operacji graficznych w środowisku Embarcadero RAD Studio XE4 z wykorzystaniem biblioteki Microsoft DirectX. 2D GRAPHICS PROGRAMMING USING EMBARCADERO RAD STUDIO XE4 AND MICROSOFT DirectX LIBRARY Abstract: This paper presents the practical aspects of 2D graphics programming using Embarcadero RAD Studio XE4 environment and Microsoft DirectX library. Słowa kluczowe: programowanie grafiki dwuwymiarowej, DirectX, RAD Keywords: 2D graphics programming, DirectX, RAD 1. WPROWADZENIE Od kilkunastu lat w środowiskach C++ Builder i Delphi dostarczanych przez Embarcadero (wcześniej Borland i CodeGear) dostępny jest zbiór klas VCL, stanowiących opakowanie dla systemowego interfejsu urządzeń graficznych GDI (ang. Graphics Device Interface). Funkcje GDI tworzą warstwę pośrednią między aplikacją wywołującą a sterownikami urządzeń graficznych. Programowanie grafiki poprzez interfejs GDI i udostępniające go klasy VCL choć popularne i sprawdzone posiada szereg ograniczeń, m.in. nie zapewnia wygładzania czcionek, antyaliasingu kreślonych elementów czy sprzętowej akceleracji obliczeń. Wprowadzenie w środowisku Embarcadero RAD Studio 2010 klasy TDirect2DCanvas udostępniło programistom Delphi i C++ nowe narzędzia programowania grafiki dwuwymiarowej zapewniając możliwość przystępnej implementacji operacji graficznych z wykorzystaniem interfejsu Direct2D. Direct2D to wysokopoziomowe API (ang. Application Programming Interface), zapewniające wsparcie programowania aplikacji używających grafiki dwuwymiarowej i wektorowej. Jest dostępne jako część biblioteki Microsoft DirectX (od wersji 11) i docelowo zastąpić ma starszy interfejs DirectDraw. Direct2D wspiera sprzętową akcelerację grafiki dwuwymiarowej, zapewniając odpowiednią jakość i szybkość realizacji operacji graficznych, umożliwiając m.in.: renderowanie tekstu, antyaliasing prymitywów, rysowanie oraz wypełnianie krzywych, figur i bitmap, renderowanie do warstw pośrednich, a także inne zaawansowane operacje na figurach (łączenie, wyznaczanie części wspólnych, rozszerzanie, obramowanie, itp.). 69
Celem niniejszego artykułu jest prezentacja możliwości zastosowania interfejsu Direct2D biblioteki Microsoft DirectX w aplikacjach tworzonych w zintegrowanym środowisku programistycznym Embarcadero RAD Studio XE4. Poniżej przedstawiono kompletny kod źródłowy przykładowego programu, wykorzystującego interfejs Direct2D do realizacji prostej animacji. Zastosowane funkcje oraz mechanizmy ich użycia mogą stanowić podstawę utworzenia własnego, bardziej rozbudowanego projektu. 2. PRZYKŁAD PRAKTYCZNY Zasada działania prezentowanej w niniejszej pracy aplikacji jest następująca: poruszenie wskaźnikiem myszy w obszarze umieszczonego na formularzu komponentu CanvasPanel klasy TPanel skutkuje wygenerowaniem w punkcie przebywania kursora figury koła o zadanej średnicy. Kolejne współrzędne punktów ścieżki kreślonej przemieszczanym ponad obrysem kontrolki wskaźnikiem dodawane są do pamięci, przechowywane w klasycznym buforze typu FIFO (ang. first in first out) i wyświetlane na ekranie w postaci kół, których średnice zależą od bieżącego położenia próbek w rejestrze. Bufor umożliwia zapamiętanie do stu kolejnych współrzędnych punktów chwilowych położeń kursora: TPoint p[100]; Program napisano w języku C++, może być on jednak w prosty sposób zaadaptowany do projektu realizowanego w środowisku Delphi po uwzględnieniu różnic składniowych. Po uruchomieniu środowiska Embarcadero RAD Studio XE4 i utworzeniu nowego projektu aplikacji (za pomocą sekwencji poleceń: File New VCL Forms Application), w pierwszej kolejności należy przejść do edycji pliku nagłówkowego tworzonego formularza (np. unit1.hpp) i w sekcji include umieścić wpis: #include <direct2d.hpp> zaś w sekcji private definicji formularza wskaźnik do obiektu klasy TDirect2DCanvas: class TForm1 : public TForm //... private: TDirect2DCanvas *FD2DCanvas; //... ; Istotną czynnością podczas pracy z obiektami klasy TDirect2DCanvas jest w przeciwieństwie do obiektów tworzonych na bazie klasy TCanvas obsługi interfejsu GDI upewnienie się, czy biblioteka Microsoft DirectX została zainstalowana w systemie użytkownika i czy funkcjonuje ona poprawnie. Sprawdzenia takiego najlepiej dokonać podczas uruchamiania aplikacji, np. za pomocą następującego ciągu poleceń: fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) // blok obsługi wyjątków try...catch() try // nowy obiekt klasy TDirect2DCanvas FD2DCanvas = new TDirect2DCanvas(CanvasPanel >Handle); 70
// jeżeli DirectX nie jest obsługiwany... if(!fd2dcanvas >Supported()) // komunikat: należy zainstalować bibliotekę DirectX w wersji min. 11 Application >MessageBoxW(L"Direct2D nie jest obsługiwany.", L"earthworm direct2d demo application", MB_OK MB_ICONHAND); // zwolnienie pamięci i zerowanie wskaźnika delete FD2DCanvas; FD2DCanvas = NULL; // zakończenie pracy aplikacji exit(exit_failure); // ustawienie flagi antyaliasingu prymitywów FD2DCanvas >RenderTarget >SetAntialiasMode(D2D1_ANTIALIAS_MODE_FORCE_DWORD); catch(...) // komunikat: przechwycono inny wyjątek (np. błędna inicjalizacja) Application >MessageBoxW(L"Wystąpił nieoczekiwany błąd.", L"earthworm direct2d demo application", MB_OK MB_ICONHAND); // zwolnienie pamięci i zerowanie wskaźnika delete FD2DCanvas; FD2DCanvas = NULL; // zakończenie pracy aplikacji exit(exit_failure); Obszar (ang. canvas płótno), na którym rysowane są obiekty, określany jest w terminologii systemów rodziny Microsoft Windows mianem kontekstu urządzenia (ang. device context). Po uzyskaniu od systemu operacyjnego aktualnego uchwytu takiego kontekstu (ang. device context handle) możliwa staje się realizacja operacji graficznych w obszarze roboczym lub ramce okna aplikacji, jak również w innych miejscach, takich jak np. pulpit, pamięć, drukarka czy inne urządzenie wyjściowe. W opisywanym przypadku uchwyt kontekstu urządzenia przekazywany jest jako parametr konstruktora obiektu klasy TDirect2DCanvas: FD2DCanvas = new TDirect2DCanvas(CanvasPanel >Handle); Każdy obiekt utworzony dynamicznie (za pomocą operatora new) musi zostać po wykonaniu przypisanego mu zadania usunięty z pamięci operatorem delete. Zaniedbanie tego obowiązku prowadzi do zjawiska niezrównoważonej konsumpcji pamięci przez aplikację (ang. memory leakage), co może zachwiać gospodarką pamięciową systemu operacyjnego. W celu zabezpieczenia programu przed niepożądanymi wyciekami należy w następujący sposób oprogramować zdarzenie OnFormDestroy formularza głównego: void fastcall TForm1::FormDestroy(TObject *Sender) if(!fd2dcanvas) return; // jeżeli obiekt nie istnieje zakończ zadanie delete FD2DCanvas; // usuń obiekt z pamięci FD2DCanvas = NULL; // zeruj wskaźnik Procedury rysowania na płótnie umieszcza się zwykle w funkcji obsługi zdarzenia OnPaint klasy TForm, co pozwala na bieżąco aktualizować zawartość formularza. W tym przypadku jednak polecenia realizujące główne zadania graficzne programu zaimplementowano w funkcji obsługi zdarzenia OnMouseMove komponentu CanvasPanel. Rozwiązanie takie powoduje wymuszanie odrysowania zawartości sceny każdorazowo w sytuacji, gdy 71
użytkownik przemieści kursor nad obszarem kontrolki. Nie są to bynajmniej jedyne możliwe rozwiązania przedstawiony niżej blok kodu umieścić można przykładowo w funkcji OnIdle (wywoływanej systemowo w momencie bezczynności aplikacji) albo w zdarzeniu OnTimer komponentu TTimer. void fastcall TForm1::CanvasPanelMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) int i,fi; // zmienne pomocnicze for(i=0; i<99; i++) // implementacja bufora FIFO p[i].x = p[i+1].x; // przesunięcie elementów kolejki współrzędna x p[i].y = p[i+1].y; // przesunięcie elementów kolejki współrzędna y p[99].x = X; // aktualna współrzędna x punktu położenia kursora p[99].y = Y; // aktualna współrzędna y punktu położenia kursora // jeżeli obiekt nie istnieje zakończ zadanie if(!fd2dcanvas) return; FD2DCanvas >BeginDraw(); // rozpoczęcie rysowania // tło obszaru rysowania FD2DCanvas >Brush >Color = clwhite; // kolor wypełnienia: biały FD2DCanvas >Brush >Style = bssolid; // styl wypełnienia: pełne FD2DCanvas >Pen >Color = clwhite; // kolor konturu: biały FD2DCanvas >Rectangle(0, 0, // wypełnienie tła CanvasPanel >Width, // prostokątem w kolorze białym CanvasPanel >Height); // figury FD2DCanvas >Brush >Color = clyellow; // kolor wypełnienia: żółty FD2DCanvas >Brush >Style = bssolid; // styl wypełnienia: pełny FD2DCanvas >Pen >Color = clgray; // kolor konturu: szary FD2DCanvas >Pen >Width = 2; // szerokość linii: 2pt // pętla kreślenie elementów na podstawie zawartości bufora FIFO for(i=0; i<100; i++) if(i<50) fi=i; else fi=50 (i 50); FD2DCanvas >Ellipse(p[i].x fi,p[i].y fi,p[i].x+fi,p[i].y+fi); // koniec rysowania uaktualnienie sceny FD2DCanvas >EndDraw(); Polecenia rysowania zawarto pomiędzy wywołaniami funkcji BeginDraw i EndDraw. Należy zwrócić uwagę na liczne podobieństwa ich użycia w odniesieniu do obiektów klasy TCanvas. Zarówno TCanvas jak i TDirect2DCanvas dziedziczą po klasie TCustomCanvas, która definiuje ich funkcjonalność (w tym powiązania z klasami TBrush, TPen, TColor, TFont), stąd pełna analogia zasad stosowania. Zasadniczą różnicę stanowi jednak fakt, że w przypadku obiektów TDirect2DCanvas wszystkie czynności związane z renderowaniem grafiki wykonywane są przez procesor karty graficznej GPU, co umożliwia redukcję obciążenia CPU, a w konsekwencji wykorzystanie zaoszczędzonych zasobów do innych zadań. Realizacja zadań programowania grafiki w oparciu o interfejs Direct2D niesie jednakże ze sobą pewne ograniczenia, m.in. nie jest możliwe uruchamianie korzystających z niego programów na starszych wersjach systemu Microsoft Windows (np. XP). 72
Rys. 1. Statyczne, utrzymane w odcieniach szarości ilustracje nie oddają ani barw, ani dynamicznych możliwości prezentowanego rozwiązania, co powinno stanowić wystarczającą zachętę do własnych, twórczych eksperymentów 73
3. PODSUMOWANIE Zaprezentowany w artykule przykład użycia klasy TDirect2DCanvas udowadnia, że wykorzystanie elementów składowych biblioteki Microsoft DirectX w projektach tworzonych w środowisku Embarcadero RAD Studio nie jest skomplikowane, zaś pod względem semantyki nie odbiega od konwencji stosowanych w popularnej od lat klasie TCanvas. Co więcej, występujące analogie umożliwiają płynną migrację od projektów zrealizowanych w oparciu o mechanizmy interfejsu GDI do aplikacji, w których główne funkcje graficzne generowane są przez API Direct2D. LITERATURA [1] http://msdn.microsoft.com [2] http://www.embarcadero.com 74