GET-/PUT-Routinen in C

In GFA-BASIC gibt es die schönen, weil einfach handzuhabenden Befehle GET, PUT, SGET, SPUT Da diese Routinen in C nicht existieren, wurden die Grafikroutinen get_image und PUT_IMAGE geschrieben, die sie in diesem Artikel finden.

Es genügt, zwei Eckpunkte und eine String-Variable als Parameter an GET zu übertragen, um ein Bildschirmrechteck im Speicher einzulesen. SGET braucht für das Einlesen des gesamten Schirmes eben nur den String-Namen. Namentlich unter GEM fühlt man den Mangel an derartigen Grafikroutinen am meisten. Dialogboxen und Fenster läßt GEM sich nur mit Mühe entlocken. Das Restaurieren der Hintergründe, nachdem die Objekte wieder verschwunden sind, wird dem Programmierer überlassen.

Die hier vorgestellten Routinen get_image und put_image sind entworfen worden, um, in einem C-Programm eingebunden, exakt so zu funktionieren wie die BASIC-Routinen. Auch die Parameterübergabe ist genau dieselbe. Im monatelangen Gebrauch haben sie sich als ‘full-proof’ erwiesen (und hoffentlich bleibt es so...). Das heißt, die get_image-Routine überprüft die Koordinatenparameter. Falls sie gleich Null oder negativ sind, werden sie negiert, und ein Zeiger, der nomalerweise auf eine Pufferadresse zeigt, zeigt dann auf Null, damit der Rechner später nicht abstürzt.

Es ist die VDI-Routine vro_cpyfm, die beim pixelweisen Verschieben von Grafikblöcken eine Hauptrolle spielt. Sie ist es, die eingesetzt wird, wenn Bit-Blöcke rasch von einem Platz im RAM an einen anderen kopiert werden müssen. Gerade deswegen kann sie viel Unheil anrichten, denn das VDI-Clipping (Abschneiden) wird nicht berücksichtigt. Vorsicht ist geboten, aber auch in dieser Hinsicht droht dem Programmierer, der das Routinenpaar get/put_image in seinen Programmen verwendet, keine Gefahr. Katastrophen wie das Überschreiben von Daten in der RAM-Disk sind wegen des ‘vertikalen Clippings’ innerhalb der Routine get_image ausgeschlossen. Das wird im Demo-Programm gezeigt. Falls man versucht, mit der Maus einen Ausschnitt zu zeichnen, der höher ist als die Arbeitsoberfläche des Desktops, gibt’s keine Kopie.

Es muß betont werden, daß es im Grunde genommen keinen Unterschied gibt zwischen Bildspeicher und ‘normalem’ RAM. Sonst wäre es nie möglich, mit mehreren getrennten Bildschirmen zu arbeiten, wie z.B. das Zeichenprogramm DEGAS ELITE es tut. Wenn es sich um ein Grafikausgabegerät handelt, sei es Bildschirm oder Drucker, kann am besten von ‘Rastern’ gesprochen werden. Raster legen fest, wieviele Pixel in der Breite und in der Höhe gebraucht werden, wieviele Bits die Farbe eines einzigen Pixels definieren, wieviele (Computer-) Wörter in der Breite benötigt werden usw. Jeder RAM-Bereich kann also als Grafikspeicher dienen, vorausgesetzt, daß das Raster durch eine Struktur genau beschrieben wird. Eine solche Struktur wird ein ‘MFDB’, Memory Form Definition-Block genannt; sie ist in VDI.H (oder GEMDEFS.H beim Megamax C-Compiler oder GEMLIB.H Lattice C, hier heißt sie FDB) enthalten.

