Část 1. Základní procedury a funkce pro práci v grafickém módu 111h. (init grafiky, umístění pixelu, kreslení čar a kruhu)
Autor tohoto článku nenese žádnou odpovědnost za škody způsobené používáním těchto procedur, funkcí a programů. Článek je určen spíše pokročilejším programátorům se znalostí Pascalu a assembleru. To ovšem nevylučuje, že hotovou unitu může využívat začátečník. Seznam zdrojů a příklady ke stažení jsou umístěny na konci posledního dílu série.
Článků na toto téma již bylo na internetu napsáno mnoho. Jak však ukazují dotazy ve fórech (a spolužáků ve třídě), rozebraných příkladů není nikdy dost. Tato unita začala vznikat asi před rokem z touhy po poznání, dnes je v takovém stavu, že se dá bez problémů využít při vytváření programu pracujícím ve 2D grafice.
Grafický mód 111h jsem zvolil proto, že v rozlišení 640x480 při 16bitových barvách (65,5 tisíc barev) se již dá vytvořit relativně dobře vyhlížející grafika, pro barvu pixelu postačuje pouze 1 word a také, a nejspíš právě proto, že při psaní první verze této unity jsem měl na internátě pouze 486ku s 2MB grafikou, na které vyšší režimy nešly. (Dnes se tomu, že jsem v roce 2004 programoval na 486ce směju.)
Dopodrobna tady celou problematiku rozebírat nebudu, adresování video paměti při vyšších SVGA módech je přehledně popsáno v článku Zobrazování 24 bit. BMP od Petra Kučery na serveru pcsvet.cz
Init grafiky:
asm
mov ah, 4fh
mov al, 02h
mov BX, 111h
int 10h
mov podpora,al
mov vystup,ah
end;
if podpora<>$4f then Begin
writeln('funkce nastaveni grafickeho modu neni podporovana..');
exit;
end;
if vystup<>00 then Begin
writeln('nepovedlo se inicializovat grafiku..');
exit;
end;
návrat do textového módu:
asm
mov ah,0h
mov al,3
int 10h
end;
vykreslení pixelu:
procedure putPixel(x,y,barva:word);
{Tato procedura pise pixely primo do VRAM (nepouziva virtualni obrazovku)}
{Je pouzivana pro vykreslovani ukazatele mysi.}
var Bank,off:word;
long:Longint;
Begin
{Pri pouziti vetsich rozlisenich a vetsim poctu barev nez v rezimu
13h (320x200x256) nelze pristupovat k videopameti jako k celku. Standardne je totiz mozno zapisovat pouze do 65536 bytu pameti.
Proto se pouziva tzv. Banku, ktere se prepinaji... Jeden bank
ma standardne velikost 65536 bytu. }
if (x>=Xmax) or (y>=Ymax) then exit;
x:=x shl 1;
long:=y;
long:=(long*radek+x) shr 16;
Bank:=long;
{ekvivalent Bank:=(y*640+x) div 65536 }
long:=y*radek;
long:=long+x;
long:=long-(Bank shl 16);
off:=long;
{ekvivalent Off:=(y*480+x) - Bank * 65536 }
{Ted vime, v jakem banku a na jeke jeho pozici se zadany pixel nachazi. }
{Pokud je tento Bank jiny nez ten, do ktereho jsme zapisovali posledne,
musime jej zmenit. (Pokud nastavujeme bank pro kazdy pixel, priserne to
zdrzuje..)}
if predchozi<>Bank then Begin
{nastavovani banku zdrzuje, proto jej nastavujeme
jen tehdy, pokud se zmenil}
asm
xor bx, bx
mov dx, BANK { Nastav Bank }
mov ax, 4f05h { Vesa funkce pro praci s okny }
int 10h
end;
end;
{ 1 2}
{ RRRR RGGG GGGB BBBB }
{ jsme v rezimu 16b, takze zelena ma 6 bitu a R a B 5bitu...}
memW[SegA000:off] :=barva;{2}
predchozi:=Bank;
end;
Podobně je napsána i funkce pro zjištění barvy určitého pixelu:
function getPixel(x,y:word):word;
{ Cte hodnotu pixelu primo z VRAM a vraci jeho barvu }
var Bank,off:word;
long:Longint;
Begin
if (x>=Xmax) or (y>=Ymax) then exit;
x:=x shl 1;
long:=y;
long:=(long*radek+x) shr 16;
Bank:=long;
{ekvivalent Bank:=(y*640+x) div 65536 }
long:=y*radek;
long:=long+x;
long:=long-(Bank shl 16);
off:=long;
{ekvivalent Off:=(y*480+x) - Bank * 65536 }
if predchozi<>Bank then Begin
{nastavovani banku zdrzuje, proto jej nastavujeme
jen tehdy, pokud se zmenil}
asm
xor bx, bx
mov dx, BANK { Nastav Bank }
mov ax, 4f05h { Vesa funkce pro praci s okny }
int 10h
end;
end;
getPixel:=memW[SegA000:off];
predchozi:=Bank;
end;
V grafickém módu 111h je pro barvu každého pixelu potřeba pouze 16bitů (1 word). Horních 5 bitů obsahuje hodnotu červené, dalších 6 zelené a dolních 5 bitů modré barvy. Pro převod barvy do 16bitového formátu ze tří barevných složek lze využít následující funkci:
function getColor(r,g,b:word):word;
{R G B jsou v rozsahu 0-255}
{vraci hodnotu barvy pouzitelne v 16bitove grafice}
Begin
r := r shr 2;
g := g shr 2;
b := b shr 2;
{rozsah 0-255 jsme si prevedli na 0-63}
r:=(r shr 1) shl 11; { 0000 0000 00II IIIX -> IIII I000 0000 0000}
g:=(g shl 10)shr 5; { 0000 0000 00II IIII -> 0000 0III III0 0000}
b:=((b shr 1)shl 11)shr 11; { 0000 0000 00II IIIX -> 0000 0000 000I IIII}
getColor := r or g or b;
end;
Čára
Pro vykreslení čáry se dá využít známého Bresenhamova algoritmu.
procedure Line(a,b,c,d:integer;barva:word);
{udela caru}
function sgn(a:real):integer;
begin
if a>0 then sgn:=1
else
if a<0 then sgn:=-1
else sgn:=0;
end;
var
i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer;
begin
u:= c-a;
v:= d-b;
d1x:= sgn(u);
d1y:= sgn(v);
m:= abs(u);
n:= abs(v);
if not(m>n) then
begin
d2x:= 0;
d2y:= d1y;
i:=m;
m:=n;
n:=i;
end
else
begin
d2x:= d1x;
d2y:= 0;
end;
s:= m shr 1;
for i:=0 to m do
begin
Pixel( a ,b,barva);
s:= s+n;
if not(s<m) then
begin
s:= s-m;
a:= a+d1x;
b:= b+d1y;
end
else
begin
a:= a+d2x;
b:= b+d2y;
end;
end;
end;
Kruh
Pro vykreslení kruhu se dá použít Pithágorova věta. V cyklu for inkrementujeme y do doby než y = r. Při každém cyklu si podle vzorce x*x = r*r - y*y vypočítáme souřadnici x a do bodu [x,y] (posunutým o souřadnice středu) umístíme pixel zadané barvy. Aby jednotlivé body kružnice byly spojené, musíme vždy udělat vodorovnou čáru mezi posledními body. Tímto způsobem lze relativně rychle vykreslit čtvrtinu kružnice. Aby byla kružnice kompletní, musíme každý bod vykreslovat vždy 4x. (Jednou do každého kvadrantu.)
procedure kruh(xs,ys,r:longint; Barva:word);
{ Tato procedura vychazi ze vzorce: r*r = x*x + y*y }
{************************************************************}
function odmocnina(cislo:longint):longint;
var vratit,mocnina:longint;
begin
vratit:=0;
mocnina:=0;
while mocnina < cislo do begin
inc(vratit);
mocnina:=vratit*vratit;
end;
odmocnina := vratit;
end;
{************************************************************}
var X,Y,posledniX:longint;
Begin
r := abs(r);
Y := 0;
posledniX := odmocnina( (r*r)-(Y*Y) );
for Y:=0 to r do Begin
x:= odmocnina( (r*r)-(Y*Y) );
repeat
Pixel( xs+posledniX,ys+Y,barva);
Pixel( xs-posledniX,ys+Y,barva);
Pixel( xs-posledniX,ys-Y,barva);
Pixel( xs+posledniX,ys-Y,barva);
dec( posledniX );
until posledniX < x;
posledniX := X;
end;
end;
To by pro teď mohlo stačit. S těmito procedurami a funkcemi se již dá vytvořit nějaký graficky nenáročný program. Pokud chcete mít ale ve svém programu nějakou animaci, zjistíte, že ve většině případech není vykreslování objektů příliš rychlé a obraz problikává. Tento problém se řeší tzv. virtuální obrazovkou, kdy je obraz vykreslován nejdříve do RAM paměti a po jeho úplném vykreslení se rychle přenese do paměti grafické karty.
Při rozlišení 640x480 s 16bitovými barvami je pro takovou virtuální obrazovku potřeba 600 kB paměti. Vzhledem k tomu, že v DOSu je přímo adresovatelných pouze 640 kB (a z toho bývá asi jen polovina volná), musíme pro naši virtuální obrazovku využít XMS paměť. Jak to udělat, to si ukážeme v příštím díle.