Wstęp

Wszystkie procedury i funkcje związane z grafiką znajdują się w module GRAPH. By skorzystać z funkcji i procedur jakie daje nam ten moduł należy najpierw przełączyć się do trybu graficznego. Do tego celu służy procedura InitGraph. Jako pierwszy parametr podajemy typ sterownika graficznego, jaki mamy zainstalowany, jako drugi parametr podajemy tryb, w jaki chcemy przejść i trzeci parametr określa ścieżkę dostępu do plików BGI. Trzeba zauważyć, że po skompilowaniu programu napisanego w trybie graficznym, może on u innej osoby nie działać poprawnie. Z tego względu, że kompilator nie dołącza plików BGI oraz plików czcionek CHR automatycznie do pliku wynikowego. Jak dołączyć pliki BGI do naszego programu dowiemy się w dalszej części tego artykułu. Po przejściu w tryb graficzny niektóre procedury nie działają, m.in. Write i Writeln. Do wyświetlania napisów służą inne procedury, ale o tym później.

Ogólna składnia InitGraph jest następująca:

InitGraph(var GraphDrive : Integer; var GraphMode : Integer; PathToDriver : String);

W miejsce GraphDrive i GraphMode podajemy wartości typu całkowitego np. 9, można również posłużyć się stałym przyjmującymi wartości odpowiednich trybów np. VGA. Poniżej znajduje się tabela ze stałymi określającymi kartę graficzną, czyli GraphDrive:

KARTA GRAFICZNA WARTOŚĆ
CurrentDriver -128 (aktualny sterownik przesłany do procedury GetModeRange)
Detect 0 (automatyczne rozpoznanie sterownika graficznego)
CGA 1
MCGA 2
EGA 3
EGA64 4
EGAMono 5
IBM8514 6
HercMono 7
ATT400 8
VGA 9
PC3270 10

Jeśli więc nie wiemy, jaką kartę graficzną posiadamy możemy w jej miejsce wpisać wartość 0, bądź DETECT, a komputer automatycznie rozpozna zainstalowaną kartę graficzną.

W poniższej tabeli zamieściłem stałe określające wartości dla różnych trybów graficznych. Wartość tą wpisujemy do GraphMode:

STAŁA WARTOŚĆ ROZDZIELCZOŚĆ NR. PALETY LICZBA STRON KOLORY
CGAC0 0 320x200 0 1 LightGreen, LightRed, Yellow
CGAC1 1 320x200 1 1 LightCyan, LightMagenta, White
CGAC2 2 320x200 2 1 Green, Red, Brown
CGAC3 3 320x200 3 1 Cyan, Magenta, LightGray
CGAHi 4 640x200 1    
MCGAC0 0 320x200 0 1 LightGreen, LightRed, Yellow
MCGAC1 1 320x200 1 1 LightCyan, LightMagenta, White
MCGAC2 2 320x200 2 1 Green, Red, Brown
MCGAC3 3 320x200 3 1 Cyan, Magenta, LightGray
MCGAMed 4 640x200   1  
MCGAHi 5 640x480   1  
EGALo 0 640x200   4 16
EGAHi 1 640x350   2 16
EGA64Lo 0 640x200   1 16
EGA64Hi 1 640x350   1 4
EGAMonoHi 3 640x350   1 64K
HercMonoHi 0 720x348   2  
ATT400C0 0 320x200 0 1 LightGreen, LightLightRed, Yellow
ATT400C1 1 320x200 1 1 LightCyan, LightMagenta, White
ATT400C2 2 320x200 2 1 Green, Red, Brown
ATT400C3 3 320x200 3 1 Cyan, Magenta, LightCray
ATT400Med 4 640x200   1  
ATT400Hi 5 640x400   1  
VGALo 0 640x200   4 16
VGAMed 1 640x350   2 16
VGAHi 2 640x350   1 16
PC3270Hi 0 720x350   1  
IBM8514Lo 0 640x480     256
IBM8514Hi 1 1024x768     256