Kopieren bringt immer ein WOHER und ein WOHIN mit sich: Deshalb braucht ein Kopierverfahren zwei MFDB-Strukturen. Eine für die Definition des Quell-, die zweite für die des Zielrasters. Das heißt daß EINE dieser Strukturen auf die Adresse des Speicherbereiches, der als ein Raster definiert ist, zeigt. Die beiden Zeiger werden als Parameter an die VDI-Rasterkopierroutine vro_cpyfm übergeben. Die Routine kann auf 16 verschiedene Weisen ihre Kopien ‘schreiben’: die bekanntesten Schreib-Modi sind ‘Überschreiben’ (3), ‘XOR’en (6), Transparent (7) und Invers-Transparent (13) (XOR ist Graphmode 3 von GFA-BASIC.). Außerdem braucht die Routine ein Feld, das mit den Koordinaten der beiden gleichgroßen Ausschnitte gespeist wird. Im Quelltext ist es als pxyar[8] deklariert worden.

Bild 1: Mit einen Gummiband kann ein Ausschnitt gewählt werden.

Der Quelltext

Es sind, wie man dort sehen kann, zwei Strukturen aufgeführt worden. Die eine ist die oben erwähnte MFDB-Struktur. Die zweite heißt IMAGE - eine selbstdefinierte Struktur, worin die Breite, Höhe und die Speicheradresse des ausgeschnittenen Rechtecks eingetragen werden. Mit der Deklaration

IMAGE *bild, *hintergrund, *desktop;

wird Speicherplatz angefordert für z.B. drei Strukturen namens bild bzw. hintergrund bzw. desktop. Das geschieht für jeden Bit-Block auf folgende Weise:

bild = (IMAGE *) malloc(sizeof(IMAGE)); 
hintergrund = (IMAGE *) malloc(sizeof(IMAGE)); 
desktop = (IMAGE *) malloc(sizeof(IMAGE));

Die Bibliotheksroutine malloc belegt Speicherplatz für dynamische Variablen und liefert einen Zeiger zurück, der auf die Adresse dieses Blocks zeigt. In Sachen Sparsamkeit und Flexibilität ist sie ideal. Denn man braucht kein auf eine feste Größe fixiertes Feld, das für die Abspeicherung der Ausschnitte entweder zu groß oder zu klein sein würde. Die Funktion malloc liefert genau das, was an Bytes nötig ist. Mit free kann der belegte Bereich wieder freigegeben werden.

Nachdem die Funktion MFDB-init aufgerufen worden ist, steht Ihnen nichts im Wege, get_image und put_image für Ihre Zwecke einzusetzen. Die Syntax ist den GFA-BASIC GET/PUT-Befehlen ähnlich.

get_image(x0,y0,x1,y1,bild);

liest das Bildschirmrechteck als Bit-Muster in den Speicherbereich ein, auf den der Zeiger der IMAGE-Struktur zeigt. Auch dieser Zeiger wurde durch einen malloc-Aufruf zurückgeliefert.

put_image(x0,y0,bild,S_ONLY);

stellt das eingelesene Rechteck am Koordinatenpunkt x0,y0 wieder auf dem Bildschirm dar. S_ONLY ist ein numerischer Ausdruck, wofür auch ein Wert von 0 bis 15 eingetragen werden kann, und ist in VDI.H (oder OBDEFS.H beim Megamax C-Compiler oder GEMLIB.H bei Lattice C) definiert.

Da put_image seine Arbeit getan hat, kann der belegte Speicherplatz (es könnten immerhin 32000 Bytes sein) freigegeben werden. Das heißt, falls der Compiler es Ihnen erlaubt, denn z.B. Megamax ist hier meiner Meinung nach nicht fehlerfrei. Der Aufruf

free(bild -> getbuf);

gibt den reservierten Platz frei. Sicherer ist es zu schreiben:

if(bild -> getbuf) > 0) free(bild -> getbuf);

da jetzt das Flag berücksichtigt wird, das, falls es 0 ist, signalisiert, daß einer der oben genannten Fehler passiert ist. Gibt der Programmierer nicht sofort nach put_image den Platz frei, kann er (sie) das 'Bildchen' innerhalb jeder Routine ‘PUT’ten. Es ist sozusagen statisch zu verwenden. In diesem Fall sollte aber nicht vergessen werden, ein free vor dem Ende des Programmes durchzuführen.

