Graphics Programming
  home | services | products | resources | forum
 
about us | contact   
Particle
November 18th, 2024



www.theparticle.com
Main
Services
Products
Resources
Forum
About Us
Contact

Particle Revelation
Hardware Destruction
Quotes
Humor [alpha]
Murphy's Laws

Programming
Java
Java Data Structures
C# Data Structures
Database Design
Graphics Tutorial
Artificial Intelligence

Downloads
SQLRunner
Graphics Tutorials
Hacking Tutorials
Java Applets
MIDI Music
Gov & Misc Docs

Games
Chess Game
Asteroids
Tic-Tac-Toe
Tetris

Applets
DRAW!
FlightBox
pWobble 3D
pRunner
NYU HWs
Swarms
Geometry
Chaos
Machine Learning

Academic
CISC 7700X
CISC 7512X
CISC 7500X
IT Mngmt (old)
SW (old)
Networks (old)
OS (old)
AI (old)
App Dev (old)
C++ (old)
OOP (old)
Web (old)
Perl (old)
DBMS (old)
ProgLangs (old)
PHP (old)
MltMedia (old)
Oracle (old)

Misc
Privacy Policy
Publications
profphreak.com



Graphics Programming.

© 1998, Particle
http://www.theparticle.com

This article is translated to Serbo-Croatian language by Web Geeks.

Introduction...

Programming graphics is a lot harder than most people think. It involves tons of knowledge. More precisely, you need to know the concepts behind data structures, math, and be fairly good at some programming language. The most important thing however, is that you have to want to learn it! It's is next to impossible to learn anything if you don't want to learn!

If you look at your computer screen (look closer), you'll notice that it is made up of little colored dots. These dots are called pixels. Each pixel has a specific color, and location on screen. Now, a simple definition of graphics programming is to make sure that right pixels appear in right places at right times. If we can make this happen, we can consider ourselves graphics programmers.

The above definition is easier said than done. Keeping track of each pixel is next to impossible without some well designed algorithms. And that's what most of this document is all about, algorithms!

Code examples will mostly be in Java, since it's the only system independent language capable of graphics. I will however, illustrate some things in C/C++ as well.

The Basics (getting it going)...

Before we get too deep into the topic of graphics programming, I'd like to illustrate a few basics. If you already know how to set pixels on the screen, change video mode, etc., then you can simply skip this section. This section is here so that you know how to experiment. I don't just want to give you an algorithm to draw a line, with no method of actually drawing it.

These next few sections may be system dependent, hardware dependent, compiler dependent, etc. But that's the trouble with most graphics, it's next to impossible to create 100% system independent graphics (excluding Java).

Ploting Pixels...

Your computer screen is two dimensional; each pixel on the screen has some location, illustrated by the x, and y values. Where x is the horizontal offset from the left side of the screen, and y is the vertical offset from the top (or sometimes the bottom), of the screen. So, a location of x = 0, and y = 0, is the top left corner of your screen. Knowing about the x, and y, you can think of your screen as a two dimensional array. With x, horizontal locations and y vertical locations. Each value inside the array represents a pixel. To plot a pixel on the screen at say x = 70 and y = 100, you'd put pixel information into that two dimensional array, at location [100][70].

Now, lets make it even more abstract! Think of your screen as a one dimensional array, kind of like starting at the upper left corner going right till end of line, and starting again on the other line, and continuing like that until it hits the lower right corner. To use this approach to plot pixels, we'll also need to know the width of the screen. Lukily for us, most of the time we'll know the width and height of the screen exactly. For example, lets name our one dimentional array "screen", so, to plot a pixel of color "color" at location x = 70, and y = 100, we'd do something like this: screen[y * width + x] = color. As you can see (or trace), it is exactly the same thing as screen[y][x] = color, only we're specifying the width manually.

