Skoro tyle narzekałem na moduł Graph (ta wygoda w użyciu i prędkość działania;), przyszedł czas na dużo lepsze rozwiązania. Na początek zaczniemy od tajemniczego trybu 13h (zaznaczam, że będzie potrzebna znajomość Asemblera, procedury w nim pisane są duuużo szybsze niż Pascalowe, a czas w grafice odgrywa bardzo istotną rolę). Co charakteryzuje ten tryb? Przede wszystkim rozdzielczość 320x200 i możliwość wyświetlania do 256 kolorów. Może się wydawać, że rozdzielczość jest nieco niska, jednak do wielu rzeczy w zupełności wystarcza. Pomijając to tryb ten ma jedną olbrzymią zaletę. Jaką? Jak wiadomo (lub nie:) pamięć komputera w trybie graficznym to cały segment A000h czyli jest to obszar o wielkości 65536 bajtów. W trybie 13h na jeden piksel jest przeznaczony 1 bajt. Pikseli jest: 320*200=64000, a więc oznacza to, że cały obraz mieści się w pamięci komputera i nie trzeba w to mieszać pamięci karty. Jest to znaczne ułatwienie, o czym przekonasz się gdy będę omawiał tryby o większych rozdzielczościach i problemach z tym związanych. Tutaj nie ma żadnych problemów, a więc do dzieła! Zapalmy piksel... Zaraz, zaraz. Najpierw musimy zainicjować nasz tryb, czyli dać komputerowi znać, że od teraz nie będziemy pisać, a rysować i to w 13h! Aby przełączyć komputer w dowolny tryb graficzny musimy posłużyć się przerwaniem o numerze 10h. Jest to bardzo proste. Wystarczy do rejestru AX przenieść numer trybu i odpalić przerwanie. I tutaj wychodzi na jaw przewaga Asemblera w sprawie obsługi przerwań. Oczywiście w Pascalu też można to zrobić, wyglądałoby to tak:
uses dos; {pierwsza wada}
procedure initgraph_pascal_ver;
var reg :registers; {tak!! trzeba deklarować specjalnĽ zmienną}
begin
reg.AX:=$13; {co by było gdyby trzeba było użyć}
end; {ojej! ale długo to trwało:}
intr ($10,reg); {większej iloci rejestrów?}
Natomiast używając wstawek asemblerowych tak:
procedure init; assembler;
asm
mov ax, 13h
end;
int 10h
Chyba nie trzeba nikogo przekonywać o zaletach tego drugiego rozwiązywania. Po przejściu w tryb graficzny możemy zabrać się za rysowanie punktu. Postawienie piksela na ekranie polega na zmodyfikowaniu odpowiedniego bajtu w pamięci, poprzez zwykłe przypisanie mu numeru koloru. Pod adresem [$A000:0] siedzi sobie lewy górny róg ekranu, czyli punkt [0,0]. Punkt [1,0] ([x,y]) zajmuje bajt następny czyli [$A000:1] itd. Po wcale nie takiej głębokiej analizie można dojść do wniosku, że aby postawić dowolny piksel o współrzędnych x, y musimy zmodyfikować bajt [$A000:y*320+x]. I to jest cała filozofia! Znowu zrobimy małe porównanie Asemblera i Pascala:
procedure putpixel_pascal+ver (x,y:word; color;byte);
begin
mem[$A000:y*320+x]:=color
end;
I wersja asmowa:
procedure putpixel (x,y:word; color:byte); assembler;
asm
mov ax, y
end;
mov bx, 320
mul bx
add ax, x {po tych operacjach ax=y*320+x}
mov dx, $A000
mov es, dx
mov di, ax
mov al, color
mov es:[di], color
Mimo iż zapis asemblerowy jest dużo dłuższy jest o wiele szybszy, chociaż na dzisiejszych szybkich komputerach można nie zauważyć różnicy. Jest to spowodowane też tym, że tryb 13h ogólnie jest dosyć szybkim trybem, ponieważ jego zaprogramowanie nie wymaga używania skomplikowanych procedur. My jednak uczymy się programować porządnie (czyli szybko, czyli w asmie), co przyda się przy trybach o wyższych rozdzielczościach. Wracając do tematu. Skoro aby zapalić punkt trzeba numer jego koloru umieścić w pamięci, to analogicznie możemy też z niej odczytać ten numer, dzięki czemu dowiemy się jaki kolor ma piksel o współrzędnych [x,y]:
function (x,y:word):byte; assembler;
asm
mov ax, y
end;
mov bx, 320
mul bx
add ax, x
mov dx, $A000
mov es, dx
mov di, ax
mov al, es:[di]
Jak wiemy wartość zwracana przez funkcję będzie umieszczona w rejestrze AL (byte). Mając te dwie instrukcje (put i getpixel) można stworzyć praktycznie dowolną procedurę graficzną. Szczególnie ta pierwsza będzie wykorzystywana chyba w każdej, a już na pewno w rysującej linię. To już nie jest takie proste. Istnieje wiele algorytmów spełniających to dosyć skomplikowane zadanie . Niektóre są lepsze, niektóre gorsze. Jeśli nie masz ochoty opracowywać tego samemu to skorzystaj z poniższego przykładu. Jest on dosyć przejrzysty, a co najważniejsze - skuteczny:
procedure line(x1,y1,x2,y2,color:integer);
function sign(x:integer):integer;
begin
if x<0 then sign:=-1
end;
else if x>0 then sign:=1
else sign:=0
var x,y,count,xs,ys,xm,ym :integer;
begin
x:=x1;y:=y1;
xs:=x2-x1;
ys:=y2-y1;
xm:=sign(xs);
ym:=sign(ys);
xs:=abs(xs);
ys:=abs(ys);
putpixel(x,y,color);
if xs > ys then begin
count:=-(xs div 2);
end
while (x <> x2 ) do begin
count:=count+ys;
putpixel(x,y,color);
x:=x+xm;
if count>0 then begin
y:=y+ym;
count:=count-xs;
end;
end;
else begin
count:=-(ys div 2);
end;
while (y <> y2 ) do begin
count:=count+xs;
putpixel(x,y,color);
y:=y+ym;
if count>0 then begin
x:=x+xm;
end;
count:=count-ys;
end;
end;
Mając tą procedurę możesz z kolei napisać np, procedurę rysującą prostokąt. Przypomina to nieco klocki lego. Jednak ten klocek nie byłby najlepszy do stworzenia wypełnionego prostokąta (którym np. można było by zmieniać kolor tła), a to dla tego, że można napisać szybszą procedurę rysującą tylko linie poziome. Do narysowania wypełnionego prostokąta w zupełności to wystarczy, a można to zapisać w asemblerze:
procedure hline (x1,y,x2:word; c:byte); assembler;
end;
asm
mov ax, y
mov es[di], al {a jeśli są to stawiamy ostatni piksel}
mov bx, 320
mul bx
add ax, x1 {AX=pozycja pierwszego bajtu linii}
mov bx, ax
add bx, x2 {BX=pozycja ostatniego bajtu linii}
mov dx, $A000
mov es, dx
mov di, ax
mov al, c
@rysuj:
mov es[di], al {rysujemy punkt}
inc di {i zwiększamy DI o 1}
cmp di, bx {porównujemy wartości DI i BX}
jnz @rysuj {jeśli NIE są równe rysujemy dalej}
Proszę, nawet udało nam się uniknąć wywoływania procedury putpixel! Zaoszczędziliśmy przez to nieco czasu. Oczywiście na pewno są sposoby aby przyspieszyć tę procedurę jeszcze bardziej lecz w tej postaci jest ona bardzo czytelna. Za jej pomocą możemy więc narysować w końcu ten wypełniony prostokąt:
procedure bar (x1,x2,y1,y2:word; c:byte);
var i:byte;
begin
for i:=y1 to y2 do
end;
hline (x1,i,x2,c)
I tyle. Właśnie poznałeś główne zagadnienia z programowania grafiki i obsługi trybu 13h. Oczywiście nie są to wszystkie jego możliwości i większość zagadnień, które opiszę w dalszych rozdziałach, będziesz mógł łatwo zaadoptować do tego trybu.