Die Frage, wie diese Routinen für das Retten der Hintergründe von Dialogboxen zu benutzen sind, läßt sich einfach beantworten. GEM bietet die Funktion form_center an, um ein Objekt auf dem Schirm zu zentrieren. Falls die Integervariablen x,y,w,h deklariert sind, kann z.B. eine Dialogbox mit Baumadresse meldung folgenderweise zentriert werden:

form_center(meldung,&x,&y,&w,&h);

Die Variable w steht für Breite (width), h für Höhe (height), also sind die Eckpunkte der Box, deren Hintergrund gerettet werden soll, bekannt. Mit

get_image(x,y,x+w,y+h,box);

legen Sie den Hintergrund im Speicher ab. Von heute ab kann auch in C sauber programmiert werden. Zum Schluß noch einige Tips für diejenigen, die get/put_image als linkbare Bibliotheksmodule verwenden wollen. Die ersten drei im Quelltext aufgeführten Header-Dateien sind, mit den globalen Variablen, unentbehrlich. Selbstverständlich sollte auch die IMAGE-Struktur deklariert werden. Aber, falls Ihnen die Routinen gefallen, warum diese Struktur nicht in VDI.H (oder GEMDEFS.H oder ..) z.B. unmittelbar über der MFDB-Struktur einfügen?

Bild 2: Das Demoprogramm gibt den gewählten Ausschnitt mehrfach aus.
/***********************************************/ 
/*                  GET_PUT.C                  */
/* Ein Programm in Turbo-C mit 2 Grafikroutinen*/ 
/* die den C-Programmierern die GFA-BASIC      */
/* Graphikbefehle GET, PUT, SGET und SPUT zur  */ 
/* Verfügung stellt. Läuft in jeder Auflösung. */ 
/* Autor: Egbert Börners, Amsterdam. Mai 1989  */ 
/*          (c) MAXON Computer 1989            */
/***********************************************/

#include <aes.h>        /* diese erste drei    */
#include <vdi.h>        /* bitte               */
#include <stdlib.h>     /* immer einbinden     */
#include <string.h>
#include <stdio.h>

typedef struct          /* eine Struktur -      */
{                       /* hierin werden        */
    int breite;         /* abgelegt: Breite,    */
    int hoehe;          /* Höhe d.einzulesenden */
    char *getbuf;       /* Rechtecks und die    */
} IMAGE;                /* Adresse im Speicher  */
                        /* bevor Funktions-     */
                        /* prototypen defi-     */
                        /* niert werden.        */
/* -------------------------------------------- */
/*              Funktionsprototypen             */
/* -------------------------------------------- */
int open_vwork(void);   /* Wkst öffnen          */
void close_vwork(void); /* und schliePen        */
void graphik(void);     /* nur Demozwecke       */
void MFDB_init(void);
void get_image(int ,int ,int ,int ,IMAGE * ); 
void put_image(int ,int ,IMAGE * ,int );
/* -------------------------------------------- */
/*          Globale Variablen GEM               */
/* -------------------------------------------- */

int contrl[12],intin[128],intout[128],ptsin[128],ptsout[128];

int work_in[12],work_out[57]; 
int handle,gl_apid;
int gl_hchar,gl_wchar,gl_hbox,gl_wbox;

/* --------------------------------------------- */
/* Glob. Variablen f. "get_image" u. "put_image" */
/* --------------------------------------------- */

MFDB schirm,getbuf; /* siehe Text. */

IMAGE *bild,*torte,*desktop; /* IMAGE-zeiger */

int Hires,Medres,Lowres; /* Auflösungsvar.*/
int pixhoehe; /* Höhe des Ausschnitts */
int schirm_vertik; /* Vert.Auf1.Bildschirm */
int pxyar[8]; /* für Koordinaten. */
int mode; /* für log.Verknüpfung: */
/* Die meist gebrauchte sind: */
/* S_ONLY 3 überschreiben */
/* S_XOR_D 6 Grapm.3 GFA */
/* S_OR_D 7 transparent */
/* NOTS_OR_D 13 inv. transpar. */

#define TRUE        1
#define FALSE       0

/***********************************************/