Most of the times, when we work with graphics, we're working with a pointer. A pointer can be to memory, or directly to a video device. Our functions that "do graphics" don't have to be concerned with where the pointer points, we just need to draw pixels into that pointer (or it's offsets) as though it was a real screen. I'll show later where drawing to memory can be very useful.

DOS Graphics...

Modern day Operating Systems have outlawed drawing directly to devices. However, it is still possible to do in Real Mode DOS programs (and with a little trickery, under Protected Mode as well). For the time being, assume we're working in Real Mode DOS, using Borland C++ v3.1 (or DOS version of Turbo C++). No, windows compilers will not work!

Before we can do anything graphical however, we need to set the graphics mode. We do this by calling BIOS. The function to change the mode is AH = 00h, AL = mode, using a 0x10 interrupt. If you are not familiar with assembler or low level programming, don't worry; most things here are fairly easy to understand. A C language function to set the video mode in DOS is:

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);
}

As you see, I've defined a special type byte_t, which is exactly the same thing as unsigned char. Sometimes, it helps to abstract things like that... I'm also using union REGS to work with CPU registers. The REGS union is defined inside the <dos.h> file. I'm setting regs.h.ah to 0, and regs.h.al to the video mode I'd like to switch to, and then calling int 0x10. It is worth mentioning that this code will only work in DOS. And now, for a simple example using the code above. (code for DOS version of Borland C++ v3.1 follows)

#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;
}

Remember all the stuff I've said about working with a pointer? Well, this code illustrates that as well. Under DOS (using a VGA), we can address the screen directly, and that "special address" is 0xA0000000 for video mode 13h, which in my opinion the easiest video mode to work with.

We define a pointer video_buffer, to point directly to 0xA0000000, and the set_pixel(int, int, byte_t) function simply plots the color at appropriate location on the screen. The video resolution of mode 13h is 320x200. Meaning, that the width of the screen is 320 pixels wide, and 200 pixels high. So, as you can see, our plot pixel function multiplies the y coordinate by the width, and adds x, and later setting that location to color.

It is important to note that you wouldn't want to plot pixels outside the 320x200 range, since if you do, you can easily erase parts of DOS, and crash your computer. (Of course, most of the "modern day" Operating Systems won't crash, and will simply kick your program out of the system.)

Inside our main(), we're first setting the mode to 13h, then we loop until a key is pressed, drawing random pixels at random locations on the screen. At the end, we simply return to the DOS video mode, which is nicely defined to be 0x03. 0x03 is a text based mode (it displays text), with text resolution of 80x25. This 0x03 mode is a 16 color mode, while the 0x13 mode is a 256 color mode.

Most of the graphics people work with is in 256 colors, but there seems to be a trend moving towards 16 bit color (that's 65535 colors).

Now, a bit about "convention," and speed. Using the video_buffer pointer to plot pixels is slower than using other pointers to other memory. This is due to the fact that when you plot pixels into video memory, the system performs memory mapped I/O, so, most of the time, we'd like to avoid that. The common way to avoid it is to plot pixels into regular memory, and later "blitting" (quickly copying) that memory onto the screen. This reduces flicker and increases overall speed of the process.

We do this by first allocating enough memory for the whole screen and clearing that memory. We then write to that memory, and blit (copy it) it to the video memory. This approach is commonly know as double buffering.

#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;
}

The code above first allocates a memory buffer, the size of the screen, it then checks to see if we actually did allocate memory. Error checking in real mode memory allocation is VERY important, since there isn't much memory to go around. While testing the above program, I must have gotten "sorry, not enough memory" message dozens of times, and I have 64 megs of ram! Real Mode only uses the 640k of DOS memory.

We then continue by clearing the memory we've allocated. This is important, since it DOES need clearing (you can try without it, and see what you get). We then fall into the !kbhit() loop, create random x, and y, and plot a random pixel in double_buffer at these random x, and y. We then call blit(double_buffer), which copies the double_buffer to video_buffer, which in turn is set to 0xA0000000.

You'll also notice that this program is much slower than the one before. This is not because we're using the double buffer, but because we're copying it too much. Every time though the hoop, we're blitting. Most of the times, we'll be drawing a whole lot to the double_buffer (not just one pixel), and blitting as rarely as possible.

One little note about the program before we go on. To make it work, you'll need to compile it under at least a medium memory model. If you don't, it will not be able to allocate memory using farmalloc(). In Borland C++ v3.1, you go into Options| Compiler| Code Generation| and select the Medium Model.

