programowanie asembler algorytmy

procedury i efekty graficzne

Rysowanie linii:

Linie możemy podzielić na trzy rodzaje: poziome, pionowe i ukośne. Poziome linie rysuje się najłatwiej. Obliczamy adres pierwszego piksela i rysujemy kolejno punkty w buforze tyle razy ile wynosi długość linii. Np:

mov        eax,[szerokość_ekranu]
mul        [Y_linii]
add        eax,[X_linii]
add        eax,[adres_buforu_ekranu]
mov        edi,eax
; teraz w edi mamy adres pierwszego piksela
mov        ecx,[długość_linii]
mov        al,kolor_piksela
cld
rep        stosb
; zakładamy tryb graficzny 8-bitowy

Jak widać kod nie jest zbyt skomplikowany. Podobnie jest w przypadku rysowania pionowej linii:

...
; ładujemy do edi adres pierwszego
; piksela jak w powyższym przykładzie
mov        ecx,[długość_linii]
mov        al,kolor_piksela
@@:
mov        [edi],al
add        edi,[szerokość_ekranu]
loop        @b

Tutaj kod też jest banalny. Troszkę trudniej jest przy rysowaniu ukośnych linii. Ukośne linie też możemy podzielić na trzy kategorie: linie po kątem 45 stopni, linie bardziej pionowe niż poziome i na odwrót.

Linie pod kątem 45 stopni:

Jak sprawdzić czy linia jest pod takim kątem? Należy sprawdzić czy |x1-x2|=|y1-y2| tzn. czy różnice współrzędnych obydwu końców linii są sobie równe (sorry, że mówię linia, a nie odcinek, ale tak jest prościej). Linię rysujemy począwszy od górnego wierzchołka. Algorytm jest podobny jak powyżej tylko, że do edi jeszcze dodajemy jeden (linia biegnie w prawo) albo odejmujemy jeden (linia biegnie w lewo). Oto kod na rysowanie linii biegnącej w prawo:

...
; ładujemy do edi adres
; pierwszego piksela
mov        ecx,[dlugość_linii] ; |x1 - x2|
mov        al,kolor_piksela
@@:
mov        [edi],al
add        edi,[szerokość_ekranu]
inc        edi
loop        @b

Linie bardziej pionowe niż poziome:

Jak sprawdzić czy linia taka jest? |x1-x2| musi być mniejsze niż |y1-y2|. Teraz musimy obliczyć deltę (x2-x1)/(y2-y1) (zakładam, że y2 jest większe niż y1 - czyli, że linia biegnie raczej w dół niż do góry). Delta wyjdzie ułamkowa, więc należy zapisać ją jako typ zmiennoprzecinkowy. Algorytm jest podobny jak rysowania linii pionowej:

fild [x2]
fisub [x1]
fild [y2]
fisub [y1]
fdivp
fstp [delta]
fild [x1]
fstp [x]
mov        ecx,[wysokosc_linii] ; y2 - y1
@@:
fld [x]
fist [pom]
fadd [delta]
fstp [x]
putpixel [pom],[y1],kolor_piksela
inc [y1]
loop @b

Putpixel zamień tu na swoją procedurkę.

Linie bardziej poziome niż pionowe:

Tutaj jest dokładnie odwrotnie niż powyżej. |x1-x2| musi być większe niż |y1-y2|, delta=(y2-y1)/(x2-x1), a kod wygląda tak:

fild [x2]
fisub [x1]
fild [y2]
fisub [y1]
fdivp
fstp [delta]
fild [y1]
fstp [y]
mov        ecx,[szerokosc_linii] ; x2 - x1
@@:
fld [y]
fist [pom]
fadd [delta]
fstp [y]
putpixel [x1],[pom],kolor_piksela
inc [x1]
loop @b

Rysowanie okręgów:

Rysowanie okręgu opiszę na podstawie algorytmu Bresenhama. Na początku możemy zauważyć, że okrąg jest symetryczny względem osi OX, OY i osi pod kątem 45 stopni (okrąg ma nieskończenie wiele osi symetrii, ale to inna sprawa). Wiedząc o tym nie musimy liczyć współrzędnych każdego punktu okręgu, tylko jego pierwszych 45 stopni. Więc dla każdego obliczonego punktu (x,y), zakładając, że CenterX i CenterY to środek naszego okręgu, możemy narysować punkty w każdej z ośmiu częśći okręgu:

PutPixel(CenterX + X, Center Y + Y)
PutPixel(CenterX + X, Center Y - Y)
PutPixel(CenterX - X, Center Y + Y)
PutPixel(CenterX - X, Center Y - Y)
PutPixel(CenterX + Y, Center Y + X)
PutPixel(CenterX + Y, Center Y - X)
PutPixel(CenterX - Y, Center Y + X)
PutPixel(CenterX - Y, Center Y - X)

Przedstawię teraz gotowy algorytm. Na początku musimy przeprowadzić natępujące obliczenia:

d := 3 - (2 * PROMIEŃ)
x := 0
y := PROMIEŃ

Teraz dla każdego piksela (siedzimy w pętli i za każdym razem zwiększmy x o 1) stosujemy następujące operacje:

Narysuj 8 puktów okręgu
if d < 0 then
    d := d + (4 * x) + 6
else
  begin
    d := d + 4 * (x - y) + 10
    y := y - 1;
  end;

I robimy to, aż x = y. Przedstawione mnożenia przez 4 można zastąpić przez przesunięcia bitowe (szybciej).


Przyciemnianie:

Efekt przyciemniania bardzo łatwo jest zastosować. W trybie paletowym są dwa sposoby: przyciemnianie palety, albo przyciemnianie pikseli na ekranie. Przyciemnianie palety realizuje się przez odejmowanie co klatkę pewnej wartości od wartości kolorów składowych w każdym kolorze w palecie. Przyciemnia się nam wtedy jednak cały ekran. Co zrobić, żeby przyciemnić tylko kawałek ekranu? Pierwszą rzeczą jest odpowiednie uszeregowanie kolorów w palecie. Gdy w palecie są jedynie odcienie jednego koloru (skala szarości itp.) należy tylko w żądanym miejscu na ekranie zmniejszyć wartości pikseli o pewną wartość (uwaga: paleta musi być wtedy uszeregowana od najciemniejszego odcienia do najjaśniejszego). Musimy jednak pamiętać o tym, że gdy np: od piksela o wartości 1 odejmiemy 3 to wyjdzie nam -2, czyli w konsekwencji 254. Aby temu zapobiec musimy zastosować następujący kod do przyciemniania piksela (d to wartość, o którą zmniejszamy piksele):

if piksel<d then
piksel := 0
else
piksel := piksel - d;

Co zrobić, gdy w palecie jest więcej kolorów? Otoż musi ona być uporządkowana. W palecie może być maksymalnie 256 kolorów (teraz mówimy o trybie 256-kolorowym), więc my możemy używać 256/n kolorów (gdzie n to ilość stopni przyciemniania - najlepiej by była to potęga dwójki, wtedy wszystkie kolory z palety zostaną wykorzystane). Np: dla n=8 możemy mieć najwyżej 32 podstawowe kolory. Teraz trzeba ustawić paletę: ostatnie 32 kolory w palecie to podstawowe kolory, przedostatnie to kolory troszkę przyciemnione, przedprzedostatnie jeszce bardziej przyciemnione itd., aż cała paleta zostanie zapełniona. Teraz, aby przyciemnić wybraną część ekranu trzeba od pikseli odejmować wartość k*32 (czyli stopień o jaki chcemy przyciemnić kawałek ekranu razy ilość podstawowych kolorów). Kod jest podobny jak powyżej:

if piksel<k*32 then
piksel := piksel mod 32
else
piksel := piksel - k*32;

Mod to reszta z dzielenia - nie możemy po prostu przypisać pikselowi wartości 0, gdyż mógłby to być inny kolor.

Przyciemnianie w trybach bezpaletowych: tutaj nie mamy już palety, którą maglibyśmy ściemnić lub uporządkować, więc musimy przyciemnić każdy piksel z osobna. Przyciemniamy każdą składową piksela (najlepiej o tę samą wartość). Kod jest podobny jak kod na samej górze strony (d to znowy wartość, o którą przyciemniamy piksele):

if piksel.r<d then
piksel.r := 0
else
piksel.r := piksel.r - d;

To jest kod na przyciemnianie składowej czerwonej. Ten sam kod należy zastosować przy przyciemnianiu składowej zielonej i niebieskiej.


Rozmywanie

Jest to efekt też bardzo prosty. Polega on na uśrednieniu wartości pobocznych pikseli. Przedstawię tylko rozmywanie w trybie bezpaletowym lub z paletą zawierającą łagodne przejścia tonalne (najlepiej odcień jednego koloru uporządkowany wzrastająco lub malejąco). Rozmywanie w trybie paletowym w większą ilością kolorów jest jakby połączeniem poniższego algorytmu z alogorytmem na przyciemnianie w trybach paletowych z większą ilością kolorów. Ale do rzeczy. Najprościej jest uśrednić wartość czterech sąsiadujących ze sobą pikseli: z lewej, z prawej, u góry i u dołu (czyli dodać je do siebie i podzielić przez cztery). Piksel wynikowy można umieścić z powrotem w tym samym buforze (prościej), albo w odpowiednim miejscu w innym buforze (dokładniej). Pierwszą i ostatnią linię najlepiej wyzerować, albo zostawić w spokoju (nie da się ich rozmyć ponieważ piksele w nich nie mają czterech 'sąsiadów').

Bardziej profesjonalnym sposobem jest korzystanie z matryc. Mają one nieparzyste wymiary, środkowy piksel jest aktualnie rozmazywanym pikselem, np:

0 1 0
1 0 1
0 1 0

Jest to matryca do poprzedniego przykładu rozmazywania. O co w tym chodzi? Obrazowo można to przedstawić tak: nakładamy matrycę na obrazek tak, żeby środek matrycy był nad rozmazywanym pikselem, potem wartość każdego piksela pod matrycą mnożymy przez mnożnik nad nim, następnie sumujemy uzyskane wartości i uzyskany wynik dzielimy przez sumę mnożników z matrycy. Czyli dla naszego przykładu piksele u góry, u dołu, z lewej i z prawej mnożymy przez jeden, sumujemy, dzielimy przez jeden, a wynik zapisujemy w rozmazywanym pikselu (najlepiej w osobnym buforze - dla dokładności). Dodam, że matryce do rozmazywania powinny być symetryczne względem osi poziomej i pionowej - jeżeli nie są to otrzymamy inny efekt. Warto poeksperymentować z matrycami. Przy romazywaniu dzielnik jest sumą skal z matrycy, możesz jednak ustawić inny dzielnik, a otrzymasz obraz rozjaśniony lub przyciemniony. Oto kilka przykładów matryc do rozmazywania:

0 1 0
1 2 1
0 1 0

1 2 1
2 4 2
1 2 1

1  2  4  2  1
2  4  8  4  2
4  8 16  8  4
2  4  8  4  2
1  2  4  2  1

Lawa:

Jest to efekt, który sam wymyśliłem (!). Polega on na rozmazywaniu i przyciemnianiu ekranu. Przy przyciemnianiu jednak nie sprawdzamy, czy piksel jest mniajszy od wartości o którą chcemy go przyciemnić - piksel z bardzo ciemnego staje się bardzo jasny. Efekt wygląda bardzo fajnie, możecie ściągnąć do sobie z działu Download.