Jeśli nie znamy nazwy naszej karty graficznej, ani nie wiemy, jaki jest najlepszy tryb dla tej karty, możemy posłużyć się procedurą, która rozpoznaje kartę i tryb. Ogólna jej składnia wygląda tak:

DetectGraph(var GraphDriver, GraphMode : Integer)

Po wykonaniu tej procedury parametry GraphDriver i GraphMode możemy podać do procedury InitGraph. Tak więc by przejść do trybu graficznego, dla każdej karty graficznej (komputer sam ją rozpozna) należy napisać następujący ciąg instrukcji:

uses Graph;
var Karta, Tryb : Integer;
begin
  DetectGraph(Karta,Tryb);
  InitGraph(Karta,Tryb,'c:\bp\bgi');
  ...
End.

Każdy ewentualny błąd jaki powstanie podczas wykonywania instrukcji graficznych zwraca funkcja GraphResult. Wszystkie ewentualne błędy są reprezentowane przez stałe. Poniżej znajduje się tabela z kodami i stałymi błędów, jaki zwraca funkcja GraphResult:

STAŁA WARTOŚĆ OPIS
grOK 0 operacja graficzna wykonana pomyślnie
grNoInitGraph -1 nie zainstalowano trybu graficznego
grNoDetect -2 nie wykryto karty graficznej
grFileNotFund -3 nie znaleziono pliku sterownika graficznego (BGI)
grInvalidDriver -4 zastosowano niewłaściwy do załadowania sterownik graficzny
grNoLoadMem -5 brakuje pamięci do załadowania sterownika graficznego
grNoScanMem -6 przekroczenie pamięci przy wypełnianiu obszaru metodą scan
grNoFloodMem -7 przekroczenie pamięci przy wypełnianiu obszaru metodą flood
grFontNotFund -8 nie znaleziono pliku definiującego czcionki
grNoFontMem -9 brakuje pamięci do załadowania kroju czcionek
grInvalidMode -10 zastosowano niewłaściwy tryb lub sterownik graficzny
grError -11 błąd operacji graficznych
grIOerror -12 błąd wejścia - wyjścia graficznego
grInvalidFont -13 zastosowano niewłaściwy krój czcionek
grInvalidFontNum -14 zastosowano niewłaściwy numer kroju czcionek

Zawsze na koniec pracy w trybie graficznym należy przywrócić tryb tekstowy, by nie wystąpiły ewentualne błędy. Do zamknięcia trybu graficznego służy procedura CloseGraph. Jest to procedura bezparametrowa i powoduje powrót do trybu tekstowego. W programach można wiele razy włączać i wyłączać tryb graficzny. Jeśli już włączyłeś ten tryb i jesteś ciekaw jaką kartę graficzną masz zainstalowaną możesz to uczynić funkcją GetDriverName. Zwraca ona łańcuch zawierający nazwę aktualnego sterownika graficznego. Funkcja GetModeName zwraca łańcuch zawierający nazwę aktualnego trybu graficznego.

W trybie graficznym każdy punkt na ekranie ma swoje współrzędne X i Y. Współrzędna X rośnie od lewej do prawej, a Y z góry na dół. Punkt o współrzędnych X=1 i Y=1 leży w lewym górnym rogu ekranu. Moduł Graph daje nam również dwie bardzo pożyteczne funkcje. GetMaxX i GetMaxY. Funkcja GetMaxX zwraca największą wartość współrzędnej poziomej X, natomiast GetMaxY pionowej Y. Jeśli chcesz więc napisać program, który niezależnie od karty graficznej, a co za tym idzie rozdzielczości wstawił punkt w samym środku ekranu użycie tej funkcji jest niezbędne. Środek ekranu ma współrzędne X=GetMaxX div 2, a Y=GetMaxY div 2.

Większość z nas ma karty zgodne z VGA, więc będzie pracować w trybie graficzny określonym dla tych kart. Niestety tryb ten daje nam do dyspozycji tylko 16 kolorów. Jak sprawdzić na innych kartach, lub potwierdzić moje słowa o ilości dostępnych kolorów, należy posłużyć się funkcją bezparametrową GetMaxColor, która zwraca największy z możliwych numerów koloru. W języku Turbo Pascal kolory mają swoje numery i stałe je reprezentujące. Poniżej znajduje się tabela z kolorami i odpowiadającymi im nazwami:

STAŁA WARTOŚĆ KOLOR
Black 0 czarny
Blue 1 niebieski
Green 2 zielony
Cyan 3 truskawkowy
Red 4 czerwony
Magenta 5 karmazynowy
Brown 6 brązowy
LightCray 7 jasnoszary
DarkCray 8 ciemnoszary
LightBlue 9 jasnoniebieski
LightGreen 10 jasnozielony
LightCyan 11 jasnotruskawkowy
LightRed 12 jasnoczerwony
LightMagenta 13 jasnokarmazynowy
Yellow 14 żółty
White 15 biały


Pierwsze kroki

Nauczyliśmy się już jak włączyć i wyłączyć tryb graficzny. Poradzimy sobie z ewentualnymi błędami. Potrafimy również korzystać z funkcji GetMaxX i GetMaxY. Przejdźmy teraz do ciekawszych i przynoszących jakieś efekty procedur i funkcji. Zacznijmy od najprostszej procedury PutPixel. Ogólna składnia tej procedury jest następująca:

PutPixel(X,Y : Integer; Pixel : Word);

Procedura wstawia w punkt o współrzędnych określonych przez X, Y i kolorze określonym przez Pixel. Jej odwrotnością jest bardzo przydatna funkcja GetPixel. Tej składnia jest następująca:

GetPixel(X,Y : Integer);

Funkcja ta zwraca numer koloru punktu, który mieści się na ekranie o współrzędnych określonych przez X i Y.

Przykładowy program z wykorzystaniem procedury PutPixel może wyglądać tak:

uses  Graph;
var Karta, Tryb, i : Integer;
begin
  DetectGraph(Karta, Tryb);
  InitGraph(Karta, Tryb, 'c:\bp\bgi');
  if GraphResult<>grOk then halt;

  for i:=1 to 360 do 
    PutPixel(GetMaxX div 2+Round(SIN(i)*10), 
       GetMaxY div 2+Round(COS(i)*10),Yellow);

  Readln;
  CloseGraph;
End.

Powyższy program rysuje na środku ekranu (dzięki funkcjom GetMaxX div 2 i GetMaxY div 2) okrąg o promieniu 10 i kolorze żółtym. Nie jest on najpiękniejszy, ale dla nas wystarczy. Oczywiście Pascal umożliwia nam na prostsze rysowanie okręgów. Jest do tego celu specjalnie stworzona procedura. Składnia jej jest następująca:

Circle(X,Y : Integer; Radius : Word);

Rysuje ona okrąg o środku w punkcie określonym przez współrzędne X ,Y i promieniu Radius. Promień jest typu Word, a więc nie może przyjmować wartości ujemnych. Ten sam rezultat co wyżej za pomocą procedury Circle wyglądał by tak:

uses Graph;
var Karta, Tryb, i : Integer;
begin
  DetectGraph(Karta, Tryb);
  InitGraph(Karta, Tryb, 'c:\bp\bgi');
  if GraphResult<>grOk then halt;

  SetColor(Yellow);
  Circle(GetMaxX div 2, GetMaxY div 2, 10);

  Readln;
  CloseGraph;
End.

Procedura SetColor z parametrem Yellow, który jest typu Word, określa kolor w jakim zostanie narysowany okrąg. Większość funkcji i procedur rysuje figury na kolor określony właśnie za pomocą tej procedury. Wszystkie figury będą rysowane na kolor określony przez SetColor dopóki nie zostanie on zmieniony za pomocą tej właśnie procedury. By uzyskać informacje o aktualnym kolorze, czyli takim jakim rysujemy, trzeba posłużyć się bezparametrową funkcją GetColor. Zwraca ona numer aktualnego koloru. Do ustawiania koloru tła służy procedura SetBKColor o składni:

SetBkColor(Color : Word);

gdzie Color - jest kolorem tła