void main()
{
    if(open_vwork() == TRUE) {
        #define max_breite (640-Lowres*320)-lastw+1 
        int x0,y0,x1,y1;
        int mousex,mousey,lastw,lasth,button,dum; 
    int x,y;
    char *meldung[] = {
        "[0][ | Desktop eingelesen. |][ Ahh ]",
        "[0][ | Torte auch... |] [ Hmm ]",
        "[0][ ][Ende|Weiter]",
        "[0][| GrüPe aus ... |][ Amsterdam ]"
        };
    /* f.jede IMAGE-Struktur Platz reservieren */ 
    bild = (IMAGE *) malloc(sizeof(IMAGE)); 
    torte = (IMAGE *) malloc(sizeof(IMAGE)); 
    desktop = (IMAGE *) malloc(sizeof(IMAGE));

    MFDB_init(); /* MFDB-Struktur init. */
    graf_mouse(M_OFF, 0x0L) ; /* Maus raus. */
    x = 639-Lowres*320; /* Nur für Demo- */
    y = 399-Lowres*200-Medres*200; /* zwecke */

    get_image (0,0, x, y, desktop) ; /* Desktop einlesen */
    form_alert(1, meldung[0]);
    graphik(); /* Demographik darstellen */
    get_image(0,0,x,y,torte); /* Torte einlesen*/ 
    form_alert(1, meldung[1]);
    graf_mouse(THIN_CROSS,0); /* Fadenkreuz */
    graf_mouse(M_ON,0);

    while(TRUE) {
                        /* Mausknopf? */ 
        vq_mouse(handle,&button,&mousex,(mousey); 
        if( button == 1 ) {
            dum = lastw = lasth — 0;
            evnt_timer(300,0); /* bremsen */
            /* Rechteck zeichnen mit 'Gummibox' */ 
            graf_rubberbox(mousex,mousey,dum,dum, (lastw, &lasth);

            /* dies sind die Koordinaten... */
            x0=mousex; y0=mousey; x1=x0+lastw-l; 
            y1 = y0+lasth-1; 
            graf_mouse(M_OFF,0x0L); 
            evnt_timer(300,0);
                        /* Bild abspeichern */ 
            get_image(x0,y0,x1,y1,bild);
                        /* Desktop 'PUT'ten */ 
            put_image(0,0,desktop,S_ONLY);

            if((bild ->getbuf >0) {/*kein Fehler? */ 
                y = 12+Hires*7;
                for(x=0; x<max_breite; x += lastw) 
                    put_image(x,y,bild,S_ONLY);

                free(bild -> getbuf); /* Speicher */ 
            } /* wieder freigeben */

            button =0;
            graf_mouse(M_ON,0x0L);

            if( form_alert(1 , meldung[2]) == 1) 
            break; /* Ende: aus Schleife */

            graf_mouse(M_OFF, 0); 
            put_image(0,0,torte,S_ONLY); 
            graf_mouse(M_ON,0); 
            graf_mouse(THIN_CROSS, 0);
        } /* if */
    } /* while */

    free(torte -> getbuf); /* alle reserv.Platz */ 
    free(desktop -> getbuf); /* wieder frei */
    form_alert(1 , meldung[3]); 
    close_vwork();
}
else
    printf("Fehler bei der Programminitialisierung");
exit (0);
}

/* ----------------------------------------- */
/*              open_vwork()                 */
/*          Arbeitsstation öffnen            */
/* ----------------------------------------- */

int open_vwork()
{
    register int i;
            /* anmelden bei AES */
    if((gl_apid = appl_init()) != -1) {

        for(i = 1; i < 10; work_in[i++] = 0); 
        work_in[10] = 2;
        handle = graf_handle(&gl_wchar,&gl_hchar, 
                             &gl_wbox, &gl_hbox);
        work_in[0] = handle;
        v_opnvwk(work_in, &handle, work_out); 
        return(TRUE); /* hat geklappt */
    }
    else
        return(FALSE); /* oder nicht */
}

/* -------------------------------------------- */
/*                  close_vwork()               */
/*          Arbeitsstation wieder schließen     */
/* -------------------------------------------- */

void close_vwork()
{
    v_clsvwk(handle); /* Station schließen */
    appl_exit(); /* abmelden bei AES */
}

/* -------------------------------------------- */
/*              void graphik(void)              */
/*  Routine die Torte auf dem Schirm darstellt  */ 
/* -------------------------------------------- */

void graphik(void)
{
    int i, farbe;
    /* Auflösungsabh. Korrekturvariabl.f. Demo */
    int x_korr = Lowres*320;
    int y_korr = (Lowres || Medres)*200;
    int x           = (640-x_korr)/2; /* Mittelpunkt*/ 
    int y           = (400-y_korr)/2; /* d.Schirmes */ 
    int radius      = 170-85*Lowres;
    vswr_mode(handle, MD_REPLACE);    /* überschreib*/ 
    vsf_color(handle, BLACK);         /* Füllfarbe */
    vsf_interior(handle, 3);          /* Fülltyp */

    for(i=0, farbe=1; i<3600; i += 450, farbe ++) {
        vsf_style(handle, (i+450)/450);
        if(Lowres | Medres) {       /* Farbbetrieb*/
            if((farbe % 4) ==0) /* farbe MOD 4*/
                farbe = 1;
            vsf_color(handle, farbe); /* Füllfarbe */
        }
        v_pie(handle, x, y, radius, i, i+400); /* Torte! */
    }
    vst_color(handle, BLACK); 
    vswr_mode(handle, MD_TRANS); 
    if (Hires) 
        y = 390; 
    else
        y = 190; 
    v_gtext(handle, x-135, y,"Bitte zeichnen Sie eine <Rubberbox>");
}

/***********************************************/ 
/*                  MFDB_init-routine          */
/* läPt die MFDB-Struktur 'schirm' automatisch */ 
/* Auflösungsabhängig ausfüllen, da Adressen-  */ 
/* Mitglied gleich 0 ist. I.d.Struktur 'getbuf'*/ 
/* (Puffer für GET) kann nur d. Anzahl der Bit-*/ 
/* Ebenen geschrieben werden. D. Routine bitte */ 
/* aufrufen bevor "get_image" oder "put_image".*/ 
/***********************************************/

void MFDB_init()
{
    schirm.fd_addr =0; /* Startadresse d. Bild-*/
                       /* schirms. ( = Flag) */

    schirm_vertik = work_out[1]; /* y-Auflösung*/

    /* Information über Anzahl der Bit-Ebenen (= */
    /* Anzahl Bits pro Pixel{färbe)) wird bei VDI */ 
    /* eingeholt. Die »erweiterte« Information */ 
    /* wird nach Aufruf der Funktion 'vq_extend' */ 
    /* in work_out[4] geschrieben. */

    vq_extnd(handle, 1, work_out); 
    getbuf.fd_nplanes = schirm.fd_nplanes = work_out[4];

    /* Die akt. Auflösung läPt sich so herleiten */
    switch (work_out[4]) {
        case 4: Lowres = TRUE; break; 
        case 2: Medres = TRUE; break; 
        case 1: Hires = TRUE;
    }
}

/A**********************************************/
/*             get_image - Routine             */
/* Routine liest ein Bildschirmrechteck ein    */
/* in d. Speicherbereich der m. "malloc" belegt*/
/* wird. Routine braucht als Parameter die Eck-*/
/* punkte des Quellrechtecks und d. Zeiger auf */
/*               eine IMAGE-Struktur.          */
/***********************************************/

void get_image(int x0,int y0,int x1,int y1,
               IMAGE * g_image)
{
    char *memory; /* Zeiger für "malloc" */
    int anzahl_byte,pixbreite,pix;
                /* Anzahl Pixel pro Byte*/ 
    pix = 8 / schirm.fd_nplanes;
    graf_mouse(256,0x0L); /* Maus aus */

    pixbreite=((x1-x0)+16) & 0xFFF0; /* d. 16 teilbar */
    pixhoehe = y1-y0;   /* Pixelhöhe d.Rechtecks*/

    if(pixbreite <=0 || pixhoehe <=0)
        g_image -> getbuf =0; /* keine Bomben bitte*/ 
    else {
        /* Bildschirmkoordinaten (= Quelle) und */
        pxyar[0]=x0; pxyar[1]=y0; pxyar[2]=x1;
        pxyar[3]=y1;
        /* Zieleckp. (linksoben anfangen) eintragen*/ 
        pxyar[4]=0; pxyar[5]=0; pxyar[6]=x1-x0; 
        pxyar[7]=pixhoehe;

        /* Anzahl Bytes des Rechtecks ausrechnen */ 
        anzahl_byte=(pixbreite / pix)*(pixhoehe + 1) + 8;


        /*          RAM-Platz reservieren       */
        memory = malloc(anzahl_byte*sizeof(char));

        if ( memory > 0 ) { /* falls Platz ist */

            g_image ->breite = x1-x0; /* Br. in IMAGE */ 
            g_image ->hoehe = pixhoehe; /* eintragen, Höhe */
            g_image ->getbuf = memory; /* u. die ” Adresse */
                                        /* des Bereichs*/

            getbuf.fd_w = pixbreite; /* Gleiche f. MFDB- */
            getbuf.fd_h = pixhoehe+1; /* Struktur die Ziel-*/
            getbuf.fd_addr = memory; /* raster beschreibt.*/ 
            getbuf.fd_wdwidth = pixbreite/16;
                        /* Rasterbreite in Worte dazu */

            /* die VDI-Pixelkopierroutine aufrufen:     */
            /*              von: Schirm nach: Puffer    */
            vro_cpyfm(handle, 3, pxyar, &schirm, &getbuf);
        }
        else { /* falls kein Platz mehr frei *
            form_alert(1," [0] [| Nicht genügend Speicher ...|][OK]"); 
            g_image ->getbuf = 0; /* Flag 1 */
        }  /* else */
    } /* if */

    graf_mouse(257,0x0L); /*Maus an */
}

/***********************************************/ 
/*              put_image - Routine            */
/* Schreibt eingelesenes Rechteck an beliebiger*/ 
/* Stelle wieder auf dem Bildschirm. D. Routine*/ 
/* braucht als Parameter d.linken oberen Ecke d*/ 
/* Zielrechtecks, einen Zeiger auf IMAGE-Struk-*/ 
/* tur, und den Modus für logische Verknüpfung */ 
/* des Quell- und Zielrasters.                 */
/***********************************************/ 

void put_image (int x0,int y0,IMAGE *p_image,int mode)
{
    if((p_image ->getbuf >0) && /* kein Flag*/
       (y0 +pixhoehe <= schirm_vertik)) /* Clip */
    {
        graf_mouse(256,0x0L);
                                 /* Array ausfüllen */ 
        pxyar[0]=0;              /* Quelle ist Puffr*/
        pxyar[1]=0;
        pxyar[2]= primage ->breite; /* Breite u.Höhe*/
        pxyar[3]= p_image ->hoehe;  /* in IMAGE-    */
                                    /* Struktur les */ 
        pxyar[4]=x0;                /* Ziel ist nun */
        pxyar[5]=y0;                /* auf Schirm.  */
        pxyar[6]=x0+pxyar[2];       /*gleiche Breite*/
        pxyar[7]=y0+pxyar[3];       /* und Höhe.    */

        /* MFDB-Struktur, jetzt Quelle des Kopier-  */
        /* Verfahrens, ausfüllen. Erstes Mitglied:  */
        /* Zeiger auf Adresse des abgesp. Rechtecks */
        getbuf.fd_addr = p_image -> getbuf; 
        getbuf.fd_w = pxyar[2]+16; /* dann Breite */ 
        getbuf.fd_h = pxyar[3]+1; /* und Höhe. */
        getbuf.fd_wdwidth = getbuf.fd_w/16; /*Breite*/
                                /* in Worte. */ 
        /* die Pixelkopierroutine aufrufen:
                        von: Puffer nach: Schirm */ 
        vro_cpyfm(handle, mode, pxyar, &getbuf, &schirm);

        graf_mouse(257,0x0L); /* Maus an */
    }
}

Egbert Börners
Aus: ST-Computer 02 / 1990, Seite 91

Links

Copyright-Bestimmungen: siehe Über diese Seite