Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Grafičko programiranje


© 1998, Particle

http://www.theparticle.com

Uvod...

Osnove (pokretanje)...


Pre nego što preduboko zađemo u temu grafičkog programiranja, hteo bih da ilustrujem neke osnove. Ako već znate kako da postavite piksele na ekranu, promenite video režim i sl, onda jednostavno preskočite ovaj odeljak. Ovaj pasus je tu da bi vas naučio da eksperimentišete. Neću samo da vam dam algoritam za crtanje linije, bez metoda da je zapravo nacrtate.

Narednih nekoliko pasusa mogu da zavise od sistema, hardvera, kompajlera itd, ali to je uglavnom problem s grafikom. Gotovo je nemoguće stvoriti grafiku 100% nezavisnu od sistema (osim Jave).

Crtanje piksela...

Vaš monitor je dvodimenzionalan, svaki piksel na ekranu ima neku lokaciju; ilustrovaćemo ih vrednostima x i y. Gde je x horizontalni ofset s leve strane ekrana, a y je vertikalni ofset odozgo (ili ponekad odozdo), na ekranu. Dakle, lokacija x = 0, i y = 0, je gornji levi ugao ekrana. Ako znate x i y, možete posmatrati ekran kao dvodimenzionalni niz. Sa x horizontalnih lokacija i y vertikalnih lokacija. Svaka vrednost unutar niza predstavlja piksel. Da biste iscrtali piksel na ekranu tako da je x= 70 i y = 100, treba da stavite podatke o pikselima u taj dvodimenzionalni niz na lokaciji [100] [70].

Sada, hajde da sve učinimo još apstraktijim! Razmislite o svom ekranu kao o jednodimenzionalnom nizu; kao da počinje u gornjem levom uglu i ide do kraja linije, pa ponovo počinje u drugom redu i nastavlja tako sve dok ne udari u donji desni ugao. Da bismo koristili ovaj način crtanja piksela, takođe ćemo morati da znamo širinu ekrana. Srećom, u najvećem broju slučajeva znaćemo tačnu širinu i visinu ekrana. Na primer, nazovimo naš jednodimenzionalni niz “ekran”; da bismo nacrtali piksel u boji "boja" na lokaciji x=70, y=100, uradili bismo nešto ovako: ekran [y* širina + x*]= boja Kao što možete videti (ili naslutiti), upravo je ista stvar kao ekran[y] [x] = boja, samo što širinu određujemo ručno.

Većinu vremena, kada radimo s grafikom, koristimo pokazivač. Pokazivač može biti u memoriji, ili direktno na video uređaju. Naš zadatak da osmislimo grafiku nema veze s tim šta pokazivač pokazuje, samo treba da nacrtamo piksele u taj pokazivač (ili njegov ofset) kao da je to pravi ekran. Kasnije ću vam pokazati gde crtanje u memoriji može biti veoma korisno.

DOS Grafika...

Moderni operativni sistemi prognali su crtanje vezujući ga za uređaje. Međutim, još uvek je moguće uraditi grafiku u realnom režimu DOS programa (i sa malo lukavstva, pod zaštićenim režimom). Za sada, pretpostavimo da radimo u Real Mode DOS, pomoću Borland C + + v3.1 (ili DOS verzija Turbo C + +). Ne, windows kompajleri neće raditi!

Pre nego što uradimo bilo šta u vezi s grafikom, moramo da podesimo grafički režim. To radimo tako što pozivamo BIOS. Funkcija za promenu režima je AH = 00h, AL = režim, koristeći 0x10 prekid. Ako niste upoznati sa asemblero, ili niskim nivoom programiranja, ne brinite, većinu podataka ovde prilično je lako razumeti. A C jezik funkcija za podešavanje video režima u DOS-u je:

typedef unsigned char byte_t;

void vid_mode(byte_t mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}
Kao što vidite, definisao sam poseban tip byte_t, koji je upravo ista stvar kao unsigned char. Ponekad pomaže ako se tako nešto apstrahuje... Ja, takođe, koristim union REGS da radim s CPU registrima. REGS union je definisan unutar <dos.h> fajla. Ja postavljam regs.h.ah na 0, i regs.h.al na video režim na koji hoću da se prebacim i onda pozivam int 0x10. Vredi napomenuti da će ovaj kod raditi isključivo u DOS-u. A sada, da vidimo kako ovo funkcioniše na jednostavnom priimeru. (Kod za DOS verziju Borland C++ v3.1 sledi)

#include <stdlib.h>
#include <conio.h>
#include <dos.h>

typedef unsigned char byte_t;

byte_t far* video_buffer = (byte_t far*)0xA0000000;

void vid_mode(byte_t mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}

void set_pixel(int x,int y,byte_t color){
	video_buffer[y * 320 + x] = color;
}

int main(){

	vid_mode(0x13);
	
	while(!kbhit())
		set_pixel(rand()%320,rand()%200,rand()%256);

	vid_mode(0x03);

	return 0;
}
Jeste li zapamtili sve što sam rekao u vezi s radom na pokazivaču? Ovaj kod ilustruje i to. Pod DOS-om (pomoću VGA), možemo se obratiti direktno ekranu, a ta "posebna adresa" je 0xA0000000 za video režim 13h, što je, po mom mišljenju, najlakši video režim za rad.

Postavimo video_buffer pokazivač da pokazuje direktno na 0xA0000000 i set_pixel (int, int, byte_t) funkcija jednostavno stavlja boju na odgovarajuću lokaciju na ekranu. Video rezolucija režima 13h je 320x200. Znači, širina ekrana je 320 piksela, a visina y= 200 piksela. Dakle, kao što možete da vidite, naša funkcija raspoređivanja piksela množi y koordinate sa širinom i dodaje x, a zatim toj lokaciji dodeljuje boju.

Važno je napomenuti da ne biste poželeli da napravite piksele izvan opsega 320x200, jer ako to uradite, možete lako izbrisati delove DOS-a i oboriti sistem. (Naravno, većina "savremenih" operativnih sistema neće se srušiti i jednostavno će izbaciti vaš program iz sistema).

Unutar našeg glavnog () prvo podešavamo režim na 13h, a zatim petlju dok ne pritisnemo taster crtajući piksele na nasumičnim lokacijama na ekranu. Na kraju, jednostavno se vraćamo na DOS video režim, koji je lepo podešen da bude 0x03. 0x03 režim zasnovan na tekstu (prikazuje tekst), sa tekstom rezolucije 80x25. Ovaj 0x03 režim ima 16 boja, dok režim 0x13 ima 256 boja.

Većina grafike s kojom ljudi rade sadrži 256 boja, ali izgleda da postoji trend da se kreće prema 16-bitnoj boji (to je 65535 boja).

Sada malo o "konvenciji" i brzini. Korišćenje video_buffer pokazivača za crtanje piksela je sporije nego korišćenje drugih pokazivača za druge memorije. To je zbog činjenice da, kada crtate piksele u video memoriji, sistem mapira memoriju I/O, a mi bismo to hteli da izbegnemo veći deo vremena. Uobičajen način da se to izbegne jeste da ucrtamo piksele u redovnu memoriju, a zatim da uradimo "blitting" (brzo kopiranje) na ekran. Ovo smanjuje treperenje i povećava ukupnu brzinu procesa.

Ovo radimo tako što prvo dodeljujemo dovoljno memorije za ceo ekran, a zatim brišemo tu memoriju. Onda pišemo u toj memoriji i kopiramo je na video memoriju. Ovaj pristup najčešće je poznat kao dupli bafering.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <string.h>
#include <alloc.h>

typedef unsigned char byte_t;

byte_t far* video_buffer = (byte_t far*)0xA0000000;

void vid_mode(byte_t mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}

void blit(byte_t far* what){
	_fmemcpy(video_buffer,what,320*200);
}

int main(){

	int x,y;
	byte_t far* double_buffer;

	double_buffer = (byte_t far*)farmalloc((unsigned long)320*200);
	if(double_buffer == NULL){
		printf("sorry, not enough memory.\n");
		return 1;
	}
	_fmemset(double_buffer,0,(unsigned long)320*200);

	vid_mode(0x13);

	while(!kbhit()){
		x = rand()%320;
		y = rand()%200;
		double_buffer[y * 320 + x] = (byte_t)(rand()%256);
		blit(double_buffer);
	}

	vid_mode(0x03);
	farfree(double_buffer);
	return 0;
}
Kod iznad prvog dodeljuje prihvatnu memoriju, veličinu ekrana, a onda proverava da li smo zaista dodelili tu memoriju. Provera grešaka u realnom režimu raspodele memorije veoma je važna jer nema mnogo memorije za traćenje. Dok sam testirao gore opisani program, sigurno sam dobio poruke: "Izvini, nema dovoljno memorije" na desetine puta, a imam 64 MB RAM memorije! Real mode koristi samo 640k DOS memorije.

Zatim nastavljamo tako što ćemo opozvati memoriju koju smo dodelili. Ovo je važno, jer čišćenje nije neophodno (možete probati i bez njega, i vidite šta ste dobili). Zatim upadamo u kbhit() petlju, kreiramo nasumičan x i y i crtamo nasumičan piksel u double_buffer na ovim nasumičnim x i y. Zatim pozivamo blit (double_buffer), koji kopira double_buffer u video_buffer, koji je podešen na na 0xA0000000.

Primetićete, takođe, da je ovaj program mnogo sporiji od prethodnog. Uzrok tome nije što koristimo dvostruki bafer, već zato što ga previše kopiramo. Svaki put kopiramo kroz okvir. Najčešće ćemo crtati celinu u duplom baferu (double_buffer) (a ne samo jedan piksel), pa će kopiranje biti retko moguće.

Jedna mala napomena o programu pre nego što krenemo dalje. Da bi radio, moraćete da ga sastavite u okviru modela koji ima najmanje srednju memoriju. Ako ne uradite tako, neće moći da izdvoji memoriju koristeći farmalloc(). U Borland C++ v3.1, možete otići u Options |Compiler| Code Generation | i izaberite Medium model.

Paleta...

Rad sa 256 boja može izgledati restriktivan, ali uglavnom nije. Razlog tome je što se ograničenje svodi samo na broj boja koje mogu biti prikazane odjednom. VGA je zapravo u stanju da prikaže 262.144 boje. Trik je u tome što pikseli koje unosimo u video memoriju nisu pravi pikseli, već reference na look-up tabeli boja. Ova tabela naziva se paleta i sastoji se od 3 bajta za svaku referencu. Na 3 bajta su crvena, zelena i plava (RGB). Dakle, da bismo prikazali određeni crveni piksel na x=100, i y= 100, mi bismo postavili, na primer, 50. element u ovoj look-up tabeli na 0xFF, 0x00, 0x00, (ovo je RGB, gde je R <crvena> 0xFF, G <zelena> je 0x00, i B<plava> je 0x00). Ovo bi trebalo da pikselu da vrednost 50 - crvenu. Zatim napišemo vrednost 50 u lokaciji memorije x= 100, i y= 100, i imamo crveni piksel na toj lokaciji.

Ovo možda zvuči komplikovano, ali nije. Uglavnom postavimo paletu jednom i radimo na fiksnoj paleti. Pre nego što postavimo tu "jednu" paletu, moramo da je kreiramo (ili da koristimo jednu "standardnu"). Za ovaj dokument, mi ćemo uglavnom koristiti lepo definisanu "standardnu" windows paletu. (Napravio sam BMP fajl sa standardnim bojama prozora, sačuvao ga i kasnije iz njega izvukao paletu koristeći sopstveni program).

Postoje razne operacije na paleti. Možemo postaviti jedan indeks u boji look-up tabele sa specifičnim ulaskom od 3 bajta ili možemo preuzeti ova 3 bajta iz specifičnog indeksa u paleti. Postoji, međutim i začkoljica u VGA. Paleta VGA sastoji se od tri šestobitna ulaza (a ne osmobitna ulaza kao što treba za bajt). Dakle, 6 * 3 = 18, a 2^18 je 262.144. Otuda maksimalni broj boja. Da je moguće sačuvati jedan bajt, imali bismo 8*3=24, a 2^24 je 16.777.216; što je mnogo više boja od standardnog VGA. Većina ne-VGA pristupa podržava paletu na bazi 8 bitova (odnosno, jenog bajta); dobar primer za to su JPEG slike sa 24 bitnim bojama, oni nisu baš zasnovani na paleti, već na RGB.

Iz informacija koje imate, već možete da izračunate veličinu palete od 256 boja. To je 3 bajta po pikselu, 256 različitih piksela, pa 3 * 256 = 768! A u stvari, 768 je veličina većine paleta od 256 boja, uključujući i one u PCX fajlovima. Ponekad, međutim, oni skladište 4 bajta po pikselu za poboljšanje brzine (kao kod BMP fajlova). Poslednji bajt se ne koristi. Najčešći poredak u paleti je RGB, ali ponekad je BGR (opet, kao u BMP fajlovima), ili nešto još apstraktnije... Postoji mnogo konvencija za ovo, vi samo treba da znate nekoliko osnovnih, a većinu ćete lako shvatiti ako razumete koncept u osnovi.

Sada, hajde da napišemo program koji čita i postavlja paletu i radi neke kul efekte jednostavno igrajući se paletom (bez crtanja bilo čega u glavnoj petlji).

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <string.h>
#include <alloc.h>

typedef unsigned char byte_t;

byte_t far* video_buffer = (byte_t far*)0xA0000000;

void vid_mode(byte_t mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}

void blit(byte_t far* what){
	_fmemcpy(video_buffer,what,320*200);
}

void setpalette(byte_t c,byte_t r,byte_t g,byte_t b){
	outp(0x3c6,0xff);
	outp(0x3c8,c);
	outp(0x3c9,r);
	outp(0x3c9,g);
	outp(0x3c9,b);
}

void getpalette(byte_t c,byte_t* r,byte_t* g,byte_t* b){
	outp(0x3c6,0xff);
	outp(0x3c7,c);
	*r = inp(0x3c9);
	*g = inp(0x3c9);
	*b = inp(0x3c9);
}

void setpalette_all(byte_t palette[256][3]){
	int i;
	for(i=0;i<256;i++)
		setpalette(i,palette[i][0]>>2,palette[i][1]>>2,
			palette[i][2]>>2);
}

void vline(int x,int y1,int y2,byte_t color,byte_t far* where){
	for(;y1 <= y2;y1++)
		where[y1*320+x] = color;
}

void movepalette(){
	byte_t r,g,b,r1,g1,b1;
	int i;
	getpalette(0,&r1,&g1,&b1);
	for(i=0;i<255;i++){
		getpalette(i+1,&r,&g,&b);
		setpalette(i,r,g,b);
	}
	setpalette(255,r1,g1,b1);
}

int main(){
	FILE* input;
	int i;
	byte_t palette[256][3];
	byte_t far* double_buffer;
	
	double_buffer =
		(byte_t far*)farmalloc((unsigned long)320*200);
	if(double_buffer == NULL){
		printf("sorry, not enough memory.\n");
		return 1;
	}
	_fmemset(double_buffer,0,(unsigned long)320*200);
	
	input = fopen("palette.dat","rb");
	if(input == NULL){
		printf("can't read file...\n");
		exit(1);
	}
	
	if(fread(palette,sizeof(palette),1,input) != 1){
		printf("can't read file...\n");
		exit(1);
	}
	fclose(input);
	
	vid_mode(0x13);
	
	setpalette_all(palette);
	
	for(i=0;i<320;i++)
		vline(i,0,199,i%256,double_buffer);
	blit(double_buffer);
	
	while(!kbhit())
		movepalette();
	
	vid_mode(0x03);
	farfree(double_buffer);
	return 0;
}
Ako pažljivo pogledate izvor, videćete da je to samo produžetak starog izvora. Ekstenzije su funkcije koje nam omogućavaju da upravljamo paletom. Imamo funkcije setpalette () i agetpalette () funkciju. Ovo set i get, odnosi se na određenu vrednost palete. Unutar glavne (), možemo pročitati naš palette.dat fajl, koji je, u stvari, windows standardna paleta. Zatim smo postavili ovu paletu. Obratite pažnju da se unutar naše setpalette - postavi paletu () funkcije, mi prebacujemo vrednost palete na 2. To je zato što standardna VGA paleta ima 6 bitova, kao što smo već ranije pomenuli. Dakle, mi prebacimo sa 2 da eliminišemo dva beznačajna bita, i da bismo sačuvali značajne.

Vline () funkcija povlači vertikalnu liniju. Ovo nije najbolji način da se crtaju linije, a mi ćemo razgovarati o mnogim različitim algoritmima za crtanje kada završimo ovaj deo. Zatim uradimo blit () the double_buffer na ekranu, i počnemo da se igramo paletom. Obratite pažnju na to da ne pišemo ništa u video memoriju, a ipak, ceo ekran se kreće (to je lepota paleta).

Pretpostavljam da ćete saznati (i shvatiti) dosta o paletama ako idete preko navedenog programa. Dakle, ne oklevajte, i idite preko tog programa! (Definitivno sastavi, i vidi efekat) Konačna raspodela ovog dokumenta će takođe uključiti fajl paleta, ali ako ga nemate, možete jednostavno da napravite sopstvenu paletu. Nešto u linijama:

for(i=0;i<256;i++){
	palette[i][0] = palette[i][1] = palette[i][2] = i;
}
Ili možete da se igrate petljama, pa pogledajte šta vam najbolje izgleda. To je bilo sve o osnovama grafike za realni režim DOS pod Borland C + + v3.1!

Grafika u zaštićenom režimu...

Grafika u realnim režimu DOS je lepa, ali ponekad ima previše ograničenja u običnom vanila DOS-u. Najznačajnija od ovih ograničenja je drastično ograničenje memorije. (Ponekad hoću da moji programi da koriste mnogo više od 640k RAM-a!) Da ne pominjem koliko me nervira da radim s udaljenim pokazivačima! Jasno vam je, raditi bilo šta ozbiljno u realnom režimu DOS-a je pakao!

Spas u zadnji donosi bezbedan i hladan 32-bitni zaštićeni režim. U ovom delu ćemo koristiti kompajler DJGPP v2. To je BESPLATAN 32-bitni Protected Mode kompajler za DOS. Ne dozvolite davas taj "DOS" navede na pogrešan trag. DJGPP radi savršeno pod Win95/98 kao i pod WinNT. Iako ne možete da prevedete ništa koristeći Win32 API, ili MFC, možete, međutim, da pišete programe bazirane na DOS-u koji su veoma moćni i izgledaju kul i pritom koristiti onoliko memorije koliko im je potrebno (do 4 gigabajta RAM ako treba).

Možete preuzeti DJGPP sa sajta http://www.delorie.com . Jednostavno kliknite na link DJGPP i pratite uputstva za preuzimanje. Ako imate problema sa podešavanjem, pregledajte FAQ koji je takođe dostupan na toj lokaciji (ili mi pošaljite mejl). Tu je i alat nazvan RHIDE, koji lepo simulira Borland-ov IDE (tako da se možete osećati kao svoj na svome čim počnete da koristite DJGPP).

Pod pretpostavkom da ste preuzeli (i instalirali) DJGPP, hajde sada da prebacimo naš program iz realnog režima DOS-a (iz prethodnog pasusa) u 32-bitni zaštićeni režim DOS-a pomoću DJGPP. Ovo može zvučati složenije nego što stvarno jeste.

Pošto je većina izvora u C, o njima ne moramo da brinemo. Moramo da brinemo o delovima pisanja memorije. Pošto smo sada u zaštićenom režimu, ne možemo samo pisati 0xA0000000, jer je memorija je zaštićena (baš kao i svaka druga memorija u zaštićenom režimu). Imamo nekoliko opcija, međutim, možemo da koristimo DJGPP određeni sistemski poziv da kopiramo dvostruki bafer na video memoriju (menjajući svoju blit () funkciju), ili možemo privremeno da onemogućimo zaštitu memorije, da pišemo na ekranu, a zatim da omogućimo zaštitu memorije.

Obe mogućnosti imaju svoje prednosti i mane! Korišćenje poziva sistema da kopirate memoriju je bezbedan i kompatibilan sa mnogim sistema, uključujući DOS/Win95/98/NT (da, radi pod NT4!), Ali je prespor! Onemogućavanje zaštite memorije, kopiranje i kasnije omogućavanje zaštite memorije nije baš kompatibilan (ni siguran), proces, ali je mnogo brži. Ovaj drugi pristup radi pod DOS/Win95/98, ali ne i pod WinNT! Pa, hajde da napišemo kod koji nam omogućava korišćenja obe pristupa, a ja ću vam dozvoliti da izaberete koji želite da koristite...

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include <conio.h>

typedef unsigned char byte_t;

void vid_mode(byte_t mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}

void blit0(byte_t* what){
	dosmemput(what,320*200,0xA0000);
}

#include <sys/nearptr.h>
/* VERY unsafe, but fast! */
void blit1(byte_t* what){
	/* disable memory protection */
	__djgpp_nearptr_enable();
	/* write to memory */
	memcpy((void*)(0xA0000+__djgpp_conventional_base),
		what,320*200);
	/* enable memory protection */
	__djgpp_nearptr_disable();
}

void setpalette(byte_t c,byte_t r,byte_t g,byte_t b){
	outp(0x3c6,0xff);
	outp(0x3c8,c);
	outp(0x3c9,r);
	outp(0x3c9,g);
	outp(0x3c9,b);
}

void getpalette(byte_t c,byte_t* r,byte_t* g,byte_t* b){
	outp(0x3c6,0xff);
	outp(0x3c7,c);
	*r = inp(0x3c9);
	*g = inp(0x3c9);
	*b = inp(0x3c9);
}

void setpalette_all(byte_t palette[256][3]){
	int i;
	for(i=0;i<256;i++)
		setpalette(i,palette[i][0]>>2,palette[i][1]>>2,
			palette[i][2]>>2);
}

void vline(int x,int y1,int y2,byte_t color,byte_t* where){
	for(;y1 <= y2;y1++)
		where[y1*320+x] = color;
}

void movepalette(){
	byte_t r,g,b,r1,g1,b1;
	int i;
	getpalette(0,&r1,&g1,&b1);
	for(i=0;i<255;i++){
		getpalette(i+1,&r,&g,&b);
		setpalette(i,r,g,b);
	}
	setpalette(255,r1,g1,b1);
}

int main(){
	FILE* input;
	int i;
	byte_t palette[256][3];
	byte_t* double_buffer;
	
	double_buffer = (byte_t*)malloc(320*200);
	if(double_buffer == NULL){
		printf("sorry, not enough memory.\n");
		return 1;
	}
	memset(double_buffer,0,320*200);
	
	input = fopen("palette.dat","rb");
	if(input == NULL){
		printf("can't read file...\n");
		exit(1);
	}
	
	if(fread(palette,sizeof(palette),1,input) != 1){
		printf("can't read file...\n");
		exit(1);
	}
	fclose(input);
	
	vid_mode(0x13);
	
	setpalette_all(palette);
	
	for(i=0;i<320;i++)
		vline(i,0,199,i%256,double_buffer);
	blit0(double_buffer);
	
	while(!kbhit())
		movepalette();
	
	vid_mode(0x03);
	free(double_buffer);
	return 0;
}
Kao što vidite, kod se nije mnogo promenio. Zapravo, samo se rukovanje memorijom promenilo. Farmalloc() je postala jednostavna stara malloc(), farfree() se pretvorio u free(), itd Pažnja treba da vam bude usmerena na blit#() funkcije... Trenutno postoje dve takve funkcije. (Siguran sam da ima još mnogo pristupa pisanju video memorije u zaštićenom režimu, ali jedan od onih koje sami treba da pogledate je _farnspokel() definisan u datoteci <sys/farptr.h>.

Blit0() je jednostavan i lak. On samo poziva sistem da izvrši kopiranje, i to je to. Obratite pažnju na to da kopiramo u 0xA0000 jer je ovo fizička memorija na ekranu (primetite četiri 0). U zaštićenom režimu, nemamo segmente ili kompenzacije kao u realnom režimu (u stvari, postoje segmenti i kompenzacije u zaštićenoj modu, ali ovde imaju drugačije značenje), tako da u zaštićenom režimu koristimo punu fizičku adresu kad god moramo da se obratimo memorijski mapiranom uređaju kao što je VGA.

Blit1() je komplikovaniji. Prvo poziva __ djgpp_nearptr_enable(), koji efikasno onemogućava zaštitu memorije. Ova funkcija ne radi nikakvu proveru grešaka prethodnih vrednosti__djgpp_nearptr_enable(). Kada više ne bude zaštite memorije, jednostavno možemo da pišemo bilo gde (uključujući i ekran) ili da zamenimo operativni sistem ako hoćemo). Koristimo memcpi() da kopiramo bafer na ekran, što je 0xA0000, od ​​__djgpp_conventional_base (konvencionalna baza) ofset. Nastavljamo tako što omogućavamo zaštitu memorije pozivom na __ djgpp_nearptr_disable ().

Ovaj metod nije mnogo brži od prethodnog. Razlog tome je što su funkcije koje omogućavaju i onemogućavaju zaštitu memorije prilično spore. Dakle, *obično,* kada prođete fazu debagovanja projekta, jednostavno ćete onemogućiti zaštitu memorije na početku programa i omogućiti je na kraju. Ovaj korak izuzetno je nebezbedan i može dovesti do nekih ozbiljnih problema (uključujući i potpuni gubitak svega).

Windows grafika...

Windows je budućnost (ili bar tako kažu), pa, očigledno, raditi grafiku u običnom starom DOS-u nije dobra ideja (ili bar tako kažu). I dalje mislim da to ipak jeste dobra ideja jer samo u DOS-u možete da se izolujete od gluposti operativnog sistema i da brinete samo o SVOM programu, a o tome kako Window pokreće vaš program. U DOS-u, najčešće, vaš program postaje operativni sistem. Većina igara JESU operativni sistemi: upravljaju I/O, tastaturom, svime! U svakom slučaju, izgleda da Windows pobeđuje u ovom ratu i vreme je da naučite programiranje u Windows-u.

Programerima je teško da shvate da u Windows-u više nisu glavni oni, nego Windows. Ako vaš program hoće da uradi bilo šta, mora da zatraži dozvolu od Windows-a. Ne možete tek tako ići okolo i crtati piksele u video memoriju, morate pitati windows da to uradi za vas. U windows-u je sve pod kontrolom windows-a. Vaš program mora to da poštuje ili će biti izbačen iz sistema.

Za ovaj odeljak biće vam potreban Microsoft Visual C++ v5 Pro (ili kasnija). U ovom delu pokrićemo osnove windows grafike koje su široko prenosive na sve windows sisteme (bez DirectX stvari).

Svi tutorijali i knjige (koje sam video) počinju jednostavnim rečima: “koristite čarobnjak za kreiranje ljuske MFC programa i krenite odatle..." Pa, ovaj dokument neće to uraditi. Napisaću ovaj kod u običnom starom C, koristeći običnu staru Win32 API. Niije to zato što mrzim MFC (iako je faktor), već zato što vas on ne uči kako da programirate u windows-u. Mnogi takozvani windows programeri koriste samo MFC, i nemaju pojma o osnovnom Win32 interfejsu (odnosno o tome kako se gradi windows program).

Pređimo na pisanje to (a ne na lupanje Microsoft-a zato što ima operativni sistem pun bagova). Svaki windows program počinje unutar winMain() funkcije, a ne u funkciji Main() funkcija, kao što je to slučaj sa većinom C programa. Windows program prvo kreira klasu prozora (WNDCLASS struktura), registruje ga, stvara prozor te klase, i prikazuje taj prozor. Jednostavno?

Zatim bi trebalo da stvori DIB sekciju u kojoj će crtati (dupli bafer) (DIB znači zalaže za Device Independent Bitmap - bitska mapa nezavisna od uređaja). To se radi tako što se prvo postave elementi BITMAPINFO strukture koji kreiraju i ostvaruju paletu, itd, a onda poziva CreateDIBSection() koja nam daje dvostruki bafer za crtanje.

Nakon svega toga, treba nam i blit() funkcija! To je teže nego u DOS-u! Počinjemo stvaranjem uređaja kontekst, tako što pozivamo CreateCompatibleDC() i selektujemo naš HBITMAP (upravljač bitskom mapom) za taj kontekst. Zatim uradimo bliting koristeći BitBlt() funkciju (koja je, uglavnom, prilično brza), i vratimo podešavanja na normalu (očistimo, itd.).

Program radi unutar višestrukih programa (u WinMain() funkciji sve dok program ne dobije WM_QUIT poruku. Sada je pravo vreme za podsećanje na to da se sve u operativnom sistemu windows obavlja pomoću poruka ;-) Naš program će primiti ovu poruku kad god pokušavamo da uništimo prozor aplikacije. (Pošto pozivamo PostQuitMessage() funkciju kad WndProc() dobije WM_DESTROY poruku.

To može biti složenije nego u DOS-u, ali to je IMHO! Ali, tako je - kako je i niko od nas ne može tu ništa da uradi. Da li da vas sad ostavim da sami prokljuvite detalje svega ovoga ili da vam dam jedan izvor kao primer? (Iznenadili biste se kad biste videli koliko knjiga izostavlja primere realne primene).

#include <windows.h>

typedef BYTE byte_t;

#define W_WIDTH 320
#define W_HEIGHT 240

struct pBITMAPINFO{
	BITMAPINFOHEADER bmiHeader;
	RGBQUAD bmiColors[256];
}BMInfo;

struct pLOGPALETTE{
	WORD	palVersion;
	WORD	palNumEntries;
	PALETTEENTRY palPalEntry[256];
}PalInfo;

HBITMAP hBM;
byte_t* double_buffer;

void blit(void);

void vline(int x,int y1,int y2,byte_t color,byte_t* where){
	for(;y1 <= y2;y1++)
		where[y1*320+x] = color;
}

void p_update(){
	int i;
	static unsigned int c=0;
	for(i=0;i<W_WIDTH;i++){
		vline(i,0,W_HEIGHT-1,(byte_t)(c%256),double_buffer);
		c = c > 512 ? 0:c+1;
	}
	blit();
}

void getpalette(RGBQUAD* p){
	int i;
	for(i=0;i<256;i++){
		p[i].rgbRed = i;
		p[i].rgbGreen = i;
		p[i].rgbBlue = i;
		p[i].rgbReserved = 0;
	}
}
		
void init_double_buffer(){
	HPALETTE PalHan;
	HWND ActiveWindow;
	HDC hDC;
	RGBQUAD palette[256];
	int i;

	ActiveWindow = GetActiveWindow();
	hDC = GetDC(ActiveWindow);

	BMInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	BMInfo.bmiHeader.biWidth = W_WIDTH;
	BMInfo.bmiHeader.biHeight = -abs(W_HEIGHT);
	BMInfo.bmiHeader.biPlanes = 1;
	BMInfo.bmiHeader.biBitCount = 8;
	BMInfo.bmiHeader.biCompression = BI_RGB;
	BMInfo.bmiHeader.biSizeImage = 0;
	BMInfo.bmiHeader.biXPelsPerMeter = 0;
	BMInfo.bmiHeader.biYPelsPerMeter = 0;
	BMInfo.bmiHeader.biClrUsed = 256;
	BMInfo.bmiHeader.biClrImportant = 256;

	getpalette(palette);

	for(i=0;i<256;i++)
		BMInfo.bmiColors[i] = palette[i];

	PalInfo.palVersion = 0x300;
	PalInfo.palNumEntries = 256;
	for(i=0;i<256;i++){
		PalInfo.palPalEntry[i].peRed = palette[i].rgbRed;
		PalInfo.palPalEntry[i].peGreen = palette[i].rgbGreen;
		PalInfo.palPalEntry[i].peBlue = palette[i].rgbBlue;
		PalInfo.palPalEntry[i].peFlags = PC_NOCOLLAPSE;
	}

		/* create the palette */
	PalHan = CreatePalette((LOGPALETTE*)&PalInfo);
		/* select it for that DC */
	SelectPalette(hDC,PalHan,FALSE);
		/* realize a palette on that DC */
	RealizePalette(hDC);
		/* delete palette handler */
	DeleteObject(PalHan);

	hBM = CreateDIBSection(hDC,(BITMAPINFO*)&BMInfo,
		DIB_RGB_COLORS,(void**)&double_buffer,0,0);
	ReleaseDC(ActiveWindow,hDC);
}

void blit(){
	HWND ActiveWindow;
	HDC hDC,Context;
	RECT Dest;
	HBITMAP DefaultBitmap;

	if((ActiveWindow = GetActiveWindow()) == NULL)
		return;
	hDC = GetDC(ActiveWindow);
	GetClientRect(ActiveWindow,&Dest);
	
	Context = CreateCompatibleDC(0);
	DefaultBitmap = SelectObject(Context,hBM);
	BitBlt(hDC,0,0,Dest.right,Dest.bottom,Context,0,0,SRCCOPY);
	SelectObject(Context,DefaultBitmap);
	DeleteDC(Context);
	DeleteObject(DefaultBitmap);
	ReleaseDC(ActiveWindow,hDC);
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,
	WPARAM wParam,LPARAM lParam){
	switch(iMessage){
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		default:
			return DefWindowProc(hWnd,iMessage,wParam,lParam);
	}
}

int WINAPI WinMain(HANDLE hInstance,HANDLE hPrevInstance,
	LPSTR lpszCmdParam,int nCmdShow){
	WNDCLASS WndClass;
	char szAppName[] = "CrazyWindow";
	HWND hWnd;
	MSG msg;
	BOOL running = TRUE;

	WndClass.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
	WndClass.lpfnWndProc = WndProc;
	WndClass.cbClsExtra = 0;
	WndClass.cbWndExtra = 0;
	WndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
	WndClass.hIcon = LoadIcon(hInstance,NULL);
	WndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
	WndClass.hInstance = hInstance;
	WndClass.lpszClassName = szAppName;
	WndClass.lpszMenuName = 0;

	RegisterClass(&WndClass);

	hWnd = CreateWindow(szAppName,szAppName,
		WS_CAPTION|WS_MINIMIZEBOX|WS_SYSMENU,
		CW_USEDEFAULT,CW_USEDEFAULT,W_WIDTH,W_HEIGHT,
		0,0,hInstance,0);
	ShowWindow(hWnd,nCmdShow);

	init_double_buffer();

	while(running == TRUE){
		p_update();
		Sleep(250);
		if(PeekMessage(&msg,0,0,0,PM_NOREMOVE) == TRUE){
			running = GetMessage(&msg,0,0,0);
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	return msg.wParam;
}
Pa dobro, ne očekujte ni ovde neko ogromno objašnjenje svega gore pomenutog, ipak ovo nije uputstvo za programiranje u windows-u. Ako vam je problem da razumete gornji kod, predlažem vam da kupite dobru knjigu o programiranju u windows-u (iako ih je prilično teško naći). Ako imate kompajler (Visual C + + V5 Pro), biće vam malo lakše da koristite sve ove stvari iz windows-a. (Nabavite i MSDN CD, gde ćete naći mnogo korisnih podataka o windows-u, reference na sve funkcije i promenljive koje se koriste u ovom programu). Osim toga, mislim da sam učinio tako jednostavno kao što eventualno može dobiti!

Mislim da sam vam sad sve prilično pojednostavio koliko god je to bilo moguće.

DirectX grafika...

Za nekoga ko nikad ništa nije programirao u DirectX-u, sam pokušaj da shvatimo sve ovo može biti prilično zbunjujući. Veoma je složen (jednostavno ga ima previše!) Razmislite o DirectX-u kao o skupu biblioteka za sve što je u vezi s igricama (zvuk, grafika, unos itd). Mi ćemo se baviti delom koji se naziva DirectDraw. Koristićemo ga za ulazak celog ekrana u 256 boja, s duplim baferima, režim pod windows-om za crtanje na ekranu.

Treba da znate da DirectX nije previše prenosan (bar nije bio u vreme pisanja ovog teksta). Mnogi DirectX programi jednostavno ne rade pod WinNT4! (a oni koji rade, samo podržavaju neke ograničene video režime, ali ne i druge) Zatim, tu su sistem koji još uvek nemaju instaliran DirectX! (Kao moja win95 mašina kod kuće) Sve ovo će se, ipak, promeniti, NT5 i Win98 dolaze s podrškom DirectX5, a kad stigne NT5, verovatno će podržavati i DX6.

Međutim, da biste napisali nešto pomoću DirectX-a, morate ga imati! Ako ga još niste nabavili, idite na Microsoft sajt i preuzmite DirectX SDK (ili ga naručite na CD-ROM-u) (prilično je veliki!). Potražite na sajtu http://www.microsoft.com/directx. Ako imate neku od novijih verzija Visual C++, velike su šanse da vam se neka verzija DirectX već mota oko hard diska (ili barem fajlovi biblioteke koje možete da povežete, ali ne i primeri i sl.)

Pre nego što počnemo, hteo bih da naglasim nekoliko stvari. Iako DirectX programi rade samo pod windows-om, njihova moć je i dalje u režimima celog ekrana (ne očekujte da ćete u skorije vreme moći da napišete applikaciju za DirectX u prozorčićima). Ako hoćete da vam grafički program bude u prozoru, iskoristite pristup koji smo već opisali u odeljku Windows grafika.

U DirectX-u uglavnom radimo na onome što se naziva “površina”. Crtamo na površinama itd. Pre nego što počnemo, moramo nacrtati DirectDraw objekat. Zatim određujemo da li hoćemo režim celog ekrana itd. Zatim kreiramo primarnu površinu (mislite o osnovnoj površini kao o vrsti video bafera u našim prethodnim primerima), a zatim ćemo napraviti zadnju površinu (kao vrsta dvostrukog bafera). Za crtanje, prvo postavimo pokazivač memorije na zadnju površinu (zaključavajući površinu), crtamo tim pokazivačem, a kasnije otključajte površinu (i uradite nešto što podseća na blit() funkciju...)

Gornje objašnjenje previše je pojednostavljeno! Kod koji sledi malo je detaljniji.

#include <windows.h>
#include <ddraw.h>

typedef BYTE byte_t;

#define W_WIDTH 640
#define W_HEIGHT 480

int b_pitch;
byte_t *double_buffer;

HWND hWnd;
PALETTEENTRY palette[256];
DDSURFACEDESC	ddsd;
LPDIRECTDRAW	lpDD;
LPDIRECTDRAWSURFACE lpDDSPrimary;
LPDIRECTDRAWSURFACE lpDDSBack;
LPDIRECTDRAWPALETTE lpDDPal;

void big_error(char* string){
	MessageBox(hWnd,string,"big & bad error",
		MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
	ExitProcess(1);
}

void init_directdraw(){
	DDSURFACEDESC ddsd_tmp;
	HRESULT		ddrval;
	ddrval = DirectDrawCreate(NULL,&lpDD,NULL);
	if(ddrval != DD_OK)
		big_error("couldn't init DirectDraw!");
	ddrval = IDirectDraw_SetCooperativeLevel(lpDD,hWnd,
		DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_ALLOWREBOOT);
	if(ddrval != DD_OK)
		big_error("couldn't set DirectDraw exclusive mode!");
	ddrval = IDirectDraw_SetDisplayMode(lpDD,W_WIDTH,W_HEIGHT,8);
	if(ddrval != DD_OK)
		big_error("couldn't set DirectDraw display mode!");
	ddsd_tmp.dwSize = sizeof(ddsd_tmp);
	ddsd_tmp.dwFlags = DDSD_CAPS;
	ddsd_tmp.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
	ddrval = IDirectDraw_CreateSurface(lpDD,&ddsd_tmp,
		&lpDDSPrimary,NULL);
	if(ddrval != DD_OK)
		big_error("couldn't create primary surface!");
	ddsd_tmp.dwSize = sizeof(ddsd_tmp);
	ddsd_tmp.dwFlags = DDSD_CAPS |DDSD_WIDTH|DDSD_HEIGHT;
	ddsd_tmp.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN|DDSCAPS_SYSTEMMEMORY;
	ddsd_tmp.dwWidth = W_WIDTH;
	ddsd_tmp.dwHeight = W_HEIGHT;
	ddrval = IDirectDraw_CreateSurface(lpDD,&ddsd_tmp,
		&lpDDSBack,NULL);
	if(ddrval != DD_OK)
		big_error("couldn't create back surface!");
}

void kill_directdraw(){
	if(lpDD != NULL){
		if(lpDDSPrimary != NULL){
			IDirectDrawSurface_Release(lpDDSPrimary);
			lpDDSPrimary = NULL;
		}
		if(lpDDSBack != NULL){
			IDirectDrawSurface_Release(lpDDSBack);
			lpDDSBack = NULL;
		}
		if(lpDDPal != NULL){
			IDirectDrawPalette_Release(lpDDPal);
			lpDDPal = NULL;
		}
		IDirectDraw_Release(lpDD);
		lpDD = NULL;
	}
}

void set_palette(RGBQUAD* pal){
	PALETTEENTRY black = {0,0,0,0},white = {0xFF,0xFF,0xFF,0};
	int i;
	if(pal != NULL){
		for(i=1;i<0xFF;i++){
			pal++;
			palette[i].peRed	= pal[i].rgbRed;
			palette[i].peGreen	= pal[i].rgbGreen;
			palette[i].peBlue	= pal[i].rgbBlue;
			palette[i].peFlags	= (byte_t)0;
		}
	}else{
		for(i=1;i<0xFF;i++){
			palette[i].peRed	= i;
			palette[i].peGreen	= i;
			palette[i].peBlue	= i;
			palette[i].peFlags	= (byte_t)0;
		}
	}
	palette[0] = black;
	palette[0xFF] = white;
	IDirectDraw_CreatePalette(lpDD,DDPCAPS_8BIT,palette,
		&lpDDPal,NULL);
	if(lpDDPal != NULL)
		IDirectDrawSurface_SetPalette(lpDDSPrimary,lpDDPal);
}

BOOL lock_surface(){
	HRESULT	ddrval;
	ddsd.dwSize = sizeof(ddsd);
	for(;;){
		ddrval = IDirectDrawSurface_Lock(lpDDSBack,NULL,
			&ddsd,DDLOCK_WAIT,NULL);
		if(ddrval == DD_OK)
			break;
		if(ddrval == DDERR_SURFACELOST){
			ddrval = IDirectDrawSurface_Restore(lpDDSPrimary);
			if(ddrval == DD_OK)
				return FALSE;
		}
		if(ddrval != DDERR_WASSTILLDRAWING)
			return FALSE;
	}
	double_buffer = (byte_t*)(ddsd.lpSurface);
	b_pitch = ddsd.lPitch;
	return TRUE;
}

void unlock_surface(){
	HRESULT ddrval;
	RECT rc = {0,0,W_WIDTH,W_HEIGHT};
	
	double_buffer = NULL;
	for(;;){
		ddrval = IDirectDrawSurface_Unlock(lpDDSBack,
			ddsd.lpSurface);
		if(ddrval == DD_OK)
			break;
		if(ddrval == DDERR_SURFACELOST){
			ddrval = IDirectDrawSurface_Restore(lpDDSPrimary);
			if(ddrval != DD_OK)
				return;
		}
		if(ddrval != DDERR_WASSTILLDRAWING)
			return;
	}
	for(;;){
		ddrval = IDirectDrawSurface_BltFast(lpDDSPrimary,0,0,
			lpDDSBack,&rc,DDBLTFAST_NOCOLORKEY);
		if(ddrval == DD_OK)
			break;
		if(ddrval == DDERR_SURFACELOST){
			ddrval = IDirectDrawSurface_Restore(lpDDSPrimary);
			if(ddrval != DD_OK)
				return;
		}
		if(ddrval != DDERR_WASSTILLDRAWING)
			return;
	}
}

void vline(int x,int y1,int y2,byte_t color,byte_t* where){
	for(;y1 <= y2;y1++)
		where[y1*b_pitch+x] = color;
}

void p_update(){
	int i;
	static unsigned int c=0;
	if(lock_surface()){
		
		for(i=0;i<W_WIDTH;i++){
			vline(i,0,W_HEIGHT-1,(byte_t)(c%256),double_buffer);
			c = c > 512 ? 0:c+1;
		}

		unlock_surface();
	}
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,
	WPARAM wParam,LPARAM lParam){
	switch(iMessage){
		case WM_DESTROY:
		case WM_LBUTTONDOWN:
		case WM_KEYDOWN:
			PostQuitMessage(0);
			return 0;
		default:
			return DefWindowProc(hWnd,iMessage,wParam,lParam);
	}
}

int WINAPI WinMain(HANDLE hInstance,HANDLE hPrevInstance,
	LPSTR lpszCmdParam,int nCmdShow){
	WNDCLASS WndClass;
	char szAppName[] = "CrazyWindow";
	MSG msg;
	BOOL running = TRUE;

	WndClass.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
	WndClass.lpfnWndProc = WndProc;
	WndClass.cbClsExtra = 0;
	WndClass.cbWndExtra = 0;
	WndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
	WndClass.hIcon = LoadIcon(hInstance,NULL);
	WndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
	WndClass.hInstance = hInstance;
	WndClass.lpszClassName = szAppName;
	WndClass.lpszMenuName = 0;

	RegisterClass(&WndClass);

	hWnd = CreateWindow(szAppName,szAppName,
		WS_CAPTION|WS_MINIMIZEBOX|WS_SYSMENU,
		CW_USEDEFAULT,CW_USEDEFAULT,
		CW_USEDEFAULT,CW_USEDEFAULT,
		0,0,hInstance,0);
	ShowWindow(hWnd,nCmdShow);

	init_directdraw();
	set_palette(NULL);

	while(running == TRUE){
		p_update();
		Sleep(250);
		if(PeekMessage(&msg,0,0,0,PM_NOREMOVE) == TRUE){
			running = GetMessage(&msg,0,0,0);
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	kill_directdraw();
	return msg.wParam;
}
Predlažem vam da nekoliko puta pređete izvor i na kraju ćete ga razumeti. Program ima potpuno isti efekat kao program u odeljku Widows grafika (samo što je ceo ekran). Da biste sastavili/povezali program, biće vam potrebne DirectX biblioteke. Povežite program uz pomoć ddraw.lib.

Mogli biste da razmislite o još nečemu: upotreba DirectX-a u vašem programu MNOGO je lakša ako pišete programe koristeći C++. Mnoge od ovih dugačkih funkcija postaju prilično kratki režimi objekata. (mnogo je čistije koristiti C++ s DirectX-om) (Jednostavno mrzim C++ <U stvari, pre nekoliko godina sam ga voleo>), tako da sad radim na ovaj način!

Napomena pre nego što nastavimo: Ovaj program radi na većini Windows platformi zato što ima standardni režim na 640x480! Ako podesite režim na 320x200, ili na X režim ( 320x240), ovaj program NEĆE raditi pod NT4. Međutim, radiće pod režimom Win95/98. Da biste promenili režime, promenite W_WIDTH i W_HEIGHT koji se nalaze na vrhu koda. (Stvarno mrzim kad popularna igrica ne ide na celom ekranu na 320x240 pod NT4. Quake 2 <hej, nemamo svi pentium 2, 400Mhz sa vudu akceleratorom!>) Postoje načini da se otklone i ovi nedostaci. I dalje možete koristiti režim 640x480 i razvući sliku. Sumnjam da bi čak i dva razvlačenja uticala na kvalitet performansi. Nadam se da će NT5 podržavati sve ove režime s malom rezolucijom... (Sigurno se neću vraćati na Win98 samo da bih igrao igrice preko celog ekrana!)

Java grafika...

Ura! Java. Većina misli da je Java previše spora i beskorisna za grafiku. Možda su i u pravu (još nisam naišao na neki stvarno kul grafički program s boljim performansama nego njegov kolega C/C++) Bez obzira na to, Java je najelegantniji jezik orijentisan na objekat koji sam ikad video (C++ je krš u poređenju s Javom!)

“Brza grafika u Javi” je oksimoron. Ipak, vremenom postaje sve brža, a u nekim slučajevima može da se takmiči s C/C++. Postoje dva načina obrade grafike u Javi - pomoću biblioteka ili uz pomoć dvostrukog bafera u kome se piše (slično onome što smo radili u prethodnom odeljku). Upotreba biblioteka je mnogo lakša, a u nekim slučajevima predstavlja jednostavno rešenje. Mnogo je lakše jednostavno reći: sayg.drawLine(0,0,100,100); i nacrtati liniju, nego da sami podešavate gomilu parametara i napišete kod za optimalnu liniju algoritma.

Prvo ćemo se pobrinuti za pristup grafičkoj biblioteci Java jer ćemo, verovatno, pomoću nje kasnije da ilustrujemo nekoliko brzih algoritama. (Jednostavno je za korišćenje!) Kada naučimo pristup "Biblioteka", pokazaću vam jedan trik koji većina knjiga, uputstava i primera pokušava da izbegne. Da bismo primenili sopstveni ImageProducer i kreirali prilično brzu grafiku pod Javom

Za primere u ovom odeljku, trebaće vam kopija JDK-a, koju možete dobiti besplatno na sajtu http://java.sun.com . Predlažem vam da preuzmete najnoviju verziju (čak i ako je beta). Microsoft Visual J++ takođe će raditi za većinu ovih primera, ali sam otkrio da je J++ veoma aktivan kada su u pitanju Java biblioteke, a mislim da se većina ljudi bolje snalazi s jednostavnim i jasnim JDK-om.

Hajde da napišemo kod! Pisaćemo regularnu aplikaciju koja otvara prozor i crta mnogo nasumičnih poligona. Evo ga:

import java.awt.*;
import java.awt.event.*;

public class jDraw extends Frame
	implements WindowListener,Runnable{
	boolean m_running;
	
	public void windowOpened(WindowEvent e){}
	public void windowClosing(WindowEvent e){
		m_running = false;
	}
	public void windowClosed(WindowEvent e){}
	public void windowIconified(WindowEvent e){}
	public void windowDeiconified(WindowEvent e){}
	public void windowActivated(WindowEvent e){}
	public void windowDeactivated(WindowEvent e){}

	public jDraw(String name){
		super(name);
		setSize(320,240);
		setResizable(false);
		addWindowListener(this);
		show();
	}

	public void paint(Graphics g){
		int n = (int)(Math.random()*8);
		int[] x = new int[n];
		int[] y = new int[n];
		for(int i=0;i<n;i++){
			x[i] = (int)(Math.random()*getWidth());
			y[i] = (int)(Math.random()*getHeight());
		}
		g.setColor(new Color((int)(Math.random()*0xFFFFFF)));
		g.fillPolygon(x,y,n);
	}

	public void update(Graphics g){
		paint(g);
	}

	public void run(){
		m_running = true;
		while(m_running){
			repaint();
			try{
				Thread.currentThread().sleep(50);
			}catch(InterruptedException e){
				System.out.println(e);
			}
		}
	}

	public static void main(String[] args){
		Thread theApp = new Thread(new jDraw("Crazy!"));
		theApp.start();
		try{
			theApp.join();
		}catch(InterruptedException e){
			System.out.println(e);
		}
		System.exit(0);
	}	
}
Kao što vidite, kod je veoma jednostavan. Otvorimo prozor, podesimo veličinu, dodamo slušač itd. Crtamo u tom prozoru i - to je to! (mnogo jednostavnije nego u prethodnim primerima). Neću ni da pominjem da je ovaj program nezavistan od sistema i da će raditi u UNIX-u kao i u Windows-u. Sumnjam da je potrebno još neko objašnjenje o ovom programu I (prilično je jednostavan - proučite ga ako ga ne razumete). U ovom primeru nije nam potrebna paleta jer su boje u RGB formatu. Kad kreiramo novu boju, određujemo RGB broj. Sada, pređimo na nešto složenije, na sopstveni ImageProducer!

Sada je vreme da naučimo da crtamo dupli bafer u Javi! U stvarnosti, biće to trostruki bafer jer ćemo prvo podesiti dvostruki bafer za java.awt.Image, a zatim (pošto uradimo bliting svog bajt[] bafera na slikutu), prenećemo sliku. Ima i elegantnijih načina da se to izvede, ali nisam hteo da gubim mnogo vremena na tome (Uskoro idem na produženi odmor i hoću da završim što više posla ;-)

Ako budem raspoložen, kasnije ću vam pokazati JDK 1.2 pristup koji je, po mom mišljenju, mnogo bolji. Nažalost, nije toliko široko kompatibilan kao ImageProducer one. Hajde da pređemo na izvor!

import java.awt.*;
import java.applet.*;
import java.awt.image.*;

public class jDrawApplet extends Applet
	implements Runnable,ImageProducer{
	
	int m_width;
	int m_height;
	Thread m_thread = null;
	Image m_screen = null;
	
	/* ImageProducer variables */
	byte[] double_buffer;
	ColorModel color_model;
	ImageConsumer consumer = null;

	/* ImageProducer interface */
	public void addConsumer(ImageConsumer ic){
		if(consumer != ic){
			consumer =  ic;
			consumer.setDimensions(m_width,m_height);
			consumer.setColorModel(color_model);
			consumer.setHints(ImageConsumer.TOPDOWNLEFTRIGHT);
		}
		blit();
	}
	public boolean isConsumer(ImageConsumer ic){
		return ic == consumer;
	}
	public void removeConsumer(ImageConsumer ic){}
	public void startProduction(ImageConsumer ic){
		addConsumer(ic);
	}
	public void requestTopDownLeftRightResend(ImageConsumer ic){}

	/* our blit() method */
	void blit(){
		if(consumer != null)
			consumer.setPixels(0,0,m_width,m_height,
				color_model,double_buffer,0,m_width);
	}

	/* our vertial line method ;-) */
	void vline(int x,int y1,int y2,byte color,byte[] where){
		for(;y1 <= y2;y1++)
			where[y1*m_width+x] = color;
	}

	int c=0;
	void do_interesting_stuff(){
		for(int i=0;i<m_width;i++){
			vline(i,0,m_height-1,(byte)(c%256),double_buffer);
			c = c > 512 ? 0:c+1;
		}
	}

	public void paint(Graphics g){
		blit();
		g.drawImage(m_screen,0,0,null);
	}

	public void update(Graphics g){
		paint(g);
	}

	/* setup stuff... */
	void setupImageProducer(){
		byte[] r = new byte[256];
		byte[] g = new byte[256];
		byte[] b = new byte[256];
		for(int i=0;i<256;i++)
			r[i] = g[i] = b[i] = (byte)i;
		color_model = new IndexColorModel(8,256,r,g,b);
		double_buffer = new byte[m_width*m_height];
	}

	public void init(){
		m_width = getSize().width;
		m_height = getSize().height;
		setupImageProducer();
		m_screen = createImage(this);
	}

	public void start(){
		if(m_thread == null){
			m_thread = new Thread(this);
			m_thread.start();
		}
	}

	public void run(){
		while(true){
			do_interesting_stuff();
			repaint();
			try{
				m_thread.sleep(250);
			}catch(InterruptedException e){
				System.out.println(e);
			}
		}
	}
}
Da, to je applet! Treba da kreirate HTML fajl da biste ga pokrenuli. Kod koji ide u taj HTML fajl je:

<html>
<body>
<applet code="jDrawApplet.class" width="320"
height="240"></applet>
</body>
</html>
Ok, hajde da brzo pređemo izvor, da vidimo šta taj izvor radi i pokušaćemo da idemo dalje. Kod počinje svoje izvršenje unutar init() metoda zbog načina na koji radi java.applet. Applet pokreće stvari. Inside init(), dobijamo širinu i visinu, i pozivamo setupImageProducer (). Pošto cela ova klasa implementira java.awt.image.ImageProducer, jednostavno inicijalizujemo klasu podataka članova unutar ove setupImageProducer() metode. Tačnije, mi pravimo sivu paletu (u Javi poznatu kao ColorModel-šema boja) i dodeljujemo memoriju za dvostruki bafer.

ColorModel nije baš paleta, to je objekat koji opisuje boju u specifičnim "modu". U našem režimu od 256 boja, veoma je sličan paleti, ali u visokim režimima boje (većina standardnih Java stvari), ima različita značenja i svrhe.

Posle našeg povratka iz setupImageProducer-a(), možemo stvoriti sliku s ovim parametrom (createImage ()). To znači da pravimo sliku, koristeći ovaj ImageProducer. Ova akcija poziva addConsumer() metod ili naš ImageProducer, dajući mu potrošača koji će nas pustiti da podesimo piksele (setPikels()) u toj slici na ekranu (m_screen Image).

Zatim smo upali u metod pokretanja metoda, kombinovanja, pozivanja funkcije repaint i svih interesantnih stvari koje radimo. Repaint () metod, asinhrono poziva update(), koji zauzvrat, poziva boju(). Unutar metoda bojenja() metoda, zovemo blit(), koji kopira naš dupli bafer u m_screen Image, a zatim crtamo sliku g.drawImage() slike ekrana (m_screen Image, što je druga vrsta blita) na sam ekran.

U okviru interesantnih_stvari ne radimo ništa posebno što već ranije nismo radili. Crtamo vertikalne linije u dvostruki bafer na isti način kako smo to radili u gore navedenom primeru za DirectX grafiku.

Možda na prvi pogled ovaj pristup deluje komplikovano (možda zato što vam nisam dobro opisao), ali prilično je jednostavno. Ako malo razmislite o svemu ovome, videćete da ima smisla.

Primer...

Razgovarali smo o tome da najveći deo vremena grafika, jednostavno, crta piksele u memoriju. Ovo ne može biti previše dramatično, ni zanimljivo, dok ne shvatite da crtanje piksela u memoriju može da proizvede veoma interesantne i kul efekte. Ovaj odeljak nije napisan da bi vas naučio nekom određenom algoritmu, već sam o da vam pokaže kako veoma mali program može postići vrlo zanimljive rezultate. Ne osećam da je ono što smo do sada radili dovoljno interesantno da bude inspirativno, pa, je pravo vreme da ubacimo jedan interesantan primer....

Jeste li ikada igrali šah? Nije bitno šta ste odgovorili (u stvari, to je trebalo da bude retorsko pitanje ;-). U svakom slučaju, primeri koje ćemo kreirati podsećaju na šahovsku tablu (ali ne tablu standardne veličine) koja izgleda kao da je pod vodom! Šta mislite, koliko je to komplikovano? Pa, dozvolite mi da vam dam izvor ...

#include <stdlib.h>
#include <conio.h>
#include <math.h>
#include <dos.h>

int sin_x_table[640];
int sin_y_table[400];

void vid_mode(unsigned char mode){
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = mode;
	int86(0x10,&regs,&regs);
}

void make_chess(unsigned char far* where){
	int x,y,tx,ty;
	static int xcounter = 0;
	static int ycounter = 0;
	xcounter = xcounter > 311 ? 0:xcounter+8;
	ycounter = ycounter > 195 ? 0:ycounter+4;
	for(y=0;y<200;y++)
		for(x=0;x<320;x++){
			tx = x + sin_y_table[y + ycounter]+40;
			ty = y - sin_x_table[x + xcounter]+40;
			if((tx % 40 >= 20) ^ (ty % 40 >= 20))
				where[y*320+x] = 0;
			else	where[y*320+x] = 15;
		}
}

int main(){
	int i;
	for(i=0;i<320;i++)
		sin_x_table[i] = (int)(sin(i * 6.28/320) * 10);
	for(;i<640;i++)
		sin_x_table[i] = sin_x_table[i-320];
	for(i=0;i<200;i++)
		sin_y_table[i] = (int)(sin(i * 6.28/200) * 10);
	for(;i<400;i++)
		sin_y_table[i] = sin_y_table[i-200];
	vid_mode(0x13);
	while(!kbhit())
		make_chess((unsigned char far*)0xA0000000);
	vid_mode(0x03);
	return 0;
}
To je to! Ako kompajlirate i pokrenete ovaj program (pod Borland C++ v3.1 DOS) (lako nigde nije prenosiv što sam već opisao u prethodnim pasusima) odmah ćete videti efekat. I to vrlo efikasan, s obzirom na to da je ovaj program tako mali.

Kao što vidite, većina ovog programa je unutar make_chess() funkcije. To je direktno pisanje na video memoriju. Ali, kao što ćete primetiti, make_chess() funkcija ne zna da piše na video memoriji. Mi smo zapravo možemo da zamenimo, tako da pokazivač pokazuje nešto drugo i da postignemo isti efekat na nekom baferu.

Još jedna važna stvar koju treba primetiti u izvoru jeste upotreba sin_x_table [] i sin_y_table []. Uglavnom, kada se bavimo sin() ili cos() u svom kodu, hteli bismo da izbegnemo te funkcije (VRLO su spore!) Postoje mnogi pristupi za izbegavanje, a najčešći je da se naprave "look-up" tabele tih vrednosti. Ponekad, to je vredno truda, ali ponekad, s obzirom na CPU brzinu i FPU sin i cos brzine, nije vredno truda. U ovom primeru definitivno vredi, čak radi lepo pod 486 (FPU sporog procesora). Matematika celih brojeva je brza na bilo kom procesoru, pa pošto to znate, možete namestiti da radi paralelno sa pa pokušajte da koristite da najveći deo vremena, i koriste samo u pokretnom zarezu (FPU) kada znate da može da funkcioniše paralelno sa CPU. Dakle, dobijate FPU kalkulaciju besplatno (najbolje je prepustiti ovu temu optimizaciji tutorijala).

Pretpostavljam da je to sve za ovaj primer. Jedva čekam da ga kompajlirate i pokrenete. Dobra vežba bi bila kad biste pokrenuli ovaj program da postigne isti efekat na nekoj slici (a ne na šahovskoj tabli). U stvari, verovatno ćemo završiti radeći to negde u ovom uputstvu).

-U izgradnji-



Published (Last edited): 17-09-2012 , source: http://www.theparticle.com/pgraph.html