By sprawdzić, jakie aktualnie mamy tło użyjemy funkcji GetBkColor. Zwraca ona numer koloru tła.

Kiedy mamy już zamazany cały ekran i przychodzi chwila, kiedy trzeba go wyczyścić, używamy procedury ClearDevice.

Kolejnym często używanym poleceniem jest Line. Składnia tej procedury jest następująca:

Line(X1,Y1, X2, Y2 : Integer);

Procedura rysuje linię prostą o końcach o współrzędnych X1,Y1 i X2,Y2.

Odpowiednikiem procedury GotoXY w trybie graficznym jest procedura MoveTo. Składnia tej procedury jest następująca:

MoveTo(X,Y : Integer);

Procedura przesuwa kursor na pozycję o współrzędnych określonych przez X,Y. W trybie graficznym można używać również dwóch funkcji: GetX i GetY. Zwracają one wartość współrzędnej kursora (X lub Y).

Procedura MoveRel o takiej samej składni przesuwa współrzędne kursora względem aktualnej pozycji o X,Y. Procedura LineTo o składni:

LineTo(Dx,Dy : Integer);

rysuje odcinek o współrzędnych: aktualnej pozycji kursora i współrzędnych punktu określonego przez Dx i Dy. Procedura LineRel o takie samej składni rysuje linię od punktu o współrzędnych aktualnych przez aktualną pozycję kursora, do punktu o współrzędnych przesuniętych względem pozycji kursora o Dx i Dy. Trochę to zagmatwane, ale chyba wszyscy zrozumieli :-))).

Pascal umożliwia nam wybór rodzaju linii, jaką chcemy rysować. Do tego celu służy procedura SetLineStyle. Jej składnia jest następująca:

SetLineStyle(LineStyle : Word; Pattern : Word; Thickness : Word);

Parametr LineStyle określa rodzaj linii, Pattern - wzorzec i Thickness - grubość. By dowiedzieć się jakim aktualnie rysujemy stylem linii trzeba posłużyć się procedurą GetLineSettings. Jej składnia wygląda tak:

GetLineSettings(var LineInfo : LineSettingsType);

Procedura w zmiennej Linenfo, która jest typu LineSettingsType zwraca rodzaj, wzorzec oraz grubość linii, którą aktualnie rysujemy. Typ LineSettingsType ma postać:

TYPE LineSettingsType = RECORD

  LineStyle : Word;
  Pattern : Word;
  Thickness : Word;
END;

Narysujmy teraz przy pomocy linii prostokąt.

Line(100,50,200,50);
Line(200,50,200,150);
Line(200,150,100,150);
Line(100,150,100,50);

Do rysowania prostokątów Pascal daje nam oddzielną procedurę. Powyższy program można zapisać jako:

Rectangle(100,50,200,150);

Procedura ta ma następującą składnię:

Rectangle(X1,Y1, X2, Y2 : Integer);

Współrzędne X1,Y1 są współrzędnymi lewego górnego rogu prostokąta, natomiast X2,Y2 prawego dolnego. Można to sobie wyobrazić jako przekątną danego prostokąta.

Procedura Arc o składni:

Arc(X,Y: Integer; StAngle, EndAngle, Radius: Word);

powoduje narysowanie łuku okręgu o środku w punkcie X,Y i promieniu Radius. Początek łuku określony jest przez StAngle, a koniec przez EndAngle. Podobną do instrukcji Arc jest procedura Ellipse. Ma ona następującą składnię:

Ellipse(X,Y : Integer; StNagle, EndAngle : Word;XRadius,YRadius : Word);

Powoduje ona narysowanie wycinka elipsy o środku w punkcie X,Y, kącie początkowym StAngle, kącie końcowym EndAngle oraz promieniach X-XRadius i Y-YRadius.


Napisy

By wstawić jakiś napis na ekran w trybie graficznym, nie można posłużyć się procedurami Write ani Writeln. W Pascal'u zostały stworzone do tego celu specjalne procedury. Teksty są na ekranie rysowane, mogą być rysowane różnymi czcionkami, dlatego wstawienie dwóch liter w tym samym miejscu ekranu, nie spowoduje zamazanie tej pierwszej, tylko narysowanie jej na wcześniejszej.

