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
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 SDL003.
Artykuł nie jest już rozwijany
Tworząc różnego rodzaju grafikę komputerową bardzo szybko natrafisz na sytuację, gdy rysowane obiekty "wychodzą" poza obszar ekranu. W takim przypadku kontynuowanie rysowania linii bądź punktów prowadzi do błędów graficznych lub błędów dostępu do pamięci - program próbuje wpisywać piksele poza buforem obrazowym, czego oczywiście mu nie wolno robić. Sytuacja ta występuje również wtedy, gdy tworzone rysunki chcesz ograniczyć do rozmiaru prostokątnego okna na swojej powierzchni graficznej.
Rozwiązaniem problemu jest zastosowanie obcinania (ang. clipping) linii lub punktów. W przypadku punktów obcinanie polega po prostu na pomijaniu rysowania punktu, jeśli znajduje się on poza dozwolonym obszarem. Natomiast odcinek jest przycinany do fragmentu leżącego wewnątrz dozwolonego obszaru. Obszar nazywamy prostokątem obcinającym (ang. clipping rectangle). Na poniższym rysunku prostokąt obcinający zaznaczono niebieską linią. Punkty i fragmenty odcinków o kolorze czarnym są rysowane na powierzchni graficznej. Punkty i fragmenty odcinków oznaczone kolorem czerwonym nie będą rysowane, ponieważ znajdują się poza prostokątem obcinającym.
Do definiowania prostokąta obcinania będziemy stosowali strukturę biblioteki SDL:
typedef struct { Sint16 x, y; Uint16 w, h; } SDL_Rect;Pola x i y określają współrzędne lewego górnego narożnika prostokąta. Pola w i h definiują jego szerokość (ang. width) oraz wysokość (ang. height). Na przykład poniższy fragment programu tworzy prostokąt znajdujący się na środku ekranu w odległości 10 pikseli od każdego z brzegów obszaru graficznego:
... SDL_Rect * clip = new SDL_Rect; // tworzymy wskaźnik prostokąta i inicjujemy go clip->x = clip->y = 10; // określamy położenie lewego górnego narożnika prostokąta clip->w = screen->w - 20; // szerokość prostokąta clip->h = screen->h - 20; // wysokość prostokąta ...Prostokąt obcinający umieszczamy w strukturze SDL_Surface naszego ekranu za pomocą wywołania funkcji bibliotecznej:
void SDL_SetClipRect(SDL_Surface * surface, SDL_Rect * clip);Pierwszy parametr wskazuje strukturę SDL_Surface naszego ekranu. Drugi parametr wskazuje prostokąt obcinający. Jeśli drugi parametr ma wartość NULL, to funkcja SDL_SetClipRect() zastosuje cały ekran jako prostokąt obcinający. Pole clip_rect jest inicjowane na rozmiary ekranu przy tworzeniu struktury SDL_Surface. Jeśli zatem chcesz obcinać grafikę tylko do rozmiarów ekranu, nie musisz wywoływać w swoim programie funkcji SDL_SetClipRect(), ale lepiej to zrobić - dla spokojnego sumienia i dla tych, którzy o tym fakcie nie wiedzą.
W naszej bibliotece SDL_gfx utworzymy funkcję gfxClipPlot(), która będzie rysowała punkty obcięte do przekazanego jej w parametrach wywołania prostokąta obcinającego.
UWAGA: Poniższy kod funkcji gfxClipPlot() dopisz do końca pliku SDL_gfx.cpp.
// gfxClipPlot() rysuje piksel 32 bitowy wewnątrz zdefiniowanego // prostokąta obcinającego clip_rect // screen - wskaźnik struktury SDL_Surface // x,y - współrzędne piksela // color - 32 bitowy kod koloru piksela //------------------------------------------------------------------ void gfxClipPlot(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color) { if((x >= screen->clip_rect.x) && (x < screen->clip_rect.x + screen->clip_rect.w) && (y >= screen->clip_rect.y) && (y < screen->clip_rect.y + screen->clip_rect.h)) gfxPlot(screen, x, y, color); }Funkcja gfxClipPlot() sprawdza, czy rysowany piksel znajduje się wewnątrz prostokąta obcinającego, który ustawiamy przez wcześniejsze wywołanie funkcji SDL_SetClipRect(). Będzie tak, jeśli:
x jest w przedziale
< screen->clip_rect.x, screen->clip_rect.x+ screen->clip_rect.w) y jest w przedziale
< screen->clip_rect.y, screen->clip_rect.y+ screen->clip_rect.h) Jeśli współrzędne spełniają warunki, to punkt jest rysowany poprzez wywołanie funkcji gfxPlot(), którą utworzyliśmy w rozdziale OL035. Parametry wywołania funkcji gfxClipPlot() są identyczne jak w funkcji gfxPlot().
UWAGA: Na końcu pliku nagłówkowego SDL_gfx.h dopisz:
void gfxClipPlot(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color);Poniższy program definiuje prostokąt na środku ekranu, rysuje na nim ramkę, pomniejsza go do wnętrza ramki, a następnie generuje 1000000 punktów o pseudolosowych współrzędnych x,y w zakresie od 0 do 1999 i przy pomocy funkcji gfxClipPlot() rysuje je we wnętrzu ramki.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P011 - obcinanie punktów //------------------------- #include <windows.h> #include <SDL/SDL_gfx.h> #include <time.h> // do inicjalizacji generatora pseudolosowego const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach int main(int argc, char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO)) { MessageBox(0, SDL_GetError(), "Błąd inicjalizacji SDL", MB_OK); exit(-1); } atexit(SDL_Quit); SDL_Surface * screen; if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) { MessageBox(0, SDL_GetError(), "Błąd tworzenia bufora obrazowego", MB_OK); exit(-1); } if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) { MessageBox(0, SDL_GetError(), "Błąd blokady bufora obrazowego", MB_OK); exit(-1); } // inicjujemy generator liczb pseudolosowych srand((unsigned)time(NULL)); // definiujemy prostokąt obcinający SDL_Rect * clip = new SDL_Rect; clip->x = screen->w >> 2; // 1/4 szerokości ekranu clip->y = screen->h >> 2; // 1/4 wysokości ekranu clip->w = screen->w >> 1; // 1/2 szerokości ekranu clip->h = screen->h >> 1; // 1/2 wysokości ekranu // rysujemy ramkę gfxRectangle(screen, clip, 0xff0000); // pomniejszamy prostokąt obcinający clip->x++; clip->y++; clip->w -= 2; clip->h -= 2; // ustawiamy prostokąt obcinający w strukturze SDL_Surface SDL_SetClipRect(screen, clip); // rysujemy milion punktów na przypadkowych współrzędnych obcinając je do prostokąta clip for(int i = 0; i < 1000000; i++) gfxClipPlot(screen, rand() % 2000, rand() % 2000, 0xffff00); if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); MessageBox(0, "Kliknij przycisk OK", "Koniec", MB_OK); return 0; }
Obcinanie odcinków poziomych i pionowych
Na początek zdefiniujemy nowe funkcje rysowania linii poziomych i pionowych oraz ramek. Jest to zadanie łatwe, ponieważ linie poziome i pionowe są rysowane w jeden określony sposób. Dla linii poziomej reguły są następujące (przy linii pionowej postępujemy w identyczny sposób).
Załóżmy, iż odcinek poziomy zdefiniowany jest współrzędnymi lewego punktu początkowego - x,y oraz szerokością len (tak będzie on określony w parametrach wywołania funkcji). Prostokąt obcinający definiuje struktura SDL_Rect o nazwie clip. Istnieje 8 możliwych położeń odcinka poziomego, które przedstawiliśmy na rysunku obok (pamiętaj, iż u nas oś OY jest skierowana w dół):
1. Odcinek ponad górną krawędzią prostokąta obcinającego. Sytuacja ta występuje, gdy
y < clip.y.
2. Odcinek przed lewą krawędzią prostokąta:x + len ≤ clip.x.
3. Odcinek za prawą krawędzią prostokąta:x ≥ clip.x + clip.w.
4. Odcinek poniżej dolnej krawędzi prostokąta:y ≥ clip.y + clip.h. W przypadkach 1...4 odcinek nie może być rysowany, zatem warunki te należy w algorytmie wykluczyć w pierwszej kolejności.
5 i 7. Odcinek przecina lewą krawędź prostokąta:
x < clip.x. Zmniejszamy długośćlen = len - (clip.x - x). Zmieniamyx = clip.x.
6 i 7, Odcinek przecina prawą krawędź prostokąta:x + len > clip.x + clip.w. Zmieniamy długośćlen = clip.x + clip.w - x.
8. Odcinek w całości zawiera się we wnętrzu prostokąta obcinającego. Jeśli przejdziemy warunki 1..7, to warunek 8 jest wynikowy i nie musimy go testować.Z podanych rozważań wynika następujący algorytm obcinania odcinka poziomego do prostokąta:
Wejście
x, y - współrzędne ekranowe lewego punktu początkowego kreślonego odcinka len - szerokość odcinka w pikselach clip - struktura SDL_Rect definiująca prostokąt obcinający Wyjście
Rysunek odcinka obciętego do zadanego prostokąta obcinającego clip. Odcinek nie jest rysowany, jeśli znajduje się poza obszarem clip.
Zmienne pomocnicze
xc - współrzędna x punktu leżącego tuż za prawą krawędzią prostokąta obcinającego xp - współrzędna y punktu leżącego tuż za prawym końcem odcinka Lista kroków
K01: xc ← clip.x + clip.w ; obliczamy współrzędną poza prawą krawędzią prostokąta K02: xp ← x + len ; obliczamy współrzędną poza prawym końcem odcinka K03: Jeśli y < clip.y, zakończ ; wykluczamy położenie nr 1 K04: Jeśli xp ≤ clip.x, zakończ ; wykluczamy położenie nr 2 K05: Jeśli x ≥ xc, zakończ ; wykluczamy położenie nr 3 K06: Jeśli y ≥ clip.y + clip.h ; wykluczamy położenie nr 4 K07: Jeśli x ≥ clip.x, idź do kroku K10 ; położenia 5 i 7 K08: len ← len - clip.x + x ; zmniejszamy długość odcinka K09: x ← clip.x ; przesuwamy współrzędną x na lewą krawędź prostokąta K10: Jeśli xp ≤ xc, idź do kroku K12 ; położenia 6 i 7 K11: len ← xc - x ; zmniejszamy długość K12; Rysuj odcinek poziomy x, y, len K13: Zakończ Na podstawie powyższego algorytmu napiszemy funkcje gfxClipHLine(), gfxClipVLine() oraz gfxClipRectangle() i dodamy je do naszej biblioteki SDL_gfx.cpp. Funkcje obcinają grafikę do podanego w parametrach prostokąta clip. Jeśli zamiast wskaźnika przekażemy NULL, to funkcje wykonają obcięcie do bieżącej ramki obrazu.
UWAGA: Poniższy kod funkcji gfxClipHLine(), gfxClipVLine() oraz gfxClipRectangle() dopisz do końca pliku SDL_gfx.cpp.
// gfxClipHLine() rysuje linię poziomą z obcięciem do clip_rect // screen - wskaźnik struktury SDL_Surface // x,y - współrzędne lewego początku lini // color - 32 bitowy kod koloru linii // len - długość linii w pikselach //------------------------------------------------------------------ void gfxClipHLine(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color, Uint32 len) { Sint32 x1c, x2c, y1c, y2c, xp; x1c = screen->clip_rect.x; y1c = screen->clip_rect.y; x2c = x1c + screen->clip_rect.w; y2c = y1c + screen->clip_rect.h; // obcinanie odcinka xp = x + len; if((y >= y1c) && (xp > x1c) && (x < x2c) && (y < y2c)) { if(x < x1c) { len += x - x1c; x = x1c; } if(xp > x2c) len = x2c - x; gfxHLine(screen, x, y, color, len); } } // gfxClipVLine() rysuje linię pionową z obcięciem do prostokąta clip_rect // screen - wskaźnik struktury SDL_Surface // x,y - współrzędne górnego początku lini // color - 32 bitowy kod koloru linii // len - długość linii w pikselach //------------------------------------------------------------------ void gfxClipVLine(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color, Uint32 len) { Sint32 x1c, x2c, y1c, y2c, yp; x1c = screen->clip_rect.x; y1c = screen->clip_rect.y; x2c = x1c + screen->clip_rect.w; y2c = y1c + screen->clip_rect.h; // obcinanie odcinka yp = y + len; if((x >= x1c) && (yp > y1c) && (y < y2c) && (x < x2c)) { if(y < y1c) { len += y - y1c; y = y1c; } if(yp > y2c) len = y2c - y; gfxVLine(screen, x, y, color, len); } } // gfxClipRectangle() rysuje ramkę na obrysie prostokąta z obcinaniem // screen - wskaźnik struktury SDL_Surface // r - definiuje prostokąt ramki // color - 32 bitowy kod koloru ramki //------------------------------------------------------------------ void gfxClipRectangle(SDL_Surface * screen, SDL_Rect * r, Uint32 color) { gfxClipHLine(screen, r->x, r->y, color, r->w); gfxClipHLine(screen, r->x, r->y + r->h - 1, color, r->w); gfxClipVLine(screen, r->x, r->y, color, r->h); gfxClipVLine(screen, r->x + r->w - 1, r->y, color, r->h); }UWAGA: Na końcu pliku nagłówkowego SDL_gfx.h dopisz:
void gfxClipHLine(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color, Uint32 len); void gfxClipVLine(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color, Uint32 len); void gfxClipRectangle(SDL_Surface * screen, SDL_Rect * r, Uint32 color);Poniższy program testuje nowe funkcje graficzne. Program definiuje na środku ekranu prostokąt obcinający. Następnie losuje 50 ramek. Wylosowaną ramkę rysuje najpierw bez obcinania w kolorze czerwonym, a następnie z obcinaniem w kolorze żółtym. W efekcie części ramek zawarte w prostokącie obcinającym będą żółte, a części zewnętrzne będą czerwone.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P012 - obcinanie prostokątów //----------------------------- #include <windows.h> #include <SDL/SDL_gfx.h> #include <time.h> // do inicjalizacji generatora pseudolosowego const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach int main(int argc, char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO)) { MessageBox(0, SDL_GetError(), "Błąd inicjalizacji SDL", MB_OK); exit(-1); } atexit(SDL_Quit); SDL_Surface * screen; if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) { MessageBox(0, SDL_GetError(), "Błąd tworzenia bufora obrazowego", MB_OK); exit(-1); } if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) { MessageBox(0, SDL_GetError(), "Błąd blokady bufora obrazowego", MB_OK); exit(-1); } // inicjujemy generator liczb pseudolosowych srand((unsigned)time(NULL)); // definiujemy prostokąt SDL_Rect * clip = new SDL_Rect; // prostokąt obcinania SDL_Rect * r = new SDL_Rect; // prostokąt ramki clip->x = screen->w >> 2; // 1/4 szerokości ekranu clip->y = screen->h >> 2; // 1/4 wysokości ekranu clip->w = screen->w >> 1; // 1/2 szerokości ekranu clip->h = screen->h >> 1; // 1/2 wysokości ekranu // ustawiamy prostokąt obcinający SDL_SetClipRect(screen, clip); // generujemy 50 przypadkowych ramek for(int i = 0; i < 50; i++) { r->x = rand() % ((screen->w >> 1) + (screen->w >> 2)); r->y = rand() % ((screen->h >> 1) + (screen->h >> 2)); r->w = 5 + rand() % (screen->w - r->x - 5); r->h = 5 + rand() % (screen->h - r->y - 5); gfxRectangle(screen, r, 0xff0000); gfxClipRectangle(screen, r, 0xffff00); } if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); MessageBox(0, "Kliknij przycisk OK", "Koniec", MB_OK); return 0; }
Obcinanie dowolnych odcinków
Istnieje wiele algorytmów obcinających odcinek do zadanego prostokąta. My zastosujemy algorytm Cohena-Sutherlanda, który jest w miarę prosty do zrozumienia. Algorytm ten dzieli obszar graficzny na 9 części. W środku znajduje się zawsze prostokąt obcinający (nr 5) zdefiniowany przez współrzędne x,y lewego górnego narożnika oraz szerokość w i wysokość h (oś OY u nas jest skierowana w dół):
Zadaniem algorytmu Cohena Sutherlanda dla dowolnego odcinka (x1,y1) - (x2,y2) jest znalezienie jego części zawartej wewnątrz prostokąta obcinającego lub odrzucenie odcinka, jeśli nie posiada on części wspólnej z prostokątem. W tym celu algorytm wylicza dla każdego końca odcinka specjalny, 4-bitowy kod, który identyfikuje położenie tego końca w jednym z 9 powyżej przedstawionych obszarów. Bity w kodzie b3b2b1b0 posiadają następujące znaczenie:
b0 = 1, jeśli koniec odcinka leży na lewo od prostokąta obcinającego (obszary 1,4,7)
b1 = 1, jeśli koniec odcinka leży na prawo od prostokąta obcinającego (obszary 3,6,9)
b2 = 1, jeśli koniec odcinka leży powyżej prostokąta obcinającego (obszary 1,2,3)
b3 = 1, jeśli koniec odcinka leży poniżej prostokąta obcinającego (obszary 7,8,9)Jeśli koniec odcinka znajduje się wewnątrz prostokąta obcinającego, to nie zachodzi żaden z powyższych przypadków i kod jest równy 0000.
Kod bitowy można wyznaczyć metodą porównań i ustawiania bitów. Przyjmijmy, iż xp i yp to współrzędne końca odcinka. Zatem:
b0 ← 1, jeśli xp < x
b1 ← 1, jeśli xp ≥ x + w
b2 ← 1, jeśli yp < y
b3 ← 1, jeśli yp ≥ y + hJeśli dla obu końców P1 i P2 odcinka kody są równe 0000, to odcinek w całości leży w prostokącie obcinającym - w takim przypadku nic nie trzeba odcinać, odcinek akceptujemy w całości.
kod(P1) | kod(P2) = 0000
Jeśli którekolwiek bity w obu kodach końców odcinka są równe jednocześnie 1, to odcinek w całości leży poza prostokątem obcinającym, zatem go odrzucamy. Wytłumaczenie jest proste - jeśli na przykład w obu kodach bit b0 jest równy 1, to oba końce odcinka leżą na lewo od prostokąta obcinającego, zatem odcinek nie może przecinać prostokąta. Podobnie jest dla pozostałych bitów:
kod(P1) & kod(P2) ≠ 0000
Jeśli dwa powyższe warunki nie zachodzą, to rysowany odcinek może (ale nie musi) znajdować się częściowo wewnątrz obszaru prostokąta obcinającego. W takim przypadku wybieramy punkt, dla którego kod jest różny od 0000. Załóżmy, iż jest to punkt P1 (jeśli tak nie jest, to punkty i ich kody zamieniamy ze sobą). Przeglądamy kolejne bity kodu:
b0 = 1
P1 leży na lewo od prostokąta obcinającego.
Wyznaczamy współrzędne punktu przecięcia odcinka z prostą zawierającą lewy bok prostokąta obcinającego:
Obliczone współrzędne x'1, y'1 przypisujemy do współrzędnych punktu P1 i ponownie obliczamy kod binarny powracając do początku algorytmu.
Podobnie postępujemy dla pozostałych bitów kodu:
b1 = 1
P1 leży na prawo od prostokąta obcinającego.
Wyznaczamy współrzędne punktu przecięcia odcinka z prostą zawierającą prawy bok prostokąta obcinającego:
Obliczone współrzędne x'1, y'1 przypisujemy do współrzędnych punktu P1 i ponownie obliczamy kod binarny powracając do początku algorytmu.
b2 = 1
P1 leży ponad prostokątem obcinającym (oś OY jest u nas skierowana w dół).
Wyznaczamy współrzędne punktu przecięcia odcinka z prostą zawierającą górny bok prostokąta obcinającego:
Obliczone współrzędne x'1, y'1 przypisujemy do współrzędnych punktu P1 i ponownie obliczamy kod binarny powracając do początku algorytmu.
b3 = 1
P1 leży poniżej prostokąta obcinającego (oś OY jest u nas skierowana w dół).
Wyznaczamy współrzędne punktu przecięcia odcinka z prostą zawierającą dolny bok prostokąta obcinającego:
Obliczone współrzędne x'1, y'1 przypisujemy do współrzędnych punktu P1 i ponownie obliczamy kod binarny powracając do początku algorytmu.
Z przedstawionych powyżej rozważań wyłania się następujący algorytm Cohena-Sutherlanda:
Wejście
x1, y1 - współrzędne ekranowe punktu P1 x2, y2 - współrzędne ekranowe punktu P2 x, y - współrzędne lewego, górnego narożnika prostokąta obcinającego (oś OY jest skierowana w dół !!!) w - szerokość prostokąta obcinającego h - wysokość prostokąta obcinającego Wyjście
Algorytm rysuje odcinek (x1,y1)-(x2,y2) obcięty do zadanego prostokąta obcinającego (x,y,w,h). Jeśli odcinek w całości leży poza obszarem prostokąta, to nie będzie rysowany.
Lista kroków
K01: k1 ← kod(x1,y1) ; wyznaczamy kody binarne końców odcinka K02: k2 ← kod(x2,y2) K03: Jeśli k1 | k2 = 0000, rysuj odcinek x1,y1 - x2,y2 i zakończ ; jeśli odcinek leży wewnątrz prostokąta obcinającego, rysujemy go K04: Jeśli k1 & k2 ≠ 0000, zakończ ; jeśli odcinek leży na zewnątrz prostokąta, odrzucamy go K05: Jeśli k1 = 0000, zamień k1 z k2 oraz x1 z x2 i y1 z y2 ; jeśli pierwszy punkt w prostokącie, zamieniamy punkty i kody K06: Jeśli k1 & 0001 = 0000, idź do kroku K10 K07: y1 ← y1 + (y2 - y1) • (x - x1) / (x2 - x1) ; punkt na lewo od prostokąta K08: x1 ← x K09: Idź do kroku K01 K10 Jeśli k1 & 0010 = 0000, idź do kroku K14 K11: y1 ← y1 + (y2 - y1) • (x1 - x - w + 1) / (x1 - x2) ; punkt na prawo od prostokąta K12: x1 ← x + w - 1 K13: Idź do kroku K01 K14: Jeśli k1 & 0100 = 0000, idź do kroku K18 K15: x1 ← x1 + (x1 - x2) • (y1 - y) / (y2 - y1) ; punkt powyżej prostokąta K16: y1 ← y K17: Idź do kroku K01 K18: x1 ← x1 + (x1 - x2) • (y1 - y - h + 1) / (y2 - y1) ; punkt poniżej prostokąta K19: y1 ← y + h - 1 K20 Idź do kroku K01 Na podstawie powyższego algorytmu zaprogramujemy funkcję biblioteczną gfxClipLine() oraz jej pochodną gfxClipLineTo().
UWAGA: Poniższy kod funkcji gfxClipLine() oraz gfxClipLineTo() dopisz do końca pliku SDL_gfx.cpp.
// gfxClipLine() rysuje odcinek pomiędzy zadanymi punktami obcięty do // obszaru prostokąta obcinającego ustawionego przez SDL_SetClipRect() // screen - wskaźnik struktury SDL_Surface // x1,y1 - współrzędne punktu startowego // x2,y2 - współrzędne punktu końcowego // color - kolor odcinka //------------------------------------------------------------------ void gfxClipLine(SDL_Surface * screen, Sint32 x1, Sint32 y1, Sint32 x2, Sint32 y2, Uint32 color) { Sint32 x1c, x2c, y1c, y2c, iax; x1c = screen->clip_rect.x; x2c = x1c + screen->clip_rect.w - 1; y1c = screen->clip_rect.y; y2c = y1c + screen->clip_rect.h - 1; for(;;) { Uint32 k1, k2, uax; k1 = (((x1 - x1c) >> 31) & 1) | (((x2c - x1) >> 30) & 2) | (((y1 - y1c) >> 29) & 4) | (((y2c - y1) >> 28) & 8); k2 = (((x2 - x1c) >> 31) & 1) | (((x2c - x2) >> 30) & 2) | (((y2 - y1c) >> 29) & 4) | (((y2c - y2) >> 28) & 8); if(!(k1 | k2)) { gfxLine(screen, x1, y1, x2, y2, color); break; } if(k1 & k2) break; if(!k1) { uax = k1; k1 = k2; k2 = uax; iax = x1; x1 = x2; x2 = iax; iax = y1; y1 = y2; y2 = iax; } if(k1 & 1) { y1 += ((y2 - y1) * (x1c - x1)) / (x2 - x1); x1 = x1c; } else if(k1 & 2) { y1 += ((y2 - y1) * (x1 - x2c)) / (x1 - x2); x1 = x2c; } else if(k1 & 4) { x1 += ((x1 - x2) * (y1 - y1c)) / (y2 - y1); y1 = y1c; } else { x1 += ((x1 - x2) * (y1 - y2c)) / (y2 - y1); y1 = y2c; } } } // Funkcja gfxClipLineTo() rysuje odcinek od zapamiętanych współrzędnych // do nowych współrzędnych, które po operacji są zapamiętywane. Odcinek // jest obcinany do podanego prostokąta // screen - wskaźnik struktury SDL_Surface // x,y - współrzędne końca odcinka // color - kod koloru odcinka // clip - wskaźnik struktury SDL_Rect z prostokątem obcinającym //------------------------------------------------------------------ void gfxClipLineTo(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color) { gfxClipLine(screen, gfx_x_coord, gfx_y_coord, x, y, color); gfx_x_coord = x; gfx_y_coord = y; }UWAGA: Na końcu pliku nagłówkowego SDL_gfx.h dopisz:
void gfxClipLine(SDL_Surface * screen, Sint32 x1, Sint32 y1, Sint32 x2, Sint32 y2, Uint32 color); void gfxClipLineTo(SDL_Surface * screen, Sint32 x, Sint32 y, Uint32 color);Poniższy program testowy definiuje na środku ekranu prostokąt obcinający, następnie generuje 100 linii o losowych współrzędnych końców i rysuje je raz w kolorze czerwonym bez obcinania i raz w kolorze żółtym z obcinaniem.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P013 - obcinanie linii //----------------------------- #include <windows.h> #include <SDL/SDL_gfx.h> #include <time.h> // do inicjalizacji generatora pseudolosowego const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach int main(int argc, char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO)) { MessageBox(0, SDL_GetError(), "Błąd inicjalizacji SDL", MB_OK); exit(-1); } atexit(SDL_Quit); SDL_Surface * screen; if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) { MessageBox(0, SDL_GetError(), "Błąd tworzenia bufora obrazowego", MB_OK); exit(-1); } if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) { MessageBox(0, SDL_GetError(), "Błąd blokady bufora obrazowego", MB_OK); exit(-1); } // inicjujemy generator liczb pseudolosowych srand((unsigned)time(NULL)); // definiujemy prostokąt SDL_Rect * clip = new SDL_Rect; // prostokąt obcinania clip->x = screen->w >> 2; // 1/4 szerokości ekranu clip->y = screen->h >> 2; // 1/4 wysokości ekranu clip->w = screen->w >> 1; // 1/2 szerokości ekranu clip->h = screen->h >> 1; // 1/2 wysokości ekranu // ustawiamy prostokąt obcinający SDL_SetClipRect(screen, clip); // generujemy 100 losowych linii for(int i = 0; i < 100; i++) { Sint32 x1 = rand() % screen->w; Sint32 x2 = rand() % screen->w; Sint32 y1 = rand() % screen->h; Sint32 y2 = rand() % screen->h; // Rysujemy czerwoną linię bez obcinania gfxLine(screen,x1,y1,x2,y2,0xff0000); // Rysujemy żółtą linię z obcinaniem gfxClipLine(screen, x1, y1, x2, y2, 0xffff00); } if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); MessageBox(0, "Kliknij przycisk OK", "Koniec", MB_OK); return 0; }
Zwróć uwagę, iż pod liniami żółtymi widać linie czerwone. Również końce niektórych linii żółtych nie pokrywają się dokładnie z liniami czerwonymi. Powodów jest kilka. Najważniejszy z nich to użycie przez nas arytmetyki liczb całkowitych do wyznaczania współrzędnych punktów przy przycinaniu odcinka. Tymczasem właściwy algorytm Cohena-Sutherlanda operuje na liczbach rzeczywistych, co znacznie zwiększa dokładność wyznaczania obcięć. Drugi powód jest taki, iż algorytm rysowania linii nie przechodzi zwykle przez wszystkie te same punkty, gdy rozpoczniemy rysowanie od punktu leżącego w innym miejscu linii - również arytmetyka liczb całkowitych ma tutaj swój wpływ. Nasza wersja algorytmu Cohena-Sutherlada nie nadaje się zatem do zastosowań profesjonalnych, ale w grafice szkolnej czy amatorskiej można z powodzeniem go wykorzystywać.
Poniższy program wykorzystuje obcinanie do swobodnego narysowania figury geometrycznej, która nie mieści się w całości na obszarze graficznym.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P014 - przykładowa figura //-------------------------- #include <windows.h> #include <SDL/SDL_gfx.h> #include <time.h> // do inicjalizacji generatora pseudolosowego #include <math.h> // udostępnia funkcje trygonometryczne const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach int main(int argc, char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO)) { MessageBox(0, SDL_GetError(), "Błąd inicjalizacji SDL", MB_OK); exit(-1); } atexit(SDL_Quit); SDL_Surface * screen; if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) { MessageBox(0, SDL_GetError(), "Błąd tworzenia bufora obrazowego", MB_OK); exit(-1); } if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) { MessageBox(0, SDL_GetError(), "Błąd blokady bufora obrazowego", MB_OK); exit(-1); } // inicjujemy generator liczb pseudolosowych srand((unsigned)time(NULL)); // Współrzędne środka ekranu Sint32 xs = screen->w >> 1; Sint32 ys = screen->h >> 1; // Promień figury Sint32 rf = (screen->w >> 2); int const MAXF = 8; // liczba figur w serii // składowe koloru Uint32 r = rand() % 256; Uint32 g = rand() % 256; Uint32 b = rand() % 256; Uint32 color = (r << 16) | (g << 8) | b ; // ustawiamy prostokąt obcinający na cały ekran (to wywołanie nie jest konieczne, ale...) SDL_SetClipRect(screen, NULL); // Obrót figury co 30 stopni for(int i = 0; i < 12; i++) { double fi = 6.283185 * i / 12; // współrzędne środka figury Sint32 xfs = xs + rf * cos(fi); Sint32 yfs = ys + rf * sin(fi); // Współrzędne wierzchołków Sint32 x[5], y[5], nx[4], ny[4]; for(int j = 0; j < 4; j++) { x[j] = xfs + rf * cos(6.283185 * j / 4 + fi); y[j] = yfs + rf * sin(6.283185 * j / 4 + fi); } // Rysujemy figurę for(int k = 0; k < MAXF; k++) { x[4] = x[0]; y[4] = y[0]; // Rysujemy kwadrat gfxMoveTo(x[0], y[0]); for(int j = 1; j < 5; j++) gfxClipLineTo(screen, x[j], y[j], color); // wyznaczamy nowe współrzędne na bokach obecnego kwadratu for(int j = 0; j < 4; j++) { nx[j] = (x[j + 1] * (MAXF - 1) + x[j]) / MAXF; ny[j] = (y[j + 1] * (MAXF - 1) + y[j]) / MAXF; } // Nowe współrzędne stają się aktualnymi for(int j = 0; j < 4; j++) { x[j] = nx[j]; y[j] = ny[j]; } } } if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); MessageBox(0, "Kliknij przycisk OK", "Koniec", MB_OK); return 0; }
Do narysowania tej figury wykorzystujemy prostą metodę znajdowania punktu podziałowego na odcinku. Jeśli mamy odcinek (x1,y1) - (x2,y2) i chcemy znaleźć na nim punkt P dzielący ten odcinek w stosunku m : (1 - m), gdzie m ∈ R, m ∈ (0,1), to stosujemy następujące wzory:
xp = (1 - m)x1 + mx2
yp = (1 - m)y1 + my2Jeśli m wyrazimy w postaci ułamka, to powyższe działania możemy wykonać w dziedzinie liczb całkowitych (tak robimy w programie), jednakże musimy się liczyć z błędami zaokrągleń.
Program wyznacza najpierw cztery wierzchołki kwadratów. Ponieważ kwadraty będą obracane o kąt fi, wykorzystujemy postać parametryczną równania okręgu do wyliczenia współrzędnych tych punktów:
xi = xfs + rfcos(2πi/4 + φ)
yi = yfs + rfsin(2πi/4 + φ)gdzie xfs, yfs są środkiem figury, rf jest promieniem okręgu a φ to kąt obrotu
Po narysowaniu pierwszego kwadratu jego boki dzielimy wg opisanej wyżej metody. Znalezione punkty podziałowe są wierzchołkami kolejnego kwadratu.
Procedurę powtarzamy dla nowego kwadratu odpowiednią liczbę razy. Otrzymujemy poniższą figurę:
Teraz wystarczy dla każdej figury wyznaczyć środki xfs, yfs leżące na okręgu o promieniu rf i środku w środku ekranu, a otrzymamy figurę złożoną:
Wykorzystaj podane informacje do napisania programów rysujących poniższe figury:
![]() |
![]() |
![]() | 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