Grafik i C

Tags:    c++
Skrevet af Bruger #24 @ 17.06.2001
Grafik i C

Jeg har fået at vide der var lidt stemning for, at vi kunne lave lidt grundlæggende grafik programmering. Til start vil jeg sige, det er til dos (fordi det er nemt og hurtigt at sætte op og komme igang). Min C-compiler er GNU compileren DJGPP, som er sat op med pgcc, og Rhide som IDE.

Denne kan hentes her : www.delorie.com

Via en "Zip-picker" kan man sætte systemet op så du får de filer DU skal bruge, og man får en god instruktion til, hvordan man sætter compileren op (der er lige nogle småting, men de er hurtigt overstået). Jeg vil mene, at efter man har læst denne tutorial, vil man være istand til at lave en simpel ild-rutine, men først skal vi lige se på hvordan man sætter en skærm, og en palette op.

Vi vælger skærm-mode 13h (fordi den har 256 farver, men dog kun en opløsning på 320*200) - huske på, at det her er kun for at forklarer princippet i det, hvis man er god og vil kode under windows eller lign, laves denne effekt på nøjagtig sammen måde, man sætter bare sit window op anderledes...

Lad det være sagt med det samme.. I får aldrig mig til at skrive direkte til skærmen, af flere årsager (flicker, palette støj etc.), så vi bruger en virtuel skærm, eller en dobbelt-buffer.

  • Hvad skal vi bruge for at lave ild.
  • En rutine til at sætte skærm-modes.
  • En rutine til at sætte paletten op. (ikke nødvendigt, hvis det var i true-color, kommer senere)
  • En rutine til at lave random dots.
  • En blur rutine.


Og hvordan så det ?

Mest naturligt ville det nok være at sætte skærmen op først :)

For at man hurtigt kan komme igang, vil jeg vise en rutine til det (man behøver ikke lave det lige præcist sådan her). Som alt andet inden for programmering, laver alle det på sin egne særpræget måde.. (næsten ihvertfald)
/************************************************************************
* SetGraphics function..Set skærm-mode *
************************************************************************/
void SetGraphics(int mode){
__dpmi_regs r;
r.x.ax = mode;
__dpmi_int(0x10,&r);
}

Vi bruger int. 10 til at skifte skærm-mode. - mere vil jeg ikke sige om dette, andet end.. læs selv :) Int's og dpmi er store områder...s elv om __dpmi_regs bare er en struct med realmode regs, ligger der mere bag... nok om det.

Hvis man har kodet bare lidt C før, vil man hurtigt se, at for at gå over i screenmode 13, kalder man denne funktion sådan her:
SetGraphics(0x13);

Det kunne næsten ikke være nemmere....

Så kommer vi til paletten....(vi vil gerne have andre en std. farverne..) Her vil jeg igen vise en rutine til dette :

/************************************************************************
* SetCol. "Grey"-scale palette håndtering *
************************************************************************/
void SetCol(Byte col, Byte r, Byte g, Byte b){
outportb(0x3c8, col);
outportb(0x3c9, r>>2);
outportb(0x3c9, g>>2);
outportb(0x3c9, b>>2);
}

Man kan i f.eks. "PC-intern" læse om disse registre, så der overlader jeg til jer selv. Den opmærksomme, ville sige.."erhmmmm... der er sq da ikke noget der hedder Byte i C ???".... Og der er rigtigt.... det er en typedef jeg bruger, fordi jeg ikke gider skrive unsigned char hele tiden. så den tager vi lige med :

typedef unsigned char Byte;

Sådan. col = inx i paletten... altså nummeret på den farve vi vil ændre... R, G og B er naturligvis farve-komponenterne. For at vi kan vælge farver mellem 0 og 255 i hver farvekomponent, bliver vi nød til at gøre følgende.

r>>2;

Dette betyder at vi shifter værdien af r, 2 gange til venstre, hvilket er det samme som at dividerer med 4. (alle der har haft lidt asm. programmering, eller digital elektronik, (binære tal) ville vide dette)...Grunden til dette er, at VGA's palette gåe kun mellem 0 og 63 i værdi, i hvert farve-komponent (skod, men sådan er det).

Vi sætter en simpel grå-tone palette op...

int x;
for(x=0;x<255;x++) SetCol(x,x,x,x);

Man kan selv rode med at sætte smarte paletter op, eller lave en loader, til en man kan gemme fra er grafik program.. De fleste ville nok lave en interploerings rutine...

