Wymagane jest zapoznanie się z następującymi podrozdziałami:
P019 - Pierwszy program dla Windows
OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokąta
OL038 - podstawowe algorytmy wypełniania obszarówOL039 - algorytm Smitha
OL040 - praca w środowisku sterowanym zdarzeniami
OL041 - czcionka bitmapowa
OL042 - czcionka wektorowa
OL043 - przyciski poleceń
OL050 - macierze - podstawowe operacje na macierzach
OL051 - przekształcenia na płaszczyźnie
OL052 - algorytm wypełniania wielokątów
UWAGA!!!
Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.
Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział. Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL011.
Artykuł nie jest już rozwijany
Sposoby rysowania okręgu i elipsy poznaliśmy już w rozdziale OL036. Polegają one na wyznaczeniu n punktów leżących na obwodzie okręgu czy elipsy i połączeniu ich odcinkami linii prostej, przy czym ostatni punkt łączymy z pierwszym (lub generujemy n + 1 punktów), aby otrzymać figurę zamkniętą. Współrzędne kolejnych punktów możemy wyznaczyć przy pomocy funkcji trygonometrycznych:
Dla okręgu![]() Dla elipsy ![]() |
n - liczba wierzchołków i - numer wierzchołka xi, yi - współrzędne i-tego wierzchołka xs, ys - współrzędne środka okręgu lub elipsy r - promień okręgu rx - promień elipsy na osi OX ry - promień elipsy na osi OY |
![]() |
Program rysowania okręgu i elipsy może wyglądać następująco (utwórz nowy projekt SDL, dołącz do niego pliki SDL_gfx.h i SDL_gfx.cpp - opis w podsumowaniu SDL011 oraz SDL_gui.h i SDL_gui.cpp - opis w podsumowaniu GUI004, a na koniec nie zapomnij o skopiowaniu czcionki vecprop9x12.fnt do katalogu projektowego):
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P042 - Okręgi i elipsy //----------------------- #include <SDL/SDL_gfx.h> #include <SDL/SDL_gui.h> #include <cmath> const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach const int N = 100; // liczba punktów na obrysie okręgu/elipsy SDL_Surface * screen; gfxFont * font = gfxOpenFont("vecprop9x12.fnt"); // Funkcja obsługująca przyciski poleceń //-------------------------------------- void fnb(gfxGUIObject * sender) { Sint32 xs, ys, rx, ry, x, y; xs = screen->w >> 1; ys = screen->h >> 1; switch(sender->tag) { case 0: rx = ry = ys - 16 - font->h; break; case 1: rx = xs - 12; ry = ys - 16 - font->h; break; case 2: exit(0); } // rysujemy okrąg lub elipsę SDL_Rect r; r.x = r.y = 0; r.w = screen->w; r.h = screen->h - 16 - font->h; if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, &r, 0); for(int i = 0; i <= N; i++) { x = xs + rx * cos(6.28318 * i / N); y = ys + ry * sin(6.28318 * i / N); if(i) gfxLineTo(screen, x, y, 0xffff00); else gfxMoveTo(x, y); } gfxLine(screen, xs - 5, ys, xs + 5, ys, 0xff0000); gfxLine(screen, xs, ys - 5, xs, ys + 5, 0xff0000); if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, r.x, r.y, r.w, r.h); } //*********************** // *** Program główny *** //*********************** int main(int argc, char * argv[]) { // tablica tekstów opisujących przyciski char * t[] = {"Okrąg", "Elipsa", "Zakończ"}; // tablica 3 wskaźników do przycisków gfxButton * b[3]; if(SDL_Init(SDL_INIT_VIDEO)) exit(-1); atexit(SDL_Quit); if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1); // tworzymy przyciski akcji SDL_Rect r; r.x = r.w = 0; r.h = font->h + 8; r.y = screen->h - r.h - 4; for(int i = 0; i < 3; i++) r.w += 20 + gfxTextLength(font, t[i]); r.x = (screen->w - r.w - 4) >> 1; for(int i = 0; i < 3; i++) { r.w = 16 + gfxTextLength(font, t[i]); b[i] = new gfxButton(i, true, screen, font, &r, t[i], fnb); r.x += r.w + 4; } // Obsługujemy zdarzenia SDL_Event event; bool running = true; while(running) { while(SDL_WaitEvent(&event)) { bool eventfree; for(int i = 0; i < 3; i++) if(!(eventfree = b[i]->DoEvents(&event))) break; if(eventfree && (event.type == SDL_QUIT)) { running = false; break; } } } // zamykamy czcionkę gfxCloseFont(font); // Usuwamy przyciski for(int i = 0; i < 3; i++) delete b[i]; return 0; }
![]() |
![]() |
Przyjrzyj się dokładnie rysowanym figurom. Nie są one specjalnie ładne. Spowodowane to jest zaokrągleniami wyliczonych wartości x i y punktów okręgu oraz elipsy. Opiszemy teraz znacznie lepszy algorytm rysowania okręgu oraz elipsy, który nie wymaga stosowania funkcji trygonometrycznych i działa na liczbach całkowitych - algorytm Bresenhama.
Okrąg jest figurą symetryczną. Fakt ten możemy wykorzystać do ograniczenia liczby wyznaczanych punktów leżących tylko w 1/8 obwodu:
Pozostałe punkty otrzymamy przez prostą symetrię względem osi OX i OY - zakładamy, iż środek okręgu znajduje się w środku układu współrzędnych. Jeśli jest inaczej, to wyznaczone punkty należy poddać translacji o wektor (xs,ys) określający współrzędne środka:
P1 = (x + xs, y + ys), x, y - współrzędne okręgu o środku w punkcie (0,0)
P2 = (y + xs, x + ys) xs, ys - współrzędne środka
P3 = (y + xs, -x + ys)
P4 = (x + xs, -y + ys)
P5 = (-x + xs, -y + ys)
P6 = (-y + xs, -x + ys)
P7 = (-y + xs, x + ys)
P8 = (-x + xs, y + ys)Nasz algorytm będzie wyznaczał kolejne współrzędne x,y, które wg powyższych wzorów przekształcimy w celu otrzymania współrzędnych docelowych punktów P1...P8. Wyjdziemy z podstawowego równania okręgu:
x2 + y2 - r2 = 0
gdzie x,y - współrzędne punktu na obrysie okręgu, a r - promień okręgu.Rysowanie okręgu rozpoczynamy w punkcie (0,r). Kolejne punkty będziemy wyznaczali poruszając się w kierunku zgodnym z ruchem wskazówek zegara aż do osiągnięcia pozycji, dla której x = y.
W rozważanej 1/8 obwodu okręgu z piksela P przechodzimy (zapalamy) do jednego z dwóch pikseli P1 lub P2. Oznaczmy wyrażenie błędu przez:
e = x2 + y2 - r2
Zachodzą następujące zależności:
e < 0 , punkt (x,y) leży wewnątrz okręgu e = 0 , punkt (x,y) leży dokładnie na obwodzie okręgu e > 0 , punkt (x,y) leży na zewnątrz okręgu Dla każdego z punktów P1 i P2 wyznaczamy wyrażenie błędu:
P1 = (x + 1, y)
e1 = e + (x + 1)2 + y2 - r2, r2 wyliczamy z równania okręgu: r2 = x2 + y2 i wstawiamy do wzoru na e1.
e1 = e + (x + 1)2 + y2 - x2 - y2
e1 = e + x2 + 2x + 1 + y2 - x2 - y2, upraszczamy wzór i otrzymujemy:
e1 = e + 2x + 1
P2 = (x + 1, y - 1)
e2 = e + (x + 1)2 + (y - 1)2 - r2
e2 = e + (x + 1)2 + (y - 1)2 - x2 - y2
e2 = e + x2 + 2x + 1 + y2 - 2y + 1 - x2 - y2
e2 = e + 2x - 2y + 2
e2 = e1 - 2y + 1 Gdy wyznaczymy błędy dla obu punktów, obliczamy odchyłkę e12:
e12 = e1 + e2
e12 = e + 2x + 1 + e + 2x - 2y + 2
e12 = 2e + 4x - 2y + 3 Badając znak odchyłki wybieramy:
e12 < 0 - punkt P1, czyli x → x + 1, y → y
e12 ≥ 0 - punkt P2, czyli x → x + 1, y → y - 1
Powód takiego postępowania wyjaśnia poniższa tabelka:
e1 e2 e12 = e1 + e2 Następny
pikselUwagi < 0 < 0 < 0 P1 = (x + 1, y) P1 i P2 są wewnątrz okręgu, lecz P1 jest bliżej brzegu < 0 ≥ 0 ? ? Ta sytuacja nie może wystąpić, gdyż oznaczałoby to, iż bliższy środkowi okręgu punkt P2 jest na zewnątrz lub na obrysie, a dalszy P1 jest wewnątrz okręgu. ≥ 0 < 0 < 0 P1 = (x + 1, y) Punkt P2 jest wewnątrz okręgu, a P1 na zewnątrz lub na obrysie. Jednakże P1 jest bliżej obrysu lub w takiej samej odległości od niego jak punkt P2 ≥ 0 ≥ 0 ≥ 0 P2 = (x + 1, y - 1) Oba punkty są albo na zewnątrz okręgu, albo na zewnątrz jest P1, a P2 leży na obrysie. W każdym przypadku P2 jest bliżej obrysu. Wartość początkową e przyjmujemy równą 0.
Po wyborze punktu P1 za nowe e przyjmujemy e1, a po wyborze P2 wyrażenie błędu e przyjmuje wartość e2.
Po tych rozważaniach możemy podać pełny algorytm Bresenhama rysowania okręgu:
Wejście
xs, ys - współrzędne środka okręgu r - promień okręgu color - kolor obrysu okręgu Wyjście
Narysowanie na powierzchni graficznej okręgu o środku w punkcie (xs, ys), o promieniu r i w kolorze color
Dane pomocnicze
x, y - współrzędne punktów na obwodzie okręgu o środku w początku układu współrzędnych e - wyrażenie błędu dla narysowanego punktu P = (x, y.) e1 - wyrażenie błędu dla punktu P1 = (x + 1, y) e2 - wyrażenie błędu dla punktu P2 = (x + 1, y - 1) Lista kroków
K01: x ← 0 ; ustalamy współrzędne punktu startowego K02: y ← r K03: e ← 0 ; wstępne wyrażenie błędu K04: Dopóki x ≤ y, wykonuj kroki K05...K19 K05: Zapal piksel (x + xs, y + ys) w kolorze color ; zapalamy 8 pikseli K06: Zapal piksel (y + xs,-x + ys) w kolorze color K07: Zapal piksel (-x + xs,-y + ys) w kolorze color K08: Zapal piksel (-y + xs, x + ys) w kolorze color K09: Zapal piksel (y + xs, x + ys) w kolorze color K10: Zapal piksel (x + xs,-y + ys) w kolorze color K11: Zapal piksel (-y + xs,-x + ys) w kolorze color K12 Zapal piksel (-x + xs, y + ys) w kolorze color K13: e1 ← e + 2x + 1 ; wyliczamy wyrażenie błędu dla P1 K14: e2 ← e1 - 2y + 1 ; wyliczamy wyrażenie błędu dla P2 K15: x ← x + 1 ; współrzędna x jest zawsze zwiększana o 1 K16: Jeśli e1 + e2 < 0, idź do kroku K19 K17: y ← y - 1 ; współrzędną y zmniejszamy tylko wtedy, gdy suma e1 i e2 jest nieujemna K18: e ← e2 i kontynuuj następny obieg pętli K04 ; za nowe e przyjmujemy e2 dla P2 K19: e ← e1 ; za nowe e przyjmujemy e1 dla P1 K20: Zakończ Utwórz nowy projekt SDL. Dołącz do niego pliki SDL_gfx.h i SDL_gfx.cpp (ich opis znajdziesz w podsumowaniu SDL011). Ponieważ będziemy testować utworzone funkcje, dodaj do projektu również pliki interfejsu GUI: SDL_gui.h i SDL_gui.cpp (opis w podsumowaniu GUI004). Nie zapomnij również o przekopiowaniu do katalogu projektowego czcionki wektorowej vecprop9x12.fnt.
Na końcu pliku nagłówkowego SDL_gfx.h dopisz:
void gfxCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color); void gfxClipCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color);Obie funkcje rysują okręgi. Różnica pomiędzy nimi polega na tym, iż gfxCircle() rysuje bez obcinania, ale bardzo szybko - wykorzystuje wskaźniki. Okrąg w całości musi się mieścić na powierzchni graficznej. Druga funkcja gfxClipCircle() do rysowania pikseli wykorzystuje funkcję gfxClipPlot, która pomija piksele leżące poza prostokątem obcinania. Okrąg nie musi się w całości mieścić na powierzchni graficznej - zostanie obcięty do prostokąta s->clip_rect. Wadą tego rozwiązania jest mniejsza szybkość rysowania okręgu w porównaniu do gfxCircle() (lecz wciąż operacja jest wystarczająco szybka dla większości zastosowań).
Parametry funkcji gfxCircle() i gfxClipCircle() są następujące:
s - wskaźnik struktury SDL_Surface. xs ys - współrzędne środka okręgu r - promień okręgu color - kolor obrysu Na końcu pliku SDL_gfx.cpp dopisz poniższy kod:
// Rysuje okrąg // s - wskaźnik struktury SDL_Surface // xs,ys - współrzędne środka // r - promień okręgu // color - kolor obrysu //--------------------------------------- void gfxCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color) { Sint32 x = 0, y = r, wx, wy; Sint32 e = 0, e1, e2; Uint32 w = s->w; Uint32 * p = (Uint32 *)(s->pixels) + ys * w + xs; wx = x * w; wy = y * w; while(x <= y) { * (p + x + wy) = color; * (p + y - wx) = color; * (p - x - wy) = color; * (p - y + wx) = color; * (p + y + wx) = color; * (p + x - wy) = color; * (p - y - wx) = color; * (p - x + wy) = color; e1 = e + (x << 1) + 1; e2 = e1 - (y << 1) + 1; x++; wx += w; if(e1 + e2 >= 0) { e = e2; y--; wy -= w; } else e = e1; } } // Rysuje okrąg z obcinaniem // s - wskaźnik struktury SDL_Surface // xs,ys - współrzędne środka // r - promień okręgu // color - kolor obrysu //--------------------------------------- void gfxClipCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color) { Sint32 x = 0, y = r; Sint32 e = 0, e1, e2; while(x <= y) { gfxClipPlot(s, x + xs, y + ys, color); gfxClipPlot(s, y + xs,-x + ys, color); gfxClipPlot(s,-x + xs,-y + ys, color); gfxClipPlot(s,-y + xs, x + ys, color); gfxClipPlot(s, y + xs, x + ys, color); gfxClipPlot(s, x + xs,-y + ys, color); gfxClipPlot(s,-y + xs,-x + ys, color); gfxClipPlot(s,-x + xs, y + ys, color); e1 = e + (x << 1) + 1; e2 = e1 - (y << 1) + 1; x++; if(e1 + e2 >= 0) { e = e2; y--; } else e = e1; } }
Operacja wypełniania okręgu algorytmem Bresenhama jest bardzo podobna do rysowania okręgu - znajdujemy początki i końce linii poziomych, które sukcesywnie zamalowują wnętrze okręgu. Oczywiście wykorzystujemy symetrię tej figury:
- Linie zewnętrzne górne i dolne rysujemy tuż przed zmianą współrzędnej y - linie wypełniające koło przebiegają następująco:
- górna od (-x,y) do (x,y)
- dolna od (-x,-y) d0 (x -y)- Linie wewnętrzne górne i dolne rysujemy tuż przed zmianą współrzędnej x:
- górna od (-y,x) do (y,x)
- dolna od (-y,-x) do (y,-x)
Linię dolną rysujemy tylko dla x > 0. Obie linie wewnętrzne rysujemy tylko dla x ≠ y. W przeciwnym razie linie wewnętrzne byłyby rysowane po narysowanych liniach zewnętrznychWejście
xs, ys - współrzędne środka okręgu r - promień okręgu color - kolor wypełnienia koła Wyjście
Narysowanie na powierzchni graficznej wypełnionego koła o środku w punkcie (xs, ys), o promieniu r i w kolorze color
Dane pomocnicze
x, y - współrzędne punktów na obwodzie okręgu o środku w początku układu współrzędnych e - wyrażenie błędu dla narysowanego punktu P = (x, y.) e1 - wyrażenie błędu dla punktu P1 = (x + 1, y) e2 - wyrażenie błędu dla punktu P2 = (x + 1, y - 1) Lista kroków
K01: x ← 0 ; ustalamy współrzędne punktu startowego K02: y ← r K03: e ← 0 ; wstępne wyrażenie błędu K04: Dopóki x ≤ y, wykonuj kroki K05...K16 K05: Rysuj linię od (-y,x) do (y,x) w kolorze color ; linia wewnętrzna górna K06: Rysuj linię od (-y,-x) do (y,-x) w kolorze color ; linia wewnętrzna dolna K07: e1 ← e + 2x + 1 ; wyliczamy wyrażenie błędu dla P1 K08: e2 ← e1 - 2y + 1 ; wyliczamy wyrażenie błędu dla P2 K09: Jeśli e1 + e2 < 0, idź do kroku K14 K10: Rysuj linię od (-x,y) do (x,y) w kolorze color ; rysujemy linię zewnętrzną górną K11: Rysuj linię od (-x,-y) do (x,-y) w kolorze color ; oraz zewnętrzną dolną K12: y ← y - 1 ; współrzędną y zmniejszamy tylko wtedy, gdy suma e1 i e2 jest nieujemna K13: e ← e2 i idź do kroku K15 ; za nowe e przyjmujemy e2 dla P2 K14: e ← e1 ; za nowe e przyjmujemy e1 dla P1 K15: x ← x + 1 ; x musimy zwiększać na końcu, aby linie zewnętrzne były rysowane prawidłowo K16: Zakończ Na końcu pliku nagłówkowego SDL_gfx.h dopisz:
void gfxFillCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color);Parametry funkcji gfxFillCircle() są następujące:
s - wskaźnik struktury SDL_Surface. xs ys - współrzędne środka koła r - promień koła color - kolor wypełnienia Na końcu pliku SDL_gfx.cpp dopisz poniższy kod:
// Rysuje pełne koło // s - wskaźnik struktury SDL_Surface // xs,ys - współrzędne środka // r - promień okręgu // color - kolor wypełnienia //--------------------------------------- void gfxFillCircle(SDL_Surface * s, Sint32 xs, Sint32 ys, Sint32 r, Uint32 color) { Sint32 x = 0, y = r; Sint32 e = 0, e1, e2; while(x <= y) { gfxClipHLine(s,-y + xs, x + ys, color, (y << 1) + 1); gfxClipHLine(s,-y + xs, -x + ys, color, (y << 1) + 1); e1 = e + (x << 1) + 1; e2 = e1 - (y << 1) + 1; if(e1 + e2 >= 0) { gfxClipHLine(s,-x + xs, y + ys, color, (x << 1) + 1); gfxClipHLine(s,-x + xs, -y + ys, color, (x << 1) + 1); y--; e = e2; } else e = e1; x++; } }Poniższy program testuje nowe funkcje.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P043 - Okręgi i koła //--------------------- #include <SDL/SDL_gfx.h> #include <SDL/SDL_gui.h> const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach SDL_Surface * screen; gfxFont * font = gfxOpenFont("vecprop9x12.fnt"); // Funkcja obsługująca przyciski poleceń //-------------------------------------- void fnb(gfxGUIObject * sender) { Sint32 xs, ys, rx, ry, rr; SDL_Rect r; r.x = r.y = 0; r.w = screen->w; r.h = screen->h - 16 - font->h; if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, &r, 0); xs = screen->w >> 1; ys = (screen->h >> 1) - 8; rr = ys - 12 - font->h; rx = xs - 8; ry = ys - 12 - font->h; switch(sender->tag) { case 0: gfxCircle(screen, xs, ys, rr, 0xffff00); break; case 1: gfxFillCircle(screen, xs, ys, rr, 0x007f00) ;break; case 2: if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); exit(0); } gfxLine(screen, xs - 5, ys, xs + 5, ys, 0xff0000); gfxLine(screen, xs, ys - 5, xs, ys + 5, 0xff0000); if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, r.x, r.y, r.w, r.h); } //*********************** // *** Program główny *** //*********************** int main(int argc, char * argv[]) { // tablica tekstów opisujących przyciski char * t[] = {"Okrąg", "Koło", "Zakończ"}; // tablica 3 wskaźników do przycisków gfxButton * b[3]; if(SDL_Init(SDL_INIT_VIDEO)) exit(-1); atexit(SDL_Quit); if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1); // tworzymy przyciski akcji SDL_Rect r; r.x = r.w = 0; r.h = font->h + 8; r.y = screen->h - r.h - 4; for(int i = 0; i < 3; i++) r.w += 20 + gfxTextLength(font, t[i]); r.x = (screen->w - r.w - 4) >> 1; for(int i = 0; i < 3; i++) { r.w = 16 + gfxTextLength(font, t[i]); b[i] = new gfxButton(i, true, screen, font, &r, t[i], fnb); r.x += r.w + 4; } // Obsługujemy zdarzenia SDL_Event event; bool running = true; while(running) { while(SDL_WaitEvent(&event)) { bool eventfree; for(int i = 0; i < 3; i++) if(!(eventfree = b[i]->DoEvents(&event))) break; if(eventfree && (event.type == SDL_QUIT)) { running = false; break; } } } // zamykamy czcionkę gfxCloseFont(font); // Usuwamy przyciski for(int i = 0; i < 3; i++) delete b[i]; return 0; }
![]() |
![]() |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe