Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych. Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2008 mgr
Jerzy Wałaszek |
Poprzednie tematy, które należy przerobić przed przystąpieniem do tej lekcji:
01 - Instalacja biblioteki SDL dla Dev-C++
02 - Powierzchnia graficzna w SDL - stawianie punktów
03 - Tworzenie biblioteki graficznej mylib
04 - Stawianie punktów i rysowanie linii poziomych oraz
pionowych
05 - Zabawa z pikselami
Pliki biblioteczne i projektowe do pobrania: | Parametry dla konsolidatora |
mylib.h mylib.cpp main.cpp |
-lmingw32 -mwindows -lSDLmain -lSDL |
Na tej lekcji omawiamy popularny algorytm Bresenhama rysowania odcinków. Jeśli nie interesują cię sprawy techniczne SDL, to przejdź do opisu nowych funkcji oraz do następnej lekcji, gdzie znajdziesz ich praktyczne zastosowania - nie zapomnij pobrać z naszego serwisu plików biblioteki mylib.cpp oraz mylib.h.
Idea algorytmu Bresenhama jest następująca:
Mamy siatkę (raster) punktów. Każdy punkt posiada odpowiednie współrzędne całkowite (x,y). Oś y jest skierowana w dół ze względów technicznych.
Na siatce wybieramy dwa punkty o współrzędnych (x1,y1)
oraz (x2,y2). Współrzędne odnoszą się do
środka piksela. Bez zmniejszania ogólności załóżmy, iż punkty te wybrano, tak aby odcinek
Zadanie sprowadza się do pokolorowania kolejnych pikseli (oczek rastra), których środki leżą najbliżej punktu na odcinku o tej samej współrzędnej x, co dany piksel. Zwróć uwagę, iż ze względu na nasze założenie o kącie nachylenia odcinka względem OX, w kierunku X współrzędne kolejnych pikseli są zwiększane zawsze o 1, natomiast w kierunku Y odbywa się to mniej często. Kierunek X nazwiemy szybkim, a kierunek Y wolnym.
Algorytm Bresenhama określa, kiedy należy wykonać ruch w kierunku wolnym Y (bo w kierunku szybkim X ruch wykonujemy zawsze!). Rozważmy równanie prostej przechodzącej przez dwa punkty (x1,y1) i (x2,y2):
W powyższym równaniu x oznacza współrzędną środka kolejnego piksela (punkty niebieskie). Współrzędna y natomiast odnosi się do punktu leżącego na prostej zawierającej odcinek (punkty czerwone). Środek piksela jest zwykle w pewnej odległości od odpowiadającego mu punktu na odcinku. Oznaczmy tę odległość przez ddy, a współrzędną y środka piksela przez yp. Zatem możemy zapisać:
Wstawmy to wyrażenie do równania prostej:
Teraz przekształćmy wyrażenie następująco:
Wyrażenie -ddy•dx jest tzw. wyrażeniem błędu (ang. error term). Zgodnie ze wzorem krok w kierunku szybkim X powoduje zmniejszenie wyrażenia błędu o dy. Jeśli wartość wyrażenia będzie ujemna, to wykonujemy krok w kierunku wolnym Y, czyli zwiększamy je o dx. Ponieważ dx ≥ dy, to wyrażenie błędu sprowadzi się znów do wartości dodatniej lub 0. Na tej podstawie algorytm Bresenhama określa kiedy należy wykonać ruch w kierunku Y - stara się zawsze utrzymać wyrażenie błędu w zakresie liczb nieujemnych. Wartości ddy wcale nie musimy obliczać. Bardzo ważna jest wartość początkowa wyrażenia błędu. Zwykle przyjmuje się ją równą dx/2.
Sformułujmy algorytm Bresenhama dla tego przypadku:
x1,y1 - całkowite współrzędne
punktu startowego
x2,y2 - całkowite współrzędne punktu
końcowego
Założenie: x2 > x1, y2 >
y1, x2 - x1 > y2
- y1
Rysunek rastrowy odcinka łączącego punkt x1,y1 z punktem x2,y2.
dx - odległość x2 od x1
dy - odległość y2 od y1
e - wartość wyrażenia błędu
K01: | dx ← x2 - x1 | ; obliczamy odległości dx i dy |
K02: | dy ← y2 - y1 | |
K03: | e ← dx / 2 | ; ustalamy wartość początkową wyrażenia błędu |
K05: | Zapal piksel x1,y1 | ; zapalamy pierwszy piksel odcinka |
K04: | Wykonuj dx razy kroki K06...K10 | ; w pętli zapalamy kolejne piksele |
K06: | x1 ← x1 + 1 | ; ruch w kierunku szybkim |
K07: | e ← e - dy | ; modyfikujemy po ruchu wyrażenie błędu |
K08: | Jeśli e ≥ 0, idź do K11 | ; jeśli e nieujemne, pomijamy ruch w kierunku wolnym |
K09: | y1 ← y1 + 1 | ; ruch w kierunku wolnym |
K10: | e ← e + dx | ; po ruchu wolnym modyfikujemy wyrażenie błędu |
K11: | Zapal piksel x1,y1 | ; zapalamy kolejne piksele odcinka |
K12: | Koniec |
Prześledzimy działanie tego algorytmu na prostym przypadku.
Lp. | Obrazek | Opis |
---|---|---|
1. | Połączymy odcinkiem punkty: (1,1) i (7,5)
Zatem: x1 = 1, y1 = 1 |
|
2. | Zapalamy pierwszy punkt na współrzędnych x1,y1, czyli (1,1) | |
3. | Na osi X przesuwamy się o 1 (kierunek szybki): x1 = x1 + 1 = 1 + 1 = 2 Modyfikujemy wyrażenie błędu: e = e - dy = 3 - 4 = -1. Ponieważ e jest ujemne, wykonujemy ruch w kierunku wolnym Y: y1 = y1 + 1 = 1 + 1 = 2 Modyfikujemy wyrażenie błędu: e = e + dx = -1 + 6 = 5 Zapalamy punkt (x1,y1), czyli (2,2). |
|
4. | Na osi X przesuwamy się o 1. x1 = x1 + 1 = 2 + 1 = 3 Obliczamy wyrażenie błędu: e = e - dy = 5 - 4 = 1 Ponieważ e nie jest ujemne , nie przesuwamy się na osi Y. Zapalamy punkt (3,2). |
|
5. | Na osi X przesuwamy się o 1 i wyliczamy nowe wyrażenie błędu: x1 = x1 + 1 = 3 + 1 = 4 e = e - dy = 1 - 4 = -3. Ponieważ e jest ujemne, wykonujemy ruch w kierunku wolnym Y: y1 = y1 + 1 = 2 + 1 = 3 Modyfikujemy wyrażenie błędu: e = e + dx = -3 + 6 = 3 Zapalamy punkt (x1,y1), czyli (4,3). |
|
6. | Na osi X przesuwamy się o 1 i wyliczamy e x1 = x1 + 1 = 4 + 1 = 5 e = e - dy = 3 - 4 = -1. Ponieważ e jest ujemne, wykonujemy ruch w kierunku wolnym Y: y1 = y1 + 1 = 3 + 1 = 4 Modyfikujemy wyrażenie błędu: e = e + dx = -1 + 6 = 5 Zapalamy punkt (x1,y1), czyli (5,4). |
|
7. | Na osi X przesuwamy się o 1 i wyliczamy nowe e: x1 = x1 + 1 = 5 + 1 = 6 e = e - dy = 5 - 4 = 1 Ponieważ e nie jest ujemne , nie przesuwamy się na osi Y. Zapalamy punkt (6,4). |
|
8. | Ostatni krok algorytmu:
x1 = x1 + 1 = 6 + 1 = 7 |
Przedstawiony powyżej algorytm Bresenhama działa poprawnie jedynie dla przypadku, gdy odcinek tworzy z osią OX kąt mniejszy lub równy 45°. Pokażemy teraz, jak rozwiązać pozostałe przypadki.
Poniżej przedstawiamy pełny algorytm Bresenhama, który rysuje odcinki pomiędzy dowolnymi punktami na powierzchni graficznej. Inne przypadki nachyleń są sprowadzane do przypadku podstawowego przez odpowiednią podmianę współrzędnych punktów odcinka.
x1,y1 - całkowite współrzędne
punktu startowego
x2,y2 - całkowite współrzędne punktu
końcowego
Rysunek rastrowy odcinka łączącego punkt x1,y1 z punktem x2,y2. Współrzędne są dowolne, całkowite.
dx - odległość współrzędnych x1 i
x2
dy - odległość współrzędnych y1, y2
kx - krok po osi X, 1 lub -1
ky - krok po osi y, 1 lub -1
e - wartość wyrażenia błędu
K01: | Jeżeli x1 ≤ x2, to kx ← 1, inaczej kx ← -1 | ; określamy krok X od x1 do x2 |
K02: | Jeżeli y1 ≤ y2, to ky ← 1, inaczej ky ← -1 | ; określamy krok Y od y1 do y2 |
K03: | dx ← |x2 - x1| | ; odległość pomiędzy x1 i x2 |
K04: | dy ← |y2 - y1| | ; odległość pomiędzy y1 i y2 |
K05: | Zapal piksel x1,y1 | ; pierwszy piksel odcinka |
K06: | Jeżeli dx < dy, idź do K16 | ; dla kątów > 45° wykonujemy wersję algorytmu z przestawionymi współrzędnymi |
K07: | e ← dx / 2 | ; obliczamy wartość początkową wyrażenia błędu |
K08: | Powtarzaj dx razy kroki K09...K14 | ; rysujemy pozostałe piksele w pętli |
K09: | x1 ← x1 + kx | ; przesuwamy się w odpowiednią stronę w kierunku szybkim |
K10: | e ← e - dy | ; obliczamy wyrażenie błędu |
K11: | Jeżeli e ≥ 0, idź do K14 | ; jeśli wyrażenie błędu jest nieujemne, pomijamy ruch w kierunku wolnym |
K12: | y1 ← y1 + ky | ; przesuwamy się w odpowiednią stronę w kierunku wolnym |
K13: | e ← e + dx | ; obliczamy nowe wyrażenie błędu |
K14: | Zapal piksel x1.y1 | ; kolejny piksel odcinka |
K15: | Zakończ | |
K16: | e ← dy / 2 | ; wersja algorytmu Bresenhama ze zamienionymi współrzędnymi x i y |
K17: | Powtarzaj dy razy kroki K18...K23 | |
K18: | y1 ← y1 + ky | |
K19: | e ← e - dx | |
K20: | Jeżeli e ≥ 0, idź do K23 | |
K21: | x1 ← x1 + kx | |
K22: | e ← e + dy | |
K23: | Zapal piksel x1,y1 | |
K24: | Zakończ |
Na podstawie podanego powyżej algorytmu tworzymy funkcję biblioteczną line() - znajdziesz ją w naszej bibliotece mylib.cpp lub jeśli tworzysz tę bibliotekę samodzielnie, to przepisz do pliku mylib.cpp poniższy kod funkcji line(). Funkcja rysuje dowolny odcinek w obszarze graficznym. Jest już zoptymalizowana w celu przyspieszenia obliczeń adresów pikseli. Jednakże realizuje dokładnie algorytm Bresenhama:
// Rysuje odcinek linii prostej //----------------------------- void line(Uint32 x1, Uint32 y1, Uint32 x2, Uint32 y2, Uint32 color) { Uint32 * p; int dx, dy, kx, ky, e; kx = (x1 <= x2) ? 1 : -1; ky = (y1 <= y2) ? screen->w : -screen->w; dx = x2 - x1; if(dx < 0) dx = -dx; dy = y2 - y1; if(dy < 0) dy = -dy; p = (Uint32 *)screen->pixels + y1 * screen->w + x1; * p = color; if(dx >= dy) { e = dx >> 1; for(int i = 0; i < dx; i++) { p += kx; e -= dy; if(e < 0) { p += ky; e += dx; } * p = color; } } else { e = dy >> 1; for(int i = 0; i < dy; i++) { p += ky; e -= dx; if(e < 0) { p += kx; e += dy; } * p = color; } } } |
Oprócz funkcji line() zdefiniujemy dodatkowo dwie funkcje, które bardzo przydają się przy rysowaniu łamanych:
// współrzędne początku odcinka dla lineto() //------------------------------------------------------------------ Uint32 gfx_x_coord = 0; Uint32 gfx_y_coord = 0; // Ustawia współrzędne początku odcinka //------------------------------------------------------------------ void moveto(Uint32 x, Uint32 y) { gfx_x_coord = x; gfx_y_coord = y; } // Rysuje odcinek od zapamiętanych współrzędnych // do nowych współrzędnych, które po operacji są zapamiętywane. //------------------------------------------------------------------ void lineto(Uint32 x, Uint32 y, Uint32 color) { line(gfx_x_coord, gfx_y_coord, x, y, color); gfx_x_coord = x; gfx_y_coord = y; } |
W pliku mylib.h znajdziesz prototypy tych funkcji - jeśli tworzysz bibliotekę mylib samodzielnie, to przepisz do pliku mylib.h prototypy zdefiniowanych powyżej funkcji:
void line(Uint32 x1, Uint32 y1, Uint32 x2, Uint32 y2, Uint32 color); void moveto(Uint32 x, Uint32 y); void lineto(Uint32 x, Uint32 y, Uint32 color); |
Rysuje odcinek w kolorze color od punktu (x1,y1) do (x2,y2). Odcinek w całości powinien mieścić się na obszarze graficznym.
Ustala początek odcinka w punkcie (x,y). Punkt ten powinien leżeć w obszarze graficznym.
Rysuje odcinek od poprzednio ustawionego punktu przez moveto() lub lineto() do punktu (x,y). Rysowany odcinek powinien mieścić się w obszarze graficznym.
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