Til sidst mangler vi en rutine til at kopierer vores virtuelle skærm til ..ehh.. den rigtige skærm..
/************************************************************************
* Copybuf. *
************************************************************************/
void CopyBuf(Byte *buf){
Byte *videoptr = (Byte *)0xA0000;
movedata(_my_ds(),(unsigned) buf, _dos_ds,(unsigned) videoptr, 320*200);
}

Det eneste jeg har at sige til ovenstående er, at skærmens hukommelse starter på 0xA0000....

Det var hvad vi skulle vide omkring hvordan man sætter skærmen op... vi skal lige se et eksempel....

#include /* includes */
#include 
#include 
#include 
#include 
#include 
#include 

typedef unsigned char Byte;

Byte Ckybuf[320*200]; // Her har vi vores virtuelle skærm...
// et array på skærmens størrelse..
int main(void){
int x;

SetGraphics(0x13); // hop over i screenmode 13

for(x=0;x<255;x++) SelCol(x,x,x,x);

memset(Ckybuf,0,320*200);// Her sletter vi vores buffer, for at 
// undgå tilfældig data på skærmen.
// Her sætter vi en prik på skærmen.
Ckybuf[160+100*320] = 255;
// Og venter på, en eller anden trykker på en tast.

CopyBuf(Ckybuf);
while(!kbhit());
SetGraphics(0x3); // tilbage til textmode igen.

return(0);
}

Husk, at grafik funktionerne self skal være med.... og hvis de ligger under main, skal de prototype'es.

Hvis man ikke ved hvordam man sætter en pixel, uden at bruge putpixel, skal vi også liiiige ha' det på det rene....

Vi har en x og en y værdi....

Vi regner position ud...

Lad os sige y = 100... og skræmen er 320 bred... De skærmen er en liniear buffer, og ikke et 2D array (det vil sige hele skærmen ligge i een lang linie i hukommelsen) beregner vi position på følgende måde:

position = y* eller position = y*320

Jeg håber det virker logisk... vi beregner faktisk bare antal linier vi "hopper" ned på skærmen... så kommer X... den er heldigvi nem at beregne, da vi bare plus'er den til position...så får vi:

position = x+y*320;

så hvis x = 160 ville pixlen blive tegnet midt på skærmen....dette kunne gøres sådan her:

Ckybuf[position] = 255;
eller
Ckybuf[x+y*320] = 255;

Nu til det sjove :)

Jeg synes vi skal starte med en Blur funktion. Hvordan virker blur.....

Blur går fra pixel til pixel i vores virtuelle buffer, og tager gennemsnits-værdien af omkringliggende pixels.. over, under, venster og højre. (naturligvis ved at lægge dem sammen, og div med 4...)

Hvis man ikke er bekendt med pointere, har jeg skrevet en pointer-tut... (meeen den er...på halv mange sider...) uden pointere bliver denne effekt ikke så "fed" at kode...men det kan da gøres med for-løkker....

Når man looper starter man gerne i y=1 (og ikke y=0, fordi pixlen over den man er på, er uden for skærmen, og kan indeholde hvad som helst.), til (i dette tilfælde) y=198, fordi hvis man kører på sidste scanlinie, ville pixlen under være uden for skærmen...og kan være hvad som helst..

Se.. nu kunne jeg bare vise hvordan man kunne gøre.. men det vil jeg lade jer om.. og så kan jeg få lidt feedback, og se om folk har fat i den lange ende (altså hjælpe hvis nødvendigt)...

Ok.. jeg gir et eksempel...

man kunne sige:

Byte *vs = Ckybuf+320;
int x = 320*198;
int val;

do{
val = *(vs);
val+= *(vs+319);
val+= *(vs+321);
val+= *(vs+640);
*(vs+320) = val/4;
vs++;
}while(--x);

Ovenstående kan optimeres, men det kan vi tage i en anden tut... For at lave det til ild, sætter man resultatet i pixlen ovenover den man normalt ville sætte den i ved alm. blur.. så vi laver rutinene lidt om:

do{
val = *(vs);
val+= *(vs+319);
val+= *(vs+321);
val+= *(vs+640);
*(vs++) = val/4;
}while(--x);

Nu mangler vi kun nogle "random pixels".....

Det kan også gøres på flere måder...

Vi bruger rand() funktionen... som giver et tal mellem 0 og noget jeg ikke kan huske...hmmmm Anyway, så bruger vi modulos, så vi med x får en værdi mellem 0 og 319, og og y mellem 0 og 3 :

x=rand()%320;
y=rand()%4;

er er hvor på x denne prik er, og y er hvilken linie prikken skal befinde sig på.... Dette ligger vi til et index i bufferen... f.eks

index = 195*320;
buf[index+(x+y*320)] = rcol;

Her er rcol en random farve...men vi vil kun have den til at være mellem 200 og 255 så vi siger:

rcol = (rand()%55)+200;

Tada.... det skulle være det.. nu kan vi lave ild..........så nu har vi:

#include /* includes */
#include 
#include 
#include 
#include 
#include 
#include 


typedef unsigned char Byte;


void SetGraphics(int mode); // prototypes..
void SetCol(Byte col, Byte r, Byte g, Byte b);
void CopyBuf(Byte *buf);
void Blur_buf(Byte *buf);
void Randot(Byte *buf);

Byte Ckybuf[320*200]; // Her har vi vores virtuelle skærm...
// et array på skærmens størrelse..
int main(void){
int x;

SetGraphics(0x13); // hop over i screenmode 13

for(x=0;x<255;x++) SetCol(x,x,x,x);

memset(Ckybuf,0,320*200);// Her sletter vi vores buffer, for at 
// undgå tilfældig data på skærmen.
// Her sætter vi en prik på skærmen.

do{
Randot(Ckybuf); // sæt prikker
Blur_buf(Ckybuf); // brænd buffer
Ckybuf[160+100*320] = 250; // bare for sjov
CopyBuf(Ckybuf); // kopier buffer til skærm..
}while(!kbhit()); // Og venter på, en eller anden trykker på en tast.

SetGraphics(0x3); // tilbage til textmode igen.

return(0);
}

void Blur_buf(Byte *buf){
Byte *vs = buf+320;
int x = 320*198;
int val;

do{
val = *(vs); // hent værdierne
val+= *(vs+319);
val+= *(vs+321);
val+= *(vs+640);
*(vs++) = val>>2; // div med 4
}while(--x); // loop hele buffer igennem..
}

void Randot(Byte *buf){
Byte *d = buf+190*320;
int x,y,l=100;

do{
x=rand()%320; // find nogle tilfældige værdier
y=rand()%4;

*(d+(x+y*320)) = (rand()%55)+200; // og en halv tilfældig farve...
}while(--l);

}


/************************************************************************
* SetGraphics function..Select screen mode. in this case - mode 13*
************************************************************************/
void SetGraphics(int mode){
__dpmi_regs r;
r.x.ax = mode;
__dpmi_int(0x10,&r);
}
/************************************************************************
* SetCol. "Grey"-scale palette handler *
************************************************************************/
void SetCol(Byte col, Byte r, Byte g, Byte b){
outportb(0x3c8, col);
outportb(0x3c9, r>>2);
outportb(0x3c9, g>>2);
outportb(0x3c9, b>>2);
}
/************************************************************************
* Copybuf.This copy the chunkybuf(virtuel screen buffer)to the screen*
************************************************************************/
void CopyBuf(Byte *buf){
Byte *videoptr = (Byte *)0xA0000;
movedata(_my_ds(),(unsigned) buf, _dos_ds,(unsigned) videoptr, 320*200);
}

Jeg ved ikke om det var for svært....for let... for uklart... for ?

Men jeg vil naturligvis gerne have feedback...meget gerne... høre jeres mening...specielt hvis jeg skal lave flere af denne slags.

Der er også noget med compiler-options...det kan man læse en del om på delorie.....


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (4)

User
Bruger #3765 @ 25.04.03 16:29
Har du taget stoffer? Det virker som om du tåger rundt i det. Det kræver en smule overblik når man forsøger at læse din tutorial - men læser man den flere gange begynder man at forstå - Nice work :D
User
Bruger #2330 @ 04.05.03 12:14
Hvilke includes skal man så bruge
User
Bruger #5369 @ 27.01.05 22:16
Øhh.. De der Includes?
User
Bruger #3861 @ 02.04.08 14:47
Øhh ja Includes?
Bruger Dev-C++

Fejl ved første del af koderne:
`__dpmi_regs' undeclared (first use this function)
`r' undeclared (first use this function)
`__dpmi_int' undeclared (first use this function)
Du skal være logget ind for at skrive en kommentar.
t