Procedura OutText ma następującą składnię:

OutText(TextString : String);

Powoduje ona narysowanie napisu TextString na ekranie w miejscu określonym położeniem kursora. Do określania położenia kursora używa się wcześniej opisaną procedurę MoveTo. Druga procedura do rysowania napisów na ekranie ma następującą składnię:

OutTextXY(X,Y : Integer; TextString : String);

Różni się ona od poprzedniej tym, że możemy określić miejsce rysowania naszego napisu. Obie procedury rysują napisy w kolorze określonym przez SetColor.

Do określania czcionki itp. służy procedura SetTextStyle. Jej struktura wygląda tak:

SetTextStyle(Font : Word; Direction : Word; CharSize : Word);

  • Font - rodzaj czcionki, (0..10)
  • Direction - kierunek (0-poziomo; 1-pionowo)
  • CharSize - wielkość czcionki (1..10)

Procedury OutText i OutTextXY pozwalają na wstawianie napisów na ekran. By wstawić jakąś liczbę, trzeba posłużyć się procedurą Str:

Str(X[:Width [:Decimals] ]; var S);

Powoduje ona zmianę wartości numerycznej X na wartość łańcuchową S. Odwrotnością jej jest procedura Val:

Val(S; var V; var Code : Integer);

Procedura ta powoduje zmianę wartości łańcuchową S na liczbową V. W przypadku wystąpienia jakiegoś błędu zmienna Code ma wartość różną od 0.

W trybie graficznym każda litera, ciąg liter może mieć różną długość i wysokość. Do określania tych właściwości służą dwie funkcje:

  • TextHeight(TextString : String);
  • TextWidth(TextString : String);

Pierwsza zwraca wysokość naszego tekstu (TextString), natomiast druga jego długość. By np. wstawić napis BINBOY dokładnie na środku ekranu, napisalibyśmy:

  ...
  Napis:='BINBOY';
  OutTextXY(GetMaxX div 2-TextWidth(Napis) div 2,GetMaxY 
    div 2-TextHeight(Napis) div 2, Napis);
  ...


Wypełnianie figur

Procedura FloodFill o składni:

FloodFill(X,Y : Integer; Border : Word);

powoduje wypełnienie wnętrza obszaru zamkniętego linią i obejmującego punkt o współrzędnych X,Y. Brzeg tego obszaru zostanie zaznaczony kolorem określonym przez Border. Wypełniany obszar jest w sposób określony przez SetFillStyle.

SetFillStyle(Pattern : Word; Color: Word);

Procedura ustala sposób wypełniania figur. Zmienna Pattern określa rodzaj wypełnienia (1..10), zmienna Color ustala kolor wypełnienia.

Procedura Bar o składni:

Bar(X1,Y1,X2,Y2 : Integer);

Rysuje prostokąt w ten sam sposób co Rectangle, tyle że zostaje on wypełniony w sposób określony procedurą SetFillStyle;

Podobną do instrukcji Bar jest procedura Bar3D. Ma ona następującą składnię:

Bar3D(X1,Y1, X2, Y2 : integer; Depth: Word; Top: Boolean);

Powoduje ona narysowanie trójwymiarowego wypełnionego prostopadłościanu. Ściana czołowa ma współrzędne (X1,Y1) (X2,Y2). Zmienna Depth określa głębie prostopadłościanu, natomiast zmienna Top określa, czy ma być rysowana górna powierzchnia prostopadłościanu.


Wycinanie obrazów

By wyciąć dany element obrazu trzeba przydzielić mu odpowiednią ilość pamięci. W tym celu trzeba użyć funkcji ImageSize. Ma ona następującą składnię:

ImageSize(X1,Y1,X2,Y2 : Integer);

Gdzie za pomocą prostokąta o wierzchołkach X1,Y1 i X2,Y2 obliczamy ilość pamięci jaka będzie potrzebna do zapamiętania obrazka. Wiedząc już ile pamięci będzie nam potrzebne, trzeba przydzielić tą pamięć. Do tego celu służy procedura GetMem. Ogólna jej składnia jest następująca:

GetMem(var P : Pointer; Size: Size);

Powoduje przydzielenie bloku pamięci o wielkości Size i zwraca wskaźnik na ten blok w zmiennej P. Odwołanie do tego bloku następuje poprzez P^. By sprawdzić ile w danej chwili mamy dostępnej pamięci trzeba użyć funkcji MaxAvail, która zwraca jej wartość. Po zakończeniu programu należy zwolnić przydzieloną wcześniej pamięć używając procedury o składni:

FreeMem(var P : Pointer; Size: Word);

Powoduje ona zniszczenie zmiennej wskazywanej przez P i zwolnienie zajmowanej przez nią pamięci.

Mając już przydzieloną pamięć należy skopiować do niej nasz rysunek. Do tego celu służy procedura GetImage o składni:

GetImage(X1,Y1,X2,Y2 : Integer; var BitMap);

Gdzie za pomocą X1,Y1 i X2,Y2 ustalamy okno, które jest kopiowanej pod adres BitMap.

Mamy już zapamiętany obrazek. By go wyświetlić posłużymy się procedurą PutImage. Ma ona następującą składnię:

PutImage(X,Y : Integer; var BitMap; BitBlt : Word);

Procedura ta służy do wyświetlania na ekranie uprzednio zapamiętanego prostokątnego obrazu po zmienną BitMap. Współrzędne X,Y określają lewy górny róg tego obrazka, a parametr BitBld określa stosowany operator binarny podczas nakładania zapamiętanego obrazu na ekranie. Może on przyjąć następujące wartości:

  • CopyPut 0 (operacja MOV)
  • XORPut 1 (operacja XOR)
  • ORPut 2 (operacja OR)
  • ANDPut 3 (operacja AND)
  • NOTPut 4 (operacja NOT)


Strony

Turbo Pascal umożliwia nam pracę na kilku stronach graficznych, w zależności od rodzaju i trybu pracy karty graficznej. Jest to bardzo przydatne. Do obsługi stron służą dwie procedury:

SetActivePage(Page : Word);

Ustala aktywną stronę, czyli są na której będziemy rysować oraz

SetVisualPage(Page : Word);

Ustala stronę, którą w danej chwili widzimy. Stosując strony można np. wyświetlić napis "Loading...", podczas którego przygotowywać kolejną stronę. Jeśli pisałeś kiedyś jakąś animację, na pewno ekran strasznie skakał. Było widać jak rysuje się dany element, a później ściera. Strony pozwalają je wyeliminować.


Dołączanie sterowników

Zauważyłeś pewnie, że po skompilowaniu programu jeśli zaniesiesz go koledze, to może nie działać. Spowodowane jest to, że albo ten kolega nie ma sterowników BGI używanych w twoim programie, albo ma je w innym miejscu. Sterowniki BGI są odpowiedzialne za obsługę grafiki i nie są dołączane do pliku wykonywalnego EXE. Jednak można to zrobić samemu. W tym celu trzeba zrobić:

  • Utworzyć pliki OBJ z plików BGI. Do tego celu służy program BINOBJ. Jego użycie jest następujące:
    BINOBJ <plikBGI> <plikOBJ> <NazwaProcedury>
  • Jeśli mamy już pliki OBJ dołączamy je do naszego programu używając dyrektywy $L, np. {$L EGAVGA.OBJ}
  • Tworzymy procedury EXTERNAL procedure EgaVgaDriverProc : external;
  • Teraz musimy tylko zarejestrować obecność sterownika w pliku: RegisterBGIDriver(@EGAVGADriverProc); Jest to funkcja. Jeśli przyjmie ona wartość <0 tzn. że wystąpił błąd.
  • Inicjujemy tryb graficzny InitGraph(karta,tryb,'');

W ten sam sposób można dołącza do pliku sterowniki do czcionek. Postępowanie jest takie same, tylko rejestrujemy je komendą RegisterBGIFont.