The Palette...

Working with 256 colors may seem restrictive, but most of the time, it's not. The reason is because the restriction is only on the number of colors that can be displayed at once. The VGA is actually capable of displaying 262,144 colors. The trick to this is that the pixels we're plotting into the video memory, are not exactly pixels, they're references into a color look-up table. This color look-up table is referred to as the palette, and is made up of 3 bytes for each reference. The 3 bytes are Red, Green, and Blue, (RGB) respectively. So, to plot a definite red pixel at x = 100, and y = 100, we'd set, for example, 50th element in this color look-up table to 0xFF, 0x00, 0x00, (this is RGB, where R <red> is 0xFF, B <blue> is 0x00, and B <blue> is 0x00). This should make pixel value of 50, red. So, we then plot a value of 50 into memory location x = 100, and y = 100, and we have a red pixel at that location.

This may sound complicated, but it's not. Most of the time, we set the palette once, and work with a fixed palette throughout. Before we can set that "one" palette, we need to create it (or use a "standard" one). For this document, we'll mostly use a nicely defined "standard" Windows palette. (I've created a BMP file with standard windows colors, saved it, and later extracted the palette out of it using my own program.)

There are operations on the palette. We can set one index in the color look-up table to a specific 3 byte entry, or we can retrieve these 3 bytes from the specific index in the palette. There is a catch under VGA however. The VGA's palette is made up of three 6 bit entries (not 8 bit entries as a byte would suggest). So, 6 * 3 = 18, and 2^18 is 262,144. And that's where the maximum number of colors comes in. Where it possible to store a byte, we'd have 8 * 3 = 24, and 2^24 is 16,777,216; that's a lot more colors than the standard VGA. Most non-VGA approaches support a byte (8 bits) based palette (a good example is JPEG images with 24 bit color, they're not exactly palette based, but RGB based).

From the information you have, you can already calculate the size of the 256 color palette. It's 3 bytes per pixel, 256 different pixels, so, 3 * 256 = 768! And in fact, 768 is the size of most 256 color palettes, including the one in PCX files. Sometimes, however, they store 4 bytes per pixel for speed efficiency (as in BMP files). The last byte is not used. Most times the palette order is RGB, but sometimes, it's BGR (again, as in BMP files) or something even more abstract... There are many conventions for this; you only need to know the basic few, and most you'll be able to figure our pretty easily if you have the basic understanding of the concept.

Now, lets write a program that reads and sets the palette, and does some cool effects simply by playing around with the palette (without actually drawing anything inside the main loop).

#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;
}

If you look closely at the source, you'll see that it's just an extension of the old source. The extensions are functions that allow us to manipulate the palette. We have a setpalette() function, and a getpalette() function. These set and get the specified palette value respectively. Inside the main(), we read our palette.dat file, which is Windows default palette. We then set this palette. Notice that inside our setpalette() function, we're shifting the palette value by 2. This is because the actual number of bits a standard VGA palette has is 6 (as mentioned earlier). So, we shift by 2, to eliminate the insignificant two bits, and preserve the significant ones.

The vline() function draws a vertical line. This is not the best way to draw lines, and we'll discuss lots of different line drawing algorithms when we're done with this part. We then blit() the double_buffer to the screen, and start playing around with the palette. Notice that we're not writing anything to video memory, and yet, the whole screen is moving (that's the beauty of palettes).

I guess if you go over the above program, you'll know (and figure out) quite a lot about palettes. So, don't hesitate, and go over it! (definitely compile it, and see the effect) The final distribution of this document will also include the palette file as well, but if you don't have it, you can simply create your own palette. Something in the lines of:

for(i=0;i<256;i++){
	palette[i][0] = palette[i][1] = palette[i][2] = i;
}

Or you can play around with for loops, so see what looks best. That's it for the basics of getting the graphics working under Real Mode DOS under Borland C++ v3.1!

Protected Mode Graphics...

Graphics under Real Mode DOS are nice, but sometimes, there are just too many restrictions in plain vanilla DOS. Most notable of these restrictions is the drastic limitations on memory. (sometimes, I want my programs to use much more than just 640k of RAM!) Not to mention the anoyance of dealing with far pointers! Well, you get the picture, doing anything serious in Real Mode DOS is hell!

To the rescue, comes the safe and cool 32 bit Protected Mode. The compiler we'll be using in this part is DJGPP v2. It is a FREE 32 bit Protected Mode compiler for DOS. Don't let that "DOS" throw you off track, DJGPP works perfectly under Win95/98, and WinNT as well. Although you can't compile anything using Win32 API, or MFC, you can, however, write DOS based programs that are very powerful and cool looking, and use as much memory as they need (up to 4 gigabytes of RAM  if needed).

You can download DJGPP from http://www.delorie.com, simply click on the DJGPP link, and follow download instructions. If you have any problems with the setup, review the FAQ which is also available at that site (or e-mail me). There is also a tool called RHIDE, which nicely simulates Borland's IDE (so, you can feel right at home when you start using DJGPP).

Assuming you've downloaded (and installed) DJGPP, lets now port our Real Mode DOS program from the previous section to 32 bit Protected Mode DOS, using DJGPP. This may sound more complex than it really is.

Since most of the source is in C, we dont' have to worry about most of it. The parts we do have to worry about are the memory writing parts. Because now we're in protected mode, we can't just write to 0xA0000000, since that memory is protected (just as every other memory in protected mode). We have several options however, we can use a DJGPP specific system call to copy the double buffer to video memory (replacing our blit() function), or we can temporarily disable memory protection, write to the screen, and then enable memory protection.

There are advantages and disadvantages to both! Using a system call to copy memory is safe and compatible with many system, including DOS/Win95/98/NT (yes, it works under NT4!), but it's too slow! Disabling memory protection, copying, and later enabling memory protection is not very compatible (nor safe), but it's a lot faster. This second approach works under DOS/Win95/98, but NOT under WinNT! Well, lets write the code that gives us the option of using both of these approaches, and I'll let you pick the one you want to use...

#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;
}

As you can see, the code didn't change much. Actually, only memory handling changed. The farmalloc() became a simple old malloc(), farfree() turned into free(), etc. The main attention should be towards the blit#() functions... There are currently two of them. (I'm sure there are many more approaches to write to video memory in protected mode, one that's worth looking into on your own is _farnspokel() defined in <sys/farptr.h> file.)

The blit0() is the simple and easy one. It simply calls the system to perform the copy, and that's it. What you should notice is that we're copying to 0xA0000, that's because this is the physical memory of the screen (notice four 0s). In protected mode, we don't have segments or offsets as in real mode (actually, there are segments and offsets in protected mode, but they have different meaning there), so, in protected mode we use a full physical address whenever we need to address a memory mapped device such as the VGA.

The blit1() is the more complicated one. It first calls __djgpp_nearptr_enable(), which effectively disables memory protection. The function does no error checking to look at return value of __djgpp_nearptr_enable(). Once memory protection is gone, we can simply write anywhere (including the screen). (or overwrite the Operating System if we wished <or had a bug that did it>!) We use regular memcpy() to copy the buffer to the screen, which is the 0xA0000, from a __djgpp_conventional_base (conventional memory base) offset. We then continue by enabling memory protection by calling __djgpp_nearptr_disable().

As it is, this method is not much faster than the one before. The reason is that functions that enable and disable memory protection are quite slow. So, *usually,* once you've gotten past the debug stage of your project, you'll simply disable memory at the beginning of your program, and enable it at the end. This is VERY unsafe though, and can lead to some serious problems (including complete loss of everything).

Windows Graphics...

Windows is the future (or so they say), so, apparently, doing graphics in plain old DOS is not a good idea (or so they say). I still think it's a good idea, since only in DOS you can isolate yourself from the stupidity of the Operating System, and worry only about YOUR program, and not how Windows runs your program. In DOS, most of the times, your program becomes the operating system. Most games ARE operating systems; they handle the I/O, keyboard, everything! Anyway, it seem Windows is winning the war, and it's time to learn Windows programming.

One of the hardest things for DOS programmers to comprehend about Windows is that you're not in charge anymore; Windows is! If your program wants to do ANYTHING, it has to ask Windows to do it. You can't just go around plotting pixels into video memory, you have to ask windows to do it for you. Everything in Windows is under the control of Windows; and your program has to respect this "understanding," or it will be kicked out of the system.

