W tym tutorialu zakładam, że znasz podstawy C++. Powinieneś wiedzieć na przykład, co to jest klasa, obiekt, metoda statyczna, destruktor. Znajomość szablonów pozwoli ci w pełni wykorzystać możliwości biblioteki, ale nie jest ona niezbędna.
Przygotowanie środowiska
Po ściągnięciu paczki, należy jej zawartość rozpakować w dowolnym katalogu na dysku, np. C:\M2D. Następnie utworzyć nowy projekt C++ w naszym ulubionym środowisku (np. Visual Studio) i zdefiniować ścieżki dostępu do bibliotek M2D.lib i M2Dd.lib oraz pliku nagłówkowego M2D.hpp. Nie jest ważne, czy utworzymy projekt windowsowy czy konsolowy. Jak komu wygodniej.
Podstawowe informacje
Zanim napiszesz pierwszy program, powinieneś wiedzieć o paru rzeczach, które pomogą ci w używaniu biblioteki.
M2D jest napisana w C++ i czerpie z jego możliwości pełnymi garściami. Oznacza to obiektowość, która ułatwia pisanie kodu i połapanie się w nim. I tak mamy na przykład klasy reprezentujące okna (RenderWindow), obrazki (Image), czcionki (Font), itd. Powrzechnie jest używany wzorzec RAII, czyli w przypadku obiektów C++ są one inicjalizowane w momencie deklaracji w konstruktorze. Dzięki temu narażamy się na mniej głupich błędów, a kod jest czytelniejszy. M2D wykorzystuje też szablony, dzięki czemu jest bardzo elastyczna bez straty szybkości. Z drugiej strony interfejs jest tak napisany, żeby początkujący użytkownik nie musiał się tym za bardzo przejmować. Nawet nie musi wiedzieć, co to jest szablon. Jako typ tekstowy wykorzystywany jest std::wstring z STL. Tak, na początku jest w. Jest to więc tekst Unicode, dzięki czemu bez problemu obsługuje polskie, rosyjskie i nawet chińskie znaki. Typy enumerowane są zrobione trochę inaczej, niż zwykle. Każdy enum jest zawarty w pewnej przestrzeni nazw. Dzięki temu np. przy wybieraniu niskiej jakości skalowania obrazka zamiast zagadkowego ISQ_Low, piszemy zrozumiałe ImageScalingQuality::Low.
Premultiplied alpha
W M2D używamy często klasy Color, reprezentującej kolor 32-bitowy z kanałem alfa, czyli współczynnikiem przezroczystości. Należy tu uważać z kanałem alfa, gdyż zachowuje się on trochę inaczej niż w znanych programach graficznych jak Photoshop i Gimp. M2D stosuje tzw. premultiplied alpha. Aby zamienić zwykły kolor RGBA na premultiplied alpha RGBA, należy przemnożyć każdy ze współczyników RGB ze zwykłego koloru przez jego wartość A i podzielić przez 255. Np. jeśli chcemy otrzymać kolor zielony półprezroczysty w Photoshopie jego współczynniki będą miały wartość (R=0,G=255,B=0,A=127). Natomiast w M2D jego odpowiednik to (R=0*127/255,G=255*127/255,B*127/255,A=127) = (R=0,G=127,B=0,A=127). Z tego wynika, że mamy tylko jeden "kolor" przezroczysty (R=0,G=0,B=0,A=0). Skoro i tak go nie widać, to po co więcej?;). Może się to wydawać dziwne, ale dzięki temu renderowanie grafiki jest szybsze i możemy uzyskać ciekawe efekty jeśli jakieś współczynniki z RGB będą miały wyższą wartość niż współczynnik A. Więcej o tej metodzie można poczytać (niestety tylko po angielsku) np. tu. Co wązne dla nieprzezroczystych kolorów (dla A=255) wszsytko pozostaje po staremu. Wczytywane i zapisywane obrazki są automatycznie konwertowane, więc nie muisz tego robić sam
Tworzenie okna i główna pętla
Poniższy program wyświetla puste okno. W pętli czekamy, aż użytkownik zamknie okno (np. kilkając na krzyżyk w prawym górnym rogu) i wtedy wychodzimy z programu. Jeśli chcemy, żeby nasz program był interaktywny i mógł reagować na poczynania użytkownika (a przeważnie chcemy;) ), to musimy mieć główną pętlę. W niej odbywa się na przemian obsługa zdarzeń (np. zamknięcie okna) i rysowanie w oknie.
Do odbierania zdarzeń wysyłanych przez system operacyjny do okna służy metoda void PollEvents(). Zapisuje ona odebrane zdarzenia w wewnętrznej kolejce zdarzeń okna. Stąd możemy je po kolei wyciągać metodą Event PopEvent(). W kadym przebiegu głównej pętli wyciągamy zdarzenia z wewnętrznej kolejki okna dopóki nie jest ona pusta, co sprawdzamy metodą bool IsEventQueueEmpty().
Następnie odbywa się rysowanie. W poniższym przykładzie akurat nic nie rysujemy, ale zapamiętaj żę żeby coś zobaczyć w oknie trzeba je odświeżyć metodą void Redraw().
/*>/* Musimy najpierw dołączyć poniższy nagłówek. */ #include <M2D.hpp> /* Wszystkie funkcje biblioteki są właśnie w tej przestrzeni nazw. */ using namespace M2D; /* Jeśli stworzymy projekt windowsowy, to zamiast int main(), powinno tu być int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) i oczywiście należy dołączyć nagłówek Windows.h. */ int main() { /* Inicjalizujemy M2D zanim zaczniemy z niej korzystać. */ Init(); /* Tworzymy okno, w którym będziemy wyświetlać grafikę. Jako argumenty konstruktora podajemy rozmiar okna w pikselach (tu 800x600) i tytuł okna w unikodzie (stąd to L na początku). Ostatni argument mówi, czy chcemy stworzyć okienko pełnoekranowe. Na razie wystarczy nam zwykłe okienko, więc podajemy false. */ RenderWindow rw(Point(800,600),L"Przykładowy program M2D",false); bool quit=false; while (!quit) //główna pętla { /* Odbramy zdarzenia z systemu (takie jak wciśnięcia klawiszy), które miały ostatnio miejsce i zapisujemy je w wewnętrznej kolejce okna. */ rw.PollEvents(); /* W poniższej pętli obsługujemy zdarzenia z wewnętrznej kolejki okna wstawione tam wcześniej. Metoda bool RenderWindow::IsEventQueueEmpty() sprawdza, czy zostały w niej jeszcze jakieś zdarzenia. */ while (!rw.IsEventQueueEmpty()) { /* Pobieramy pierwsze w kolejce zdarzenie umieszczone tam wcześniej przez metodę void RenderWindow::PollEvents(). */ Event e=rw.PopEvent(); /* Jeśli zdarzenie polegało na zamknięciu okna, to ustawiamy quit na true, co spowoduje opuszczenie pętli głównej i zakończenie programu. */ if (e.Type==EventType::WindowClose) quit=true; } /* Żeby coś zobaczyć w oknie, to musimy go odrysować. Na razie nic w nim nie rysujemy, więc zobaczymy tylko pusty, czarny ekran. */ rw.Redraw(); } /* Przed zakończeniem programu musimy tą metodą posprzątać po M2D. */ Release(); return 0; }
Pierwsza grafika
Następny program wczytuje z pliku obrazek. Pamiętaj, że ten plik musi istnieć i program musi umieć go znaleźć. M2D może wczytywać większość popularnych formatów plików graficznych. Następnie obrazek ten i zielone koło są wyświetlane na ekranie. To tylko przykład. M2D zawiera wiele innych funkcji rysujących np. linie, prostokąty, skalowane obrazki, czy części obrazków. Można je podejrzeć w nagłówkowym M2D.hpp w klasie Image. Wszystkie zaczynają się na Put... Żeby móc rysować po ekranie, w klasie RenderWindow znajduje się pole Screen. Jest ono obiektem klasy Image. Cokolwiek w tym obrazku narysujemy, zostanie wyświetlone na ekranie (oczywiście po wywołaniu metody Redraw()). Zauważ, że w kodzie przy deklaracji obiektu klasy Image widać znaki <>. Klasa Image jest tak naprawdę szablonem, którego argumentem jest struktura reprezentująca piksel obrazka. Na razie nie musisz się tym przejmować. Zostanie to wyjaśnione w dalszej częsci tutoriala.
#include <M2D.hpp> using namespace M2D; int main() { Init(); RenderWindow rw(Point(800,600),L"Przykładowy program M2D",false); /* Tworzymy nowy obrazek. Zostanie on wczytany z pliku obrazek.jpg. */ Image<> image(L"obrazek.jpg"); bool quit=false; while (!quit) { rw.PollEvents(); while (!rw.IsEventQueueEmpty()) { Event e=rw.PopEvent(); if (e.Type==EventType::WindowClose) quit=true; } /* Czyścimy ekran. Zostanie on zamalowany na czarno. */ rw.Screen.Clear(); /* Rysujemy wczytany wczerśniej obrazek na ekranie. Jego lewy górny róg znajdzie się na pozycji (100,50). */ rw.Screen.PutImage(image,Point(100,50)); /* Rysujemy też koło o środku w punkcie (550,450), promieniu długości 100 pikseli zielonym kolorze. */ rw.Screen.PutFillCircle(Point(550,450),100,Color::Green); /* Odświeżamy ekran, dzięki czemu ujrzymy na nim nasz obrazek i koło. */ rw.Redraw(); } Release(); return 0; }
Mysz i klawiatura
Ten przykład pokazuje, jak obsługiwać te dwa najbardziej podstawowe urządzenia wejścia. Najczęściej będziemy do tego używać zdarzeń podobnie jak przy zamykaniu okna. W poniższym programie kliknięcie lewym przyciskiem myszy gdzieś na ekranie spowoduje wyświetlenie tym miejscu obrazka. Ponowinie pamiętaj, że obrazek musi faktycznie istnieć we wskazanym miejscu. Poza zdarzeniami istnieje jeszcze jeden sposób obsługi myszy i klawiatury. Możemy bezpośrednio sprawdzać,. czy dany klawisz lub przycisk jest wciśnięty. Służą do tego odpowiednio metody bool KeyPressed(Key::EnumType k) i bool MouseButtonPressed(MouseButton::EnumType b) klasy RenderWindow. Zwracają one true, jęśli klawisz/przycisk jest właśnie wciśnięty (a dokładniej, czy był wciśnięty w momencie ostatniego wywołania metody void PollEvents()).
#include <M2D.hpp> using namespace M2D; int main() { Init(); RenderWindow rw(Point(800,600),L"Przykładowy program M2D",false); Image<> image(L"obrazek.jpg"); bool quit=false; Point imagePosition; //Pozycja, na której zostanie narysowany obrazek. while (!quit) { rw.PollEvents(); while (!rw.IsEventQueueEmpty()) { Event e=rw.PopEvent(); if (e.Type==EventType::WindowClose) quit=true; /* Umożliwiamy też wyjście pry pomocy klawisza Esc. Jeśli typem zdarzenia było wciśnięcie klawisza (e.Type==EventType::KeyDown) i tym klawiszem było Esc (e.Key==Key::Escape), to ustawiamy zmienną quit na true. */ if (e.Type==EventType::KeyDown&&e.Key==Key::Escape) quit=true; /* Jeśli zdarzeniem było kliknięcie przycisku myszy i w dodatku był to lewy przycisk, to zapisujemy aktualną pozycję kursora (czyli miejsce kliknięcia) do imagePosition. */ if (e.Type==EventType::MouseButtonDown&&e.MouseButton==MouseButton::Left) imagePosition=rw.GetMousePosition(); } rw.Screen.Clear(); /* Rysujemy wczytany wcześniej obrazek na pozycji, w której nastąpiło ostatnie lewe kliknięcie (tą pozycję zapisaliśmy wcześniej). */ rw.Screen.PutImage(image,imagePosition); rw.Redraw(); } Release(); return 0; }
Tekst i jeszcze o klawiaturze
M2D pozwala na wczytywanie popularnych formatów czcionek i wyśiwetlanie tekstu za ich pomocą. Ze względów wydajnościowych czcionki zawsze są praechowywane w postaci bitmap. Z tego powodu przy tworzeniu czcionki należy podać jej docelowy rozmiar (w pikselach). Do przechowywania czcionek służy klasa Font, a do wyświetlania tekstu metoda void PutText(const Font& f,Point p,const std::wstring& text,Color c). Przyjmuje ona jako argumenty kolejno czcionkę, przy pomocy której tekst zostanie wyrenderowany, pozycję początku tekstu, sam tekst i jego kolor. Rysowany tekst jest wygładzany.
Czasem chcemy wykorzystać klawiaturę do wpisywania tekstu. Do tego muzimy wiedzieć, jaki znak odpowiada wciśniętemu klawiszowi. M2D posiada funkcję, dzięki której możemy przekonwertować fizyczny klawisz (typ Key::EnumType) na znak Unicode (wchar_t). Klasa RenderWindow ma specjalnie do tego przeznaczoną metodę wchar_t TranslateKey(Key::EnumType k). Niektórych może dziwić, dlaczego nie jest ona statyczna (bo do czego potrzebne okno przy zwykłym zamienianiu klawiszy na znaki?). Jest tak dlatego, że czasem wciskamy klawisze z przytrzymanym Shiftem, Altem, albo wciśniętym NumLockiem. Klawisz A na klawiaturze może więc oznaczacz zarówno znak 'A', jak i 'a', 'ą' oraz 'Ą'. I to zależnie od układu klawiatury, ustawionego języka itd. Wszystkie potrzebne informacje są zapisane właśnie w klasie RenderWindow.
#include <M2D.hpp> using namespace M2D; int main() { Init(); RenderWindow rw(Point(800,600),L"Przykładowy program M2D",false); /* Wczytujemy z pliku czcionkę. Musimy podać także jej rozmiar, ponieważ czcionki w M2D są przechowywane jako bitmapy, nawet jeśli wczytujemy czcionkę wektorową. */ Font font(L"arial.ttf",16); bool quit=false; std::wstring text(L"Napisz coś: "); //Tu będziemy przechowywać wpisywany tekst. while (!quit) { rw.PollEvents(); while (!rw.IsEventQueueEmpty()) { Event e=rw.PopEvent(); if (e.Type==EventType::WindowClose) quit=true; /* Sprawdzamy, czy wciśnięto jakiś klawisz. */ if (e.Type==EventType::KeyDown) { /* Tłumaczymy klawisz na znak Unicode, jaki on reprezentuje. Zostanie także uwzględniony np. wciśnięty Shift albo NumLock. */ wchar_t c=rw.TranslateKey(e.Key); /* Jeśli dał się prawidłowo przetłumaczyć (zwróć uwagę, że to nie zawsze jest możliwe, bo np. klawisze strzałek nie reprezentują żadnego znaku), to dodajemy ten znak do tekstu. */ if (c!=0) text+=c; } } rw.Screen.Clear(); /* Rysujemy wpisany tekst na wybranej pozycji (pozycja oznacza początek tekstu). Używamy do tego koloru białego i wczytanej wcześniej czcionki. */ rw.Screen.PutText(font,Point(10,20),text,Color::White); rw.Redraw(); } Release(); return 0; }
Dla bardziej zaawansowanych
Do zrobienia później. Będzie tu opis wykorzystania szablonów w M2D i propozycje do czego może się to przydać. Na razie możesz obejrzeć kod źródłowy projektu Demo2. Możesz go ściągnąć tu.