Mit Hilfe des BGI (Borland Graphics Interface) entstehen Grafikanwendungen, die nahezu ohne Anpassungen unter Ibr-bo C und Turbo Pascal auf dem PC/ AT, ST und Mac laufen. TOS vermittelt in zwei Teilen die Grundlagen portabler BGI-Grafikprogrammierung.
Mit Turbo-C 2.0 brachte Borland die BGI-Bibliothek (»Borland Graphics Interface«) heraus, die eine Reihe von hervorragenden Grafik- und Textfunktionen bietet. Der Clou: Turbo-C- und Turbo-Pascal-Compiler besitzen unter MS-DOS und Macintosh identische Bibliotheken. Bei »sauberer« Anwendung der Funktionen lassen sich bestehende Programme problemlos und fast ohne Anpassung übernehmen. Auf der TOS-Diskette befindet sich im BGI-Archiv eine auf diesen Artikel zugeschnittene BGI-Bibliothek für das Turbo-C-Demo unserer vorletzten Ausgabe 8/90. Kopieren Sie diese in den LIB- und »graphics.h« in den INCLUDE-Ornder. Um mit dem BGI zu arbeiten, müssen Sie zunächst sicherstellen, daß die Bibliotheksdatei »TCBGILIB.LIB« in der Projektdatei enthalten ist. Sie müssen zudem in Ihrem Programmtext die Datei »GRAPHICS.H« per Include-Direktive zuladen. Sie enthält die Funktionsdeklarationen und wichtige Konstantendefinitionen.
Bevor Sie mit den Grafikfunktionen arbeiten, müssen Sie die BGI-Bibliothek initialisieren. Dazu rufen Sie die »initgraph«-Funktion mit drei Parametern auf. Der erste Parameter ist ein Zeiger auf eine int-Variable, die den verwendeten Grafiktreiber festlegt. Dabei wählen Sie zwischen dem GEM-Treiber (Konstante »ST_GEM«), dem Treiber für die höchste Auflösung (»DETECT«) und dem gerade benutzten Treiber (»CURRENT_ DRIVER«), wobei normalerweise der GEM-Treiber genutzt wird. Auch der zweite Parameter ist ein int-Zeiger, über den Sie die Grafikauflösung festlegen. Sie haben die Wahl zwischen der niedrigen (»STLOW«), mittleren (»STMEDIUM«) und hohen Auflösung (»STHIGH«). Der dritte Parameter ist ein Zeiger auf eine Zeichenkette mit dem Dateinamen des gewünschten Treibers auf der Diskette. Da beim Atari ST im Gegensatz zu MS-DOS die Treiber speicherresident sind, übergeben Sie stets einen Leerstring:
int treiber = DETECT, aufloesung = STHIGH;
initgraph(&treiber,&aufloesung,""); /* Initialisierung */
Der »initgraph«-Aufruf ist ein Dreh- und Angelpunkt in jedem BGI-Programm, da hier je nach Hardware unterschiedliche Einstellungen notwendig sind. Bei der Portierung von Programmen müssen Sie deshalb vor allem an dieser Stelle Ihre Änderungen durchführen.
Bei der Initialisierung und auch bei anderen Funktionsaufrufen kommt es unter Umständen zu Fehlern, z. B. wenn ein Diskettentreiber oder Zeichensatz fehlt.
Wollen Sie einen sauberen Programmierstil vorweisen und »auf Nummer sicher gehen«, so führen Sie mit Hilfe der »graphresult«-Funktion eine Fehlerabfrage durch, wobei Sie als Ergebnis einen int-Wert erhalten.
Ist dieser gleich »grOk« ( = 0), so ist alles ok:
if (graphresult ( ) ! =gr0k) puts("BGI-Fehler!!!");
Ist die Arbeit mit der BGI-Bibliothek beendet, so sollten Sie noch einen abschließenden »closegraph()«-Aufruf durchführen. Nach dem »initgraph«-Aufruf können Sie sofort die BGI-Zeichenfunktionen verwenden, wobei die Zeichenattribute auf Standardwerte gesetzt sind. Haben Sie die Attribute verändert und möchten wiederauf die Standardwerte umschalten, so verwenden Sie dazu die »graphdefaults()«-Funktion. Um die Grafikauflösung zu wechseln, führen Sie einen »setgraph mode«-Aufruf durch, wobei Sie die bei »init-graph« genannten Konstanten verwenden:
setgraphmode(STMEDIUM); /* Mittlere Auflösung * /
Eines der Hauptprobleme portabler Grafikprogrammierung ist die oft so verschiedene Hardware -Auflösung und die Farbzahl differieren gewaltig. Leider bietet das BGI kein normiertes Koordinatensystem, sondern arbeitet je nach Grafikhardware und -modus mit verschiedenen Systemen, beim ST beispielsweise in der hohen Auflösung mit x-Koordinaten von 0 bis 639, y-Koordinaten von 0 bis 399 und einem Ursprung am linken oberen Bildrand. Die maximal möglichen Werte stellen Sie per »getmaxxO«- bzw. »getmaxy()«-Funktionsaufruf fest. Gleiches gilt für die Anzahl der Farben mit der »getmaxcolor()«-Funktion:
printf("x < = %d y < = %d\n", getmaxx( ) , getmaxy());
printf ( "Farbe <r=:%d\n" , getmaxcolor ( ) ) ;
Möchten Sie Ihre Programme portabel gestalten, so sollten Sie ein einheitliches Koordinatensystem verwenden, z. B. mit Koordinaten von jeweils 0 bis 32767. Vor jeder Ausgabe werden die Koordinaten stets in das gerätespezifische Format gewandelt. X-Koordinaten müßten hierfür zunächst mit dem maximalen X-Wert multipliziert und durch 32767 geteilt werden, was ohne Zeitoptimierung folgendermaßen aussieht:
xneu = (int) (((long) xwert * (long) getmaxx()) / 32767);
Wenden wir nun unseren Blick den Farbfähigkeiten des BGI zu, bei denen wir leider einige Einschränkungen hinnehmen müssen. Das Farbregistermodell des ST-Videoshifters ist übernommen worden, doch die Palettenwahl ist eingeschränkt. Sie wählen zwischen folgenden 16, durch Konstanten definierten Farben: »BLACK«, »BLUE«, »GREEN«, »CYAN«, »RED«, »MAGENTA«, »BROWN«, »LIGHTGRAY«, »DARKGRAY«, »LIGHTBLUE«, »LIGHTGREEN«, »LIGHTCYAN«, »LIGHTRED«, »LIGHTMAGENTA«, »YELLOW« und »WF-IITE«. Zum Setzen der FHintergrundfarbe steht der »setbkcolor«-Aufruf zur Verfügung:
setbkcolor(WHITE); /* Weißer Hintergrund * /
Zur inversen Darstellung im Monochrommodus ist hier übrigens der Wert »BLACK« zu verwenden. Die anderen Farbregister werden über die »setpalette«-Funktion gesetzt, der wir Registernummer und Farbwert übergeben:
setpalette(1,BLACK); /* Farbe 1 = schwarz * /
Die gesamte Farbpalette läßt sich per »setallpalette«-Aufruf komplett setzen, wobei Sie als einzigen Parameter die Adresse einer »palettetype«-Struktur übergeben. Dessen signed char-Element ».size« gibt die Zahl der Einträge an, die signed char-Elemente ».colors[ij« enthalten die Farbwerte. Möchten Sie die vier Farben schwarz (Hintergrund), weiß, rot und grün setzen, so ist folgender Programmtext nötig:
struct palettetype palette; palette.size = 4; /*4 Einträge */ palette.colors[0]^BLACK; palette . colors[l] =WHITE; palette.colors[2]=RED; palette.colors[3] — GREEN; setallpalette(&palette);
Per »getpalettef&palette)«-Aufruf lesen Sie die aktuelle Farbpalette ein. Die »getdefaultpalette«-Funktion liefert jederzeit die ursprüngliche Standardpalette, auf die Sie als Ergebnis einen Zeiger erhalten:
struct palettetype *oldpalette; oldpalette—getdefaultpalette();
/* Alte Palette */
printf("%d EinträgeXn",(int) oldpalette->size) ;
Nachdem wir nun die Farbfähigkeiten kennen, kommen wir zu den Zeichenfunktionen. Hier existieren deutliche Parallelen zu den Line-A- und GEM-VDI-Funktionen. So können Sie auch unter BGI per »setviewport«-Funktion ein Ausgabefenster definieren, das alle aus diesem herausgehende Ausgaben automatisch abschneidet. Als Parameter übergeben Sie die Koordinaten der linken oberen und rechten unteren Ecke des Fensters. Mit dem darauffolgenden Parameter legen Sie fest, ob das Grafikfenster aktiviert (— 1) oder deaktiviert (= 0) ist:
setviewport(100,50,230,90,1); /* Fenster ein */
Über einen »clearviewport«-Aufruf löschen Sie den Fensterinhalt, mit der »cleardevice«-Funktion den gesamten Bildschirm. Einzelne Punkte setzen Sie mit der »putpixel«-Funktion, der Sie neben den Koordinaten des Punktes die Nummer der Farbe übergeben. Die »getpixel«-Funktion erwartet nur die beiden Koordinaten und liefert den Farbwert zurück. Alle Parameter und Ergebnisse sind übrigens vom Typ »int«: int test;
putpixel(10,40,3); / * Farbe 3 */
test=getpixel(10,40); /* Farbe ermitteln * /
Listing 1 (BGI1.C) wendet die meisten der bisher besprochenen Funktionen an. In zufälligen Zeichenfenstern erscheinen Bildpunkte an zufälligen Positionen. Zum Linienzeichen steht Ihnen die »line«-Funktion zur Verfügung. Als Parameter übergeben Sie die Koordinaten des Anfangs- und Endpunktes. Die Zeichenfarbe übermitteln Sie mit der »setcolor«-Funktion:
setcolor(3); / * Farbe 3 */
line(10,30, 90,50); /* Linie ziehen */
Der BGI-Treiber verwaltet intern einen nicht sichtbaren Grafik-Cursor, der auf den Endpunkt der letzten Grafikoperation zeigt. Möchten Sie von diesem Punkt aus eine Gerade zu einem anderen ziehen, so verwenden Sie die »lineto«-Funktion, der Sie lediglich die Endkoordinaten übergeben:
lineto(170,30); /* Linie zu (170,30) */
Ähnlich arbeitet die »linereU-Funktion, der Sie eine Distanz übergeben, um die der Endpunkt der Linie vom Anfangspunkt versetzt sein soll. Positive Werte entsprechen einer Verschiebung nach rechts bzw. unten, negative bewirken eine Verschiebung nach links bzw. oben:
linerel(-30,-10 ) ; /* 30 nach links, 10 nach oben * /
Die »moveto«- und »moverel«-Funktionen arbeiten analog, verschieben jedoch nur den Grafikcursor, ohne die Linie zu zeichnen. Per »getxO«- und »getyO«-Aufruf erhalten Sie die aktuelle Cursorposition.
Die »setlinestyle«-Funktion bestimmt das Aussehen der Linien. Mit dem ersten Parameter wählen Sie zwischen den Linienmustern »SOLID_LINE« (durchgezogen), »DOTTED_________________LINE« (gepunktet), »CENTER_LINE«, »DASHED____LINE« (gestrichelt) und »USERBIT__LINE« (benutzerdefiniert). Der zweite Parameter ist vom Typ unsigned und nur dann von Bedeutung, wenn Sie das benutzerdefinierte Linienmuster verwenden. Die Bits dieses Parameters bestimmen das Linienmuster. Für leicht gepunktete Geraden ist der Wert $11 (%00010001 = 17) notwendig. Mit dem dritten Parameter wählen Sie zwischen einer Linienbreite von 1 (»NORM____WIDTH«) und 3 (»THICK___WIDTH«):
setlinestyle(USERBIT_LINE,0x11,THICK_ WIDTH);
Über die »setwritemode«-Funktion wählen Sie für alle Zeichenoperationen zwischen den Schreibmodi »COPY____PUT« (Überschreiben) und »XOR____PUT« (Exklusiv-Oder-Verknüpfung):
setwritemode(C0PY_PUT); /* Kopier-Schreibmodus */
Ungefüllte Rechtecke zeichnen Sie mit der »rectangle«-Funktion, der Sie die Koordinaten paare der linken oberen und rechten unteren Ecke übergeben:
rectangle(30,10,400,90); /* Rechteck zeichnen */
Beliebige Linienzüge bringen Sie per »drawpoly«-Aufruf auf den Bildschirm. In einem int-Feld legen Sie hierzu die Koordinaten paare aller Eckpunkte ab. Der Funktion übergeben Sie die Koordinatenzahl sowie ein Zeiger auf das Koordinatenfeld. Für ein Dreieck sind beispielsweise vier Ecken nötig, wobei der Anfangs- gleich dem Endpunkt ist:
int ecken[8]={ 10,20,300,110,140,203,10,20};
drawpoly (4, ecken) ; / * Dreieck zeichnen* /
Mit der »circle«-Funktion zaubern Sie Kreise auf den Bildschirm, wobei Sie neben den Mittelpunktskoordinaten den Radius übergeben. Die »arc«-Operation arbeitet ähnlich, stellt jedoch nur einen Kreisabschnitt dar. Hierzu übergeben Sie als dritten und vierten Parameter den Anfangs- und Endwinkel des Bereiches, darauf folgt der Kreisradius, »ellipse« ist wiederum eine Erweiterung dieser Funktion und stellt einen Ellipsenabschnitt dar, wobei Sie als fünften und sechsten Parameter die Radien in x- und y-Richtung angeben:
circle (200,300,120 ) ; /*Kreis*/
arc(200,300,10,350,120); /* Kreisabschnitt 10-350 Grad */
ellipse(200,300,10,350,120, 70); /* Ellipse, Radien 120 und 70 */
Listing 2 (BGI2.C) zeigt die Verwendung der besprochenen Funktionen zum Darstellen von Liniengrafiken. Es stellt solange zufällige Objekte auf dem Bildschirm dar, bis Sie eine Taste betätigen. In der nächsten Ausgabe stellen wir Ihnen die restlichen Funktionen des BGI vor. Dazu gehören Funktionen zum Füllen von Objekten, Darstellen von Shapes und umfangreiche Textausgabe. (ba)
über den Autor: Frank Mathy ist 23 Jahre alt und studiert an der FH Wiesbaden Informatik. Er ist Autor zahlreicher Bücher, u. a. auch des in diesem Quartal erscheinenden Turbo-C 2.0-Buchs des Markt&Technik Verlags und ein Experte in Sache BGI-Programmierung.
#include <stdio.h>
#include <stdlib.h>
#include <ext.h>
#include <graphics.h> /* BGI-Bibliothek */
int treiber=DETECT; /* Treibertyp */
int modus; /* Grafikmodus */
int maxx,maxy; /* Maximale Koordinaten */
int maxc; /* Größte Zeichenfarbe */
void init_all(void) /* Initialisierungen */
{
initgraph(&treiber,&modus,""); /* BGI initialisieren */
graphdefaults(); /* Normaleinstellungen */
maxx=getmaxx(); /* Maximale X-Koordinate */
maxy=getmaxy(); /* Maximale Y-Koordinate */
maxc=getmaxcolor(); /* Maximale Farbe */
cleardevice(); /* Bildschirm löschen */
}
void exit_all(void) /* Beenden des Programmes*/
{
setallpalette(getdefaultpalette());/* Alte Farbpalette */
closegraph(); /* BGI-Arbeiten beenden */
}
void main(void)
{
int i;
int x,y;
int x1,x2,y1,y2;
srand(0); /* Zufallsgenerator an */
init_all(); /* Alles initialisieren */
setbkcolor(WHITE); /* Hintergrund weiß */
while(!kbhit()) /* Solange keine Taste */
{
x1=rand()%maxx; x2=x1+maxx/2; /* X-Koordinaten */
y1=rand()%maxy; y2=y1+maxy/2; /* Y-Koordinaten */
if(x2>maxx) x2=maxx; /* Bei zu hohen Koord. */
if(y2>maxy) y2=maxy;
setviewport(x1,y1,x2,y2,1); /* Fenster einschalten */
clearviewport(); /* Fenster löschen */
for(i=0; i<1000; i++)
putpixel(rand()%maxx,rand()%maxy,1);
/* Bildpunkte zeichnen */
}
getch();
setbkcolor(BLACK); /* Hintergrund schwarz */
getch();
setviewport(0,0,maxx,maxy,0); /* Kein Fenster */
exit_all(); /* Arbeit beenden */
}
Listing 1. In zufällig errechneten Ausgabefenstern erscheinen an zufälligen Positionen Bildschirmpunkte
/* Beispielprogramm 2 */
/* Von Frank Mathy für die TOS 10/90 */
/* Für Turbo C 2.0 (mit BGI-Bibliothek */
#include <stdio.h>
#include <stdlib.h>
#include <ext.h>
#include <graphics.h> /* BGI-Bibliothek */
#define XRAND rand()%maxx /* Zufällige Koordinaten */
#define YRAND rand()%maxy
#define RRAND rand()%100
#define WRAND rand()%360 /* Zufällige Winkel */
int treiber=DETECT; /* Treibertyp */
int modus; /* Grafikmodus */
int maxx,maxy; /* Maximale Koordinaten */
int maxc; /* Größte Zeichenfarbe */
void init_all(void) /* Initialisierungen */
{
initgraph(&treiber,&modus,""); /* BGI initialisieren */
graphdefaults(); /* Normaleinstellungen */
maxx=getmaxx(); /* Maximale X-Koordinate */
maxy=getmaxy(); /* Maximale Y-Koordinate */
maxc=getmaxcolor(); /* Maximale Farbe */
cleardevice(); /* Bildschirm löschen */
}
void exit_all(void) /* Beenden des Programmes*/
{
setallpalette(getdefaultpalette());/* Alte Farbpalette */
closegraph(); /* BGI-Arbeiten beenden */
}
void main(void)
{
int e[8];
srand(0); /* Zufallsgenerator an */
init_all(); /* Alles initialisieren */
setpalette(0,WHITE); /* Weißer Hintergrund */
e[0]=e[6]=10; e[1]=e[7]=10; /* Punkt 1 */
e[2]=maxx-20; e[3]=maxy/2; /* Punkt 2 */
e[4]=maxx/2; e[5]=maxy-10; /* Punkt 3 */
drawpoly(4,e); /* Dreieck zeichnen */
setwritemode(XOR_PUT); /* Exclusiv-Oder */
while(!kbhit()) /* Solange keine Taste */
{
setcolor(rand()%maxc); /* Zufällige Farbe */
setlinestyle(rand()%5,rand(),NORM_WIDTH);
switch(rand()%6) /* Zufällige Operation */
{
case 0: line(XRAND,YRAND,XRAND,YRAND);
break; /* Zufällige Gerade */
case 1: rectangle(XRAND,YRAND,XRAND,YRAND);
break; /* Zufälliger Rahmen */
case 2: circle(XRAND,YRAND,RRAND);
break; /* Zufälliger Kreis */
case 3: arc(XRAND,YRAND,WRAND,WRAND,RRAND);
break;
case 4: ellipse(XRAND,YRAND,WRAND,WRAND,RRAND,RRAND);
break; /* Zufällige Ellipse */
case 5: cleardevice();
break;
}
}
exit_all(); /* Arbeit beenden */
}
Listing 2. So kurz kann eine Grafikanwendung sein. An zufälligen Positionen Linien, Boxen, Kreise oder Ellipsen.