For this section, you'll need Microsoft Visual C++ v5 Pro (or later). This section will cover basic Windows graphics, which are widely portable to all Windows systems (no DirectX stuff).

Every one of the tutorials and books (ones I've seen) start out the easy way, they say "use a wizard to create a shell MFC program, and go from there"... well, this document will not do this. I'll write this code in plain old C, using plain old Win32 API. This is not because I hate MFC (although it is a factor), but because it doesn't teach you Windows programming. A lot of the so called Windows programmers only use MFC, and have no idea of the underlying Win32 interface (or how a Windows program is constructed).

Lets get to the point of actually writing it (and not bashing Microsoft for the buggy OS). Every Windows program starts out inside a WinMain() function, not a main() function, as it is the case with most C programs. A Windows program first creates a window class (a WNDCLASS structure), registers it, creates a window of that class, and displays that window. Simple?

Then it should create a DIB section to draw into (the double buffer) (the DIB stands for Device Independent Bitmap). This is done by first setting elements of BITMAPINFO structure, creating and realizing the palette, etc., and then calling CreateDIBSection() which gives us the double buffer to draw into.

After all that, we also need a blit() function! This is harder than it is in DOS! We start by creating a device context, by calling CreateCompatibleDC(). Selecting our HBITMAP (bitmap handler) for that context. Blitting using BitBlt() function (which is quite fast most of the times), and returning settings to normal (doing clean up, etc.).

The program runs inside a while loop (in WinMain() function), which loops until the program receives the WM_QUIT message. Now is a good time to remember that everything in Windows is done using messages ;-) Our program will receive this message whenever we try to destroy the application's window. (since we're calling PostQuitMessage() function whenever WndProc() gets a WM_DESTROY message.)

This may be more complex than in DOS, and it is IMHO! But that's how it is and there is very little you or me can do about it. Now, shall I leave you to figure out the details of all this, or shall I give you the source as an example? (you'd be surprised how many books "leave out the details" of actual implementation of something)

#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;
}

Well, don't expect a huge explanation of all this stuff here; after all, this is not a Windows programming tutorial. If you have problems understanding the above code, I suggest you get a good Windows Programming book (although they're pretty hard to come by). I'm hoping that if you have the compiler (Visual C++ v5 Pro) to compile this source, you'll be somewhat comfortable with Windows stuff... (And have a MSDN CD, which has TONS of useful stuff about Windows, and references to all the functions and variables used in this program.)

Besides, I think I've made it as simple as it can possibly get!

DirectX Graphics...

For somebody who've never programmed anything using DirectX, it can be mind boggling to actually try to understand all this stuff. It is complex (there is too much of it)! Think of the entire DirectX as a collection of libraries for all kinds of "game" related stuff (sound, graphics, input, etc.) The part we'll be dealing with in this section is DirectDraw. We'll be using it to enter a full screen 256 color, double buffered,  mode under Windows, and draw stuff to the screen.

You should know that DirectX is not too portable (at least not at the time of this writing). A lot of the DirectX programs simply don't run under WinNT4! (and ones that do run, only support some limited video modes, and not others) Then, there are system that still don't have DirectX installed! (like my Win95 machine at home) All this is about to change however, NT5 and Win98 both come with DirectX 5 support, and by the time NT5 ships, it will probably support DX6.

To write anything using DirectX however, you need to have it! So, if you haven't already, go to the Microsoft web-site, and download DirectX SDK (or order it on a CD-ROM) (it's quite large!). The place to start looking for it is: http://www.microsoft.com/directx. If you have one of later versions of Visual C++, chances are, that some version of DirectX is already hanging around your hard disk (or at least the library files to link to) (and not the examples, etc.)

Before we start, I'd like to point out a few things. Although DirectX programs run only under Windows, their power is still in their full screen modes. (so, don't expect to be writing a windowed application using DirectX any time soon...) If you need your graphics program to run in a window, use the approach described earlier in a Windows Graphics section.

In DirectX, most of the time, we're dealing with a thing called a Surface. We draw to surfaces, etc. Before we can do anything however, we need to create a DirectDraw object. Then we specify whether we'd like to run full screen, etc. Then we create the primary surface (think of primary surface as kind of a video_buffer in our previous examples), and then we create a back surface (think of back surface as kind of a double_buffer). To draw, we first get a memory pointer the back surface (locking the surface), we draw to that pointer, and later unlock the surface (and do something which is kind of like the blit() function...)

The explanation above is too simplistic! The code below is a bit more detailed.

#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;
}

I suggest you go over the source (several times); eventually, you'll start understanding it. The program does exactly the same effect as the program in the Windows Graphics section (only full screen!). To compile/link the program, you'll need DirectX libraries, and link this program with ddraw.lib

Another thing you might consider; using DirectX in your program is a LOT easier if you're writing your programs using C++. Lots of these long functions, become fairly short methods of objects. (it's a lot cleaner to use C++ with DirectX) (I just hate C++ <I actually used to love it a few years back.>, so, I did it this way!)

Notice before we go on: This program works on most Windows platforms, but this is due to the fact that it's mode 640x480 (standard Windows mode)! If you set the mode to 320x200, or to Mode X (which is: 320x240), the program will NOT work under NT4. However, the program will work under Win95/98. To change these different modes, modify the W_WIDTH and W_HEIGHT defines at the top of the code. (I really hate it when a popular game doesn't run full screen at 320x240 under NT4; i.e.: Quake 2 <hey, not all of us have a Pentium 2, 400Mhz with a Voodoo 2 Accelerator!>) There are approaches to make this defect go away. You can still use 640x480 mode, and do a stretch upon blitting. I doubt a 2x stretch would hit the performance too harshly. Anyway, I hope NT5 will support all these low resolution modes... (I'm definitely not going back to Win98 just to play games full screen!)

Java Graphics...

Yeah! Java. Most people think Java is too slow and useless for graphics, and they may be right (I have yet to come across a really really cool graphics program that outperformed it's C/C++ counterparts.) Nonetheless, Java is the most elegant Object Oriented language I've seen (C++ is junk compared to Java!).

"Fast graphics in Java" is somewhat an oxymoron. But it is getting faster, and in some cases can compete with C/C++. There are two ways of doing graphics in Java, using the libraries, or creating a double buffer to draw into (similar to what we've been doing in previous sections). Using libraries is a lot easier and in some cases an easy solution. It's much easier to simply say g.drawLine(0,0,100,100); to draw a line, than to setup tons of stuff, and write the code for an optimized line algorithm yourself.

First, we'll worry about the Java graphics library approach, since we'll probably be using it later to illustrate a few quick algorithms. (It's easy to use!) Once we're done learning the "library" approach, I'll show you a trick most books, tutorials, and examples try to avoid. To implement our own ImageProducer, and create fairly fast graphics under Java.

For the examples in this section, you'll need a copy of the JDK, which you can get for free from http://java.sun.com. I suggest you get the latest version (even if it's beta). Microsoft Visual J++ will also work for most examples here, but I've found that J++ is very outdated when it comes to Java libraries, and I think that most people are better off with a plain and simple JDK.

Well, lets get to writing the code! We will write a regular application which opens a window, and draws tons of random polygons into it. Here it is:

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);
	}	
}

As you can see, the code is fairly simple. We open a window, set it's size, add a listener, etc. Draw to that window, etc. and that's it! (a lot easier than previous examples) Not to mention this program is system independent, and will even work under UNIX as well as Windows. I doubt this program needs much explanation (it's fairly simple) (trace it if you don't understand it). The reason we don't need a palette in this example is because the colors are in RGB format. When we create a new Color, we're specifying the RGB integer. Now, lets move onto something more complex, our own ImageProdicer!

Right now, it's time to learn how to draw to a double_buffer under Java! In reality, it's actually going to be a triple buffer, since we'll first setup a double_buffer for a java.awt.Image, and then (after blitting our byte[] buffer to the Image), we'll blit the Image to the screen. There are more elegant ways of handling that, but I just didn't want to spend much time on this (Gonna leave on an extended vacation pretty soon, and want to get as much done as possible ;-)

Later (if I feel like it), I'll show a JDK 1.2 approach to this, which in my opinion is much better. Unfortunately, it's not as widely compatible as the ImageProducer one. Well, lets get right to the source!

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);
			}
		}
	}
}

Yes, it is an applet! You have to create an HTML file to run it. The code that goes into that HTML file is:

<html>
<body>
<applet code="jDrawApplet.class" width="320"
height="240"></applet>
</body>
</html>

Ok, lets briefly go over the source, and what it does, etc., and try to move on. The code starts it's execution inside the init() method, due to the way java.applet.Applet initializes stuff. Inside init(), we get width and height, and call setupImageProducer(). Since this whole class is implementing java.awt.image.ImageProducer, we're simply initializing class data members inside this setupImageProducer() method. More specifically, we're creating a gray scale palette (in Java known as a ColorModel), and allocate memory for the double_buffer.

ColorModel is not exactly a palette, it's an Object that describes color in specific "mode". In our 256 color mode, it's VERY similar to a palette, but in high color modes (most of standard Java stuff), it has different meanings and purposes.

After our return from setupImageProducer(), we create an Image with this as the parameter (to createImage()). This means that we're creating an Image, using this ImageProducer. This action, calls addConsumer() method or our ImageProducer, giving it a consumer that will let us setPixels() in that ImageConsumer (the m_screen Image).

We then fall into the run() method, loop forever, calling repaint(), and do_interesting_stuff(). The repaint() method, asynchronously calls update(), which in turn, calls paint(). Inside the paint() method, we call blit(), which blits our double_buffer into the m_screen Image, and then we g.drawImage() the m_screen Image (another kind of blit()) to the screen.

Inside the do_interesting_stuff(), we're doing nothing special that we haven't done before. We're drawing vertical lines to the double_buffer, the same way we were doing in the DirectX Graphics demo above.

This approach may seem complicated at first (maybe because I did a bad job at describing it...), but it's pretty simple. If you think about it, it will start making a lot of sense.

An example...

We've talked about the fact that most of the time, graphics is simply plotting pixels to memory. This may not be too dramatic, nor interesting, until you realize that plotting pixels to memory can produce quite interesting and cool looking effects. This section is not to teach you any specific algorithm, but just to show you that simply by plotting pixels into memory, a very small program can achieve very interesting results. I don't feel that what we've been doing so far is interesting enough to be inspiring; so, here's where this simple example comes in...

Have you ever played chess? Doesn't matter what your answer is (in fact, it was meant to be a rhetorical question ;-). Anyway, the examples we're about to create is going to kind of a chess board (not of standard size), as seen underwater! How complex do you think the program will be? Well, let me give you the source...

#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;
}

That's it! If you compile and run this program (under Borland C++ v3.1 DOS) (easily portable anywhere, as described in previous sections) you'll see the effect. And it is very effective, considering that this program is so small.

As you see, most of this program is spent inside the make_chess() function. It is writing directly to video memory. But as you'll notice, the make_chess() function doesn't know it's writing to video memory. We can actually replace that pointer buy something else, and have it do the effect to some buffer!

Another important thing you should notice in the source is the use of sin_x_table[] and the sin_y_table[]. Most of the time, when we deal with sin() or cos() in our code, we'd like to avoid these functions (they're VERY slow!). There are many approaches to avoiding them, and the most common one is to create these "look-up" tables of these values. Sometimes, it is worth the effort, but sometimes, considering CPU cache speeds and FPU sin and cos speeds, it's not worth it. In this example it's definitely worth it, it even works nice under a 486 (slow FPU processor). Integer math is fast on any processor, so, try to use that most of the time, and only use floating point (FPU) when you know you can make it work in parallel with the CPU, thus, getting the FPU calculations for free. (but this subject is best left to an optimization tutorial)

I guess that's it for this example. I greatly earge you to compile and run it. A good exercise would be to make this program do the same exact effect only using some picture (not a chess board). (In fact, we'll probably end up doing it sometime in this tutorial...)

- Under Construction -

Copyright © 1998, Particle

© 1996-2024 by End of the World Production, LLC.