[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Linux games programming tutorial-part2



I'm a idiot. I forgot to attach the file. Scuse me.
Graphics programming basics: A PCX loader/viewer.

It's now time for us to have some fun: This part will cover graphics programming basics through the programming of a pcx viewer using svgalib and GGI. Loading images is very important in game development, and as the pcx format is quite simple to read it is the best to get started with graphics. After reading this (long) part, you should be capable of playing with sprites.
Through this part we'll type several programs. Edit and compile them all in the same directory, because some of them will be dependant.

1)Checking your svgalib installation.

First, make sure you have the svgalib libraries installed on your system:
$ls /usr/lib/libvga*
/usr/lib/libvga.a            /usr/lib/libvgagl.a
/usr/lib/libvga.so           /usr/lib/libvgagl.so
/usr/lib/libvga.so.1         /usr/lib/libvgagl.so.1
/usr/lib/libvga.so.1.2.13    /usr/lib/libvgagl.so.1.2.13

If they are installed, you should get a similar output.
If you don't have them, install the svgalib-dev package that come with your distribution.
With RedHat it should be: 
rpm -ivh svgalib-dev<version>.rpm
Once you have found this file on your cd.

And with debian:
dpkg -i svgalib-dev<version>.deb

Now it should be okay, you are ready to compile with svgalib.

2)How to use a graphic library.
What is said here apply to ANY graphic library. The use of a graphic library include several steps:
-1:Initialise the library
-2:Initialise the graphic mode
-3:Work with the library
-4:Eventually restore a text mode
 OR
-5:Eventually call a fonction that will quit properly the library

Keep in mind that using a library, especially the svgalib which require root rights, without initialising it, will cause unpredictable results. So check you respect the initialise-use-quit order before launching your programs.

3)Our first program using svgalib:
This program will display some noise on our screens. Type this:

svgatest.c:
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>

int main()
{
  GraphicsContext physicalscreen;

/* Initialise */
  if (vga_init()!=0) {printf("Error initialising svgalib!\n");_exit(1);}
  if (vga_setmode(G320x200x256)!=0) {printf("Can't set mode 320x200x256!\n");_exit(1);}
  if (gl_setcontextvga(G320x200x256)!=0) {printf("Error setting context!\n");_exit(1);}
  gl_getcontext(&physicalscreen);
  if (gl_setcontextvgavirtual(G320x200x256)!=0) {printf("Error setting virtual context!\n");_exit(1);}

  gl_setpalettecolor(0,0,0,0);
  gl_setpalettecolor(1,63,0,0);
  gl_setpalettecolor(2,0,63,0);
  gl_setpalettecolor(3,0,0,63);
  gl_setpalettecolor(4,63,63,63);

/* Use */
  gl_clearscreen(0);
  gl_setpixel(20,20,1);
  gl_fillbox(50,50,100,100,2);
  gl_line(30,20,300,180,3);
  gl_fillbox(100,80,100,100,4);
  vga_waitretrace();
  gl_copyscreen(&physicalscreen);
  getchar();

/* Restore */
  vga_setmode(TEXT);
  return(0);
}

Warning: Most of the above functions return an error code if they have failed. Always check it; if you draw something while the gl_setcontext function failed your program will badly crash.

Compile it with:
$gcc -Wall svgatest.c -o svgatest -lvgagl -lvga

Remember the libraries? We link our program to 2 libs:vgagl and vga (keep that order). Libvga is the core svgalib library, its main purpose is to initialise graphics modes. Libvgagl is an extension to the vga library that allow you to draw graphic primitives. And, look at the source: Each function name we called start by "vga_" or "gl_", that's of course to allow the programmer to know which library the function own. 

Become root, and launch it:
#./svgatest

It should display some lines, boxes, etc... on your screen, wait for a keypress, and quit. Not really wonderfull, but that's a start. Let's check it line by line:

  GraphicsContext physicalscreen;
Svgalib works with graphics contexts. A graphic context is defined by its mode, its size and its depth. It allow you to use a virtual screen that is larger than the physical one (eg you can set a screen resolution of 320x200 and use a virtual screen of 640x480).

  vga_init();
Well, this function doesn't need further explanations. It just initialise the library. Never forget it, unless you like troubles. It return 0 on success, otherwise an error code.

  vga_setmode(G320x200x256);
This function sets the physical screen mode. It doesn't depend of any graphic context! It just sets your screen resolution. Return 0 on succes, otherwise an error code.

  gl_setcontextvga(G320x200x256);
Here it comes. This function sets the graphic context mode. It should be the same as the mode you set with vga_setmode. The created graphic context become the active graphic context.
In clear, after the function call we have:

          320
     --------------  
    |              |
200 |              |
    |              |
    |              |
     --------------
       User screen
     Current context
      We draw here

And again, it return 0 on succes, an error code on failure.

  gl_getcontext(&physicalscreen);
This function will save the actual graphic context (actually the user screen) to the physicalscreen variable, which will point to the user screen.

          320
     --------------  
    |              |  
200 |              |<----physicalscreen
    |              | 
    |              | 
     --------------
       User screen
     Current context
      We draw here

  gl_setcontextvgavirtual(G320x200x256);
It will allocate a virtual screen in system memory and will make it the current context. Return 0 on success, otherwise an error code.

          320                                           320
     --------------                                --------------
    |              |                              |              |
200 |              |<----physicalscreen       200 |              |
    |              |                              |              |
    |              |                              |              |
     --------------                                --------------
       User screen                                  System memory
                                                   Current context
                                                    We draw here

  gl_setpalettecolor(0,0,0,0);
  gl_setpalettecolor(1,63,0,0);
  gl_setpalettecolor(2,0,63,0);
  gl_setpalettecolor(3,0,0,63);
  gl_setpalettecolor(4,63,63,63);
As you have seen we initialised our screen to a 256 (8 bits per pixel))color mode. 256 colors modes are indexed; this means each pixel on the screen will store a color number, and not the real color, contrary to 16bpp or 24bpp modes. The gl_setpalettecolor function allow us to set the color palette. The first argument is the number of the color we modify. The 3 last are the red, green and blue value we affect to the color. The values can go from 0 to 63. In our example the color #0 will be totally black; the #1 is a violent red, the #2 a green, #3 a blue and #4 is white. The colors #5-#255 are not initialised, but that's not a problem since we don't use them.

  gl_clearscreen(0);
Simply fill the current graphic context with the color we gave as argument (black).

  gl_setpixel(20,20,1);
Will set the pixel at position (20,20) to color #1 (red).

  gl_fillbox(50,50,100,100,2);
Fill a box at position (50,50) with size (100,100) with the color#2.

  gl_line(30,20,300,180,3);
This one draw a line from (30,20) to (300,180) in color #3.

  gl_fillbox(100,80,100,100,4);
Another fillbox.

After all those draw our system is like this:
          320                                           320
     --------------                                --------------
    |              |                              | \            |
200 |              |<----physicalscreen       200 |   -\-----    |
    |              |                              |   |   \ |    |
    |              |                              |   -------\   |
     --------------                                --------------
       User screen                                  System memory
                                                   Current context
                                                    We draw here

As you can see everything is draw into the memory, so nothing appear to the user screen for now.

  vga_waitretrace();
This function is used to wait the screen vertical retrace. It's purpose is to synchonize the animation with the screen in order to have a correct display. However in this example it's not very usefull, so you can avoid this line :).

  gl_copyscreen(&physicalscreen);
Back to the graphical contexts. This will copy the current graphic context (the one we initialised with gl_setcontextvgavirtual) to the context we give in parameter (in our example it's physicalscreen which correspond to the user screen). In clear it will display the image:

          320                                           320
     --------------                                --------------
    | \            |                              | \            |
200 |   -\-----    |<----physicalscreen<--\   200 |   -\-----    |
    |   |   \ |    |                       \----- |   |   \ |    |
    |   -------\   |                        Copy  |   -------\   |
     --------------                                --------------
       User screen                                  System memory
                                                   Current context
                                                    We draw here

  getchar();
Huh?

  vga_setmode(TEXT);
With this we go back to a safe text mode before ending the program.

Well, don't worry if you have troubles understanding the graphics context concept. In our example we use them in order to draw into a memory buffer before displaying the entire image. Drawing directly to the screen will make the primitives appear one to one, what look not really beautiful. You can use the same initialisation part for your program if you don't want to worry with contexts, then call gl_copyscreen when you want to display the image.

Also, this little explanation doesn't replace the svgalib documentation. Each one of the above called function has a manpage; you can also find a more general documentation by typing "man svgalib" and "man vgagl".

4)The same with GGI, but before...
... We will install GGI! As it certainly doesn't come with your distribution, you'll have to download the source then compile and install it.

You will need two files:
libgii-<version>.tgz
libggi-<version>.tgz

You can get them at http://www.ggi-project.org.

To avoid further problems, check that you have the line "/usr/local/lib" in your /etc/ld.so.conf file. If not, add it. The GGI libraries will install themselves to /usr/local/lib, and the linker (ld) doesn't check this directory for libraries by default.

Libgii is a low-level input library. It's needed to use GGI.

First untar the source tarball:
$tar xvfz libgii-<version>.tgz

Then change to the directory that have been created:
$cd libgii-<version>

Compilation is done by the classic configure-make-make install:
$./configure
$make

Become root.
#make install
#ldconfig

Now you can install libGGI. As for libgii, untar, compile and install it:
$tar xvfz libggi-<version>.tgz
$cd libggi-<version>
$./configure
$make

Become root.
#make install
#ldconfig

If you got no error you are ready to compile with GGI. Else feel free to contact me.

5)The same with GGI.
Here comes the source:

ggitest.c:
#include <stdio.h>
#include <ggi/ggi.h>

int main()
{
/* Initialise */
  ggi_visual_t vis;
  ggi_color palette[5];

  if (ggiInit()!=0) {printf("Error initialising GGI!\n");_exit(1);}
  if ((vis=ggiOpen(NULL))==NULL) {printf("Error opening visual!\n"); ggiExit(); _exit(1);}
  if (ggiSetGraphMode(vis,320,200,320,200,GT_8BIT)) {printf("Set the GGI_DISPLAY variable to the correct value!\n"); _exit(1);}

  palette[0].r=0;
  palette[0].g=0;
  palette[0].b=0;
  palette[1].r=0xffff;
  palette[1].g=0;
  palette[1].b=0;
  palette[2].r=0;
  palette[2].g=0xffff;
  palette[2].b=0;
  palette[3].r=0;
  palette[3].g=0;
  palette[3].b=0xffff;
  palette[4].r=0xffff;
  palette[4].g=0xffff;
  palette[4].b=0xffff;

  ggiSetPalette(vis,0,5,palette);

/* Use */
  ggiSetGCForeground(vis,0);
  ggiFillscreen(vis);
  ggiSetGCForeground(vis,1);
  ggiDrawPixel(vis,20,20);
  ggiSetGCForeground(vis,2);
  ggiDrawBox(vis,50,50,100,100);
  ggiSetGCForeground(vis,3);
  ggiDrawLine(vis,30,20,300,180);
  ggiSetGCForeground(vis,4);
  ggiDrawBox(vis,100,80,100,100);

  ggiFlush(vis);
  ggiGetc(vis);

/* Quit */
  ggiClose(vis);
  ggiExit();
  return(0);
}

Compilation:
$gcc -Wall ggitest.c -o ggitest -lggi

Before launching it, we'll discuss a bit about the way GGI handle graphic modes. In this example we set a 8bpp, palettized graphic mode (that's the GT_8BIT in ggiSetGraphMode). So GGI will of course attempt to set a 8bpp mode. But GGI can run into the console, X, and several other targets. So what happen if we launch our program from a X server at 16 or 24 bpp depth? Our program will simply don't run. But don't worry, there's a tip! Set the GGI_DISPLAY variable to palemu:X, with the bash it is:

$export GGI_DISPLAY=palemu:X

It's a variable GGI will look for to find out a device it can render to. In our example we use a palette emulation (palemu), rendered in a X window (X). See the doc/targets.txt file that comes with your libggi tarball for more details.

Under the console, GGI may use the svgalib; so get root provileges if you are in that case.
If you aren't in the console and your X server is 16 or 24 bpp export set this variable and launch the program:

$./ggitest

The display will be the same as our first program using the svgalib.

Again, a line by line check:

  ggi_visual_t vis;
Svgalib uses graphic contexts, GGI use visuals. This variable is a visual, e.g. something we can draw to.

  ggi_color palette[5];
Definition of the color palette. A color in GGI is defined by the ggi_color structure:
typedef struct 
{ 
  uint16 r,g,b,a; 
}ggi_color;

More details come with the palette initialisation, below.

  ggiInit();
Well, it simply initialise GGI. Never forget it. It returns 0 for OK, otherwise an error code.

  vis=ggiOpen(NULL);
This line open a visual, which will be pointed by our vis variable. It returns a visual on success and NULL if an error occurs. This function can take several arguments; see the man page. We can for example open a visual which is located in the system memory.

  ggiSetGraphMode(vis,320,200,320,200,GT_8BIT);
Sets the graphic mode of our visual: Physical resolution of 320x200, virtual resolution of 320x200 and 8 bit depth.

  palette[0].r=0;
  palette[0].g=0;
  palette[0].b=0;
  palette[1].r=0xffff;
  palette[1].g=0;
  palette[1].b=0;
  palette[2].r=0;
  palette[2].g=0xffff;
  palette[2].b=0;
  palette[3].r=0;
  palette[3].g=0;
  palette[3].b=0xffff;
  palette[4].r=0xffff;
  palette[4].g=0xffff;
  palette[4].b=0xffff;
Sets the values of our color palette. Each value can go from 0 to 65535 (0xffff). There's a better way to define the palette; it's explained in the next program. I chosen this heavy but simple method to make it easier to understand.

  ggiSetPalette(vis,0,5,palette);
Set 5 colors of palette starting from the color 0 for the visual vis. See the man page for more details.

  ggiSetGCForeground(vis,0);
This funtion sets the foreground color for drawing. Here it will set the color 0, a total black.

  ggiFillscreen(vis);
Fill the screen with the foreground color.

  ggiSetGCForeground(vis,1);
  ggiDrawPixel(vis,20,20);
Change the foreground color (switch to the red) and draw a pixel at position (20,20).

  ggiSetGCForeground(vis,2);
  ggiDrawBox(vis,50,50,100,100);
Switch to the green color and draw a box which top-left corner is at position (50,50). Lenght and height are 100.

  ggiSetGCForeground(vis,3);
  ggiDrawLine(vis,30,20,300,180);
Switch to the blue color and draw a line from (30,20) to (300,180).

  ggiSetGCForeground(vis,4);
  ggiDrawBox(vis,100,80,100,100);
Switch to white and draw another box.

  ggiFlush(vis);
This function wait for all the primitives to be drawn on the visual.

  ggiGetc(vis);
Wait until a key is pressed, and return the key. It's prefered to getchar() because it take care of the visual (we can have several visuals and need a keypress in only one of them).

  ggiClose(vis);
Close our visual.

  ggiExit();
Exit the library.

Again, this little program is just to get started with GGI. GGI comes with a very good tutorial/documentation so read it and read the man pages. All the things you may not find clear here have certainly a better explanation in those docs. In all case, don't hesitate to read them.

6)Image manipulation basics.
Allright, you should have read the svgalib and GGI docs, so now that we have a support to work with we can start real coding. Svgalib and GGI offer some functions that are usefull for image manipulation:

Svgalib:
void gl_putbox(int x, int y, int w, int h, void *buf);
void gl_putboxpart(int x, int y, int w, int h  , int  bw, int bh, void *buf , int xo, int yo);
void gl_putboxmask(int x, int y, int w, int h, void *buf);

GGI:
int ggiPutBox(ggi_visual_t vis, int x, int y, int w, int h, void *buf);

The putbox functions simply put an image pointed by buf to position (x,y) with width w and height h.

Two "bonus" functions comes with svgalib: Putboxmask do the same excepted it doesn't draw the color #0. Putboxpart put a part of the image, of size bw and bh, starting at position (xo,yo).

For more convenience, and to practise a bit what we have seen in the first part, we'll create a file for our initialisations and quit functions, so we don't need to write them each time:

svgacommon.c:
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>

void init_svgalib(GraphicsContext * physicalscreen)
{
  if (vga_init()!=0) {printf("Error initialising svgalib!\n");_exit(1);}
  if (vga_setmode(G320x200x256)!=0) {printf("Can't set mode 320x200x256!\n");_exit(1);}
  if (gl_setcontextvga(G320x200x256)!=0) {printf("Error setting context!\n");_exit(1);}
  gl_getcontext(physicalscreen);
  if (gl_setcontextvgavirtual(G320x200x256)!=0) {printf("Error setting virtual context!\n");_exit(1);}
}

void exit_svgalib()
{
  vga_setmode(TEXT);
}

ggicommon.c:
#include <ggi/ggi.h>

void init_ggi(ggi_visual_t * vis)
{
  if (ggiInit()!=0) {printf("Error initialising GGI!\n");_exit(1);}
  if ((*vis=ggiOpen(NULL))==NULL) {printf("Error opening visual!\n"); ggiExit(); _exit(1);}
  if (ggiSetGraphMode(*vis,320,200,320,200,GT_8BIT)) {printf("Set the GGI_DISPLAY variable to the correct value!\n"); _exit(1);}
}

void exit_ggi(ggi_visual_t * vis)
{
  ggiClose(*vis);
  ggiExit();
}

Compile the two files:
$gcc -Wall -c svgacommon.c -o svgacommon.o
$gcc -Wall -c ggicommon.c -o ggicommon.o

Now we just need to call init_svga or init_ggi to initialise the screen mode and exit_svga or exit_ggi before exiting.

Let's draw a little pixmap on our screens:
Svgalib version:
svgasprite.c:
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>

void init_svgalib (GraphicsContext * physicalscreen);
void exit_svgalib();

char sprite[]={00,00,00,00,00,00,00,01,01,00,00,00,00,00,00,00,
	       00,00,00,00,00,00,01,02,02,01,00,00,00,00,00,00,
	       00,00,00,00,00,01,02,03,03,02,01,00,00,00,00,00,
	       00,00,00,00,01,02,03,04,04,03,02,01,00,00,00,00,
	       00,00,00,01,02,03,04,05,05,04,03,02,01,00,00,00,
	       00,00,01,02,03,04,05,06,06,05,04,03,02,01,00,00,
	       00,01,02,03,04,05,06,07,07,06,05,04,03,02,01,00,
	       01,02,03,04,05,06,07,00,00,07,06,05,04,03,02,01,
	       01,02,03,04,05,06,07,00,00,07,06,05,04,03,02,01,
	       00,01,02,03,04,05,06,07,07,06,05,04,03,02,01,00,
	       00,00,01,02,03,04,05,06,06,05,04,03,02,01,00,00,
	       00,00,00,01,02,03,04,05,05,04,03,02,01,00,00,00,
	       00,00,00,00,01,02,03,04,04,03,02,01,00,00,00,00,
	       00,00,00,00,00,01,02,03,03,02,01,00,00,00,00,00,
	       00,00,00,00,00,00,01,02,02,01,00,00,00,00,00,00,
	       00,00,00,00,00,00,00,01,01,00,00,00,00,00,00,00};

char palette[]={00,00,00,
                00,00,60,
		20,20,50,
		30,30,50,
		40,40,40,
		30,30,30,
		20,20,20,
		10,10,10};

int main()
{
  GraphicsContext physicalscreen;
  init_svgalib(&physicalscreen);

  gl_setpalettecolors(0,8,&palette);

  gl_clearscreen(0);
  gl_putbox(50,50,16,16,&sprite);
  vga_waitretrace();
  gl_copyscreen(&physicalscreen);
  getchar();

  exit_svgalib();
  return(0);
}

This program will draw the sprite we defined on the screen. Play with it: add some gl_putbox, change the background color, change the palette color, the sprite, etc...
You have seen we use another function to initialise the palette; this one is much more convenient because it allow us to set the palette up from a table which contains the RGB value for each color.

GGI version:
#include <stdio.h>
#include <ggi/ggi.h>

void init_ggi(ggi_visual_t * vis);
void exit_ggi(ggi_visual_t * vis);

char sprite[]={00,00,00,00,00,00,00,01,01,00,00,00,00,00,00,00,
	       00,00,00,00,00,00,01,02,02,01,00,00,00,00,00,00,
	       00,00,00,00,00,01,02,03,03,02,01,00,00,00,00,00,
	       00,00,00,00,01,02,03,04,04,03,02,01,00,00,00,00,
	       00,00,00,01,02,03,04,05,05,04,03,02,01,00,00,00,
	       00,00,01,02,03,04,05,06,06,05,04,03,02,01,00,00,
	       00,01,02,03,04,05,06,07,07,06,05,04,03,02,01,00,
	       01,02,03,04,05,06,07,00,00,07,06,05,04,03,02,01,
	       01,02,03,04,05,06,07,00,00,07,06,05,04,03,02,01,
	       00,01,02,03,04,05,06,07,07,06,05,04,03,02,01,00,
	       00,00,01,02,03,04,05,06,06,05,04,03,02,01,00,00,
	       00,00,00,01,02,03,04,05,05,04,03,02,01,00,00,00,
	       00,00,00,00,01,02,03,04,04,03,02,01,00,00,00,00,
	       00,00,00,00,00,01,02,03,03,02,01,00,00,00,00,00,
	       00,00,00,00,00,00,01,02,02,01,00,00,00,00,00,00,
	       00,00,00,00,00,00,00,01,01,00,00,00,00,00,00,00};

ggi_color palette[]={{0,0,0},
                     {0,0,60*1024},
		     {20*1024,20*1024,50*1024},
		     {30*1024,30*1024,50*1024},
                     {40*1024,40*1024,40*1024},
                     {30*1024,30*1024,30*1024},
                     {20*1024,20*1024,20*1024},
                     {10*1024,10*1024,10*1024}};

int main()
{
  ggi_visual_t vis;
  init_ggi(&vis);

  ggiSetPalette(vis,0,8,palette);
  ggiSetGCForeground(vis,0);
  ggiFillscreen(vis);
  ggiPutBox(vis,50,50,16,16,&sprite);
  ggiFlush(vis);
  ggiGetc(vis);

  exit_ggi(&vis);
  return(0);
}

Again, play with this program, make it segfault, etc.... :)
A comment on the palette: ggiSetPalette must be invoked with a pointer to a ggi_color structure. The ggi_color structure is defined like this:

typedef struct 
{ 
  uint16 r,g,b,a; 
}ggi_color;

As we only need the r, g and b values, brackets are used in the palette definition to mark we have finished with the structure, and want to go to the next one.
Also, svgalib color definition have a range of [0..63]. GGI color definition have a range of [0..65535]. So to keep the same colors we have to multiply each color value by (65536/64)=1024.

Right, now we know how to draw sprites we have defined, but we can't make a complete game by defining all the sprites we use like this! The best way is to load sprites from disk. Here it comes, now it's time to program our PCX loader!

7)The PCX structure.
Some theory on the PCX format: like most image format it has a header, (in this case it is 128 bytes long). Then comes the data, encoded with RLE compression. The last 768 bytes are the color palette. The beginning of the palette is marked by the 0ch byte.

                                PCX FILE
     -------------------------------------------------------------
     |                                               0ch         |
     -------------------------------------------------------------
     \_______/\_______________________________________/\_________/
     0 to 127            128 to filesize-769             768 last
      Header                 Encoded Image             Color Palette

Here is PCX header:
     
Byte Lenght		Description
			
0    1			Signature                                          
1    1			Version
2    1			Encoding (1=yes, 0=no)
3    1			Number of bits per pixel
4    2			Minimum X
6    2			Minimum Y
8    2			Maximum X (Lenght)
10   2			Maximum Y (Height)
12   2			Vertical resolution
14   2			Horizontal resolution
16   48			Palette (used for 16 color modes, we don't need it)
64   1			Reserved
65   1			Number of color layers
66   2			Number of bytes per line
68   2			Palette type (1=color)
70   58			Reserved for future versions

So the PCX header structure is:

typedef struct
{
  char signature;
  char version;
  char encoding;
  char bytes_per_pixel;
  unsigned short int xmin;
  unsigned short int ymin;
  unsigned short int xmax;
  unsigned short int ymax;
  unsigned short int vres;
  unsigned short int hres;
  char palette[48];
  char reserved;
  char color_layers;
  unsigned short int bytes_per_line;
  unsigned short int palette_type;
  char unused[58];
}PCX_Header;

After checking the file is in PCX format and getting the size from the header, we have to read the image. The RLE compression algorithm is quite simple:
-read one byte.
-if the two most significant bits are at 1, mask the byte by 63 to get the number of time you have to repeat the next byte. Else simply put the byte.
And so on until you have read the complete image.


Then, we just have to read the color palette and set it up.

This function read the pcx image from file to target, assuming the image is size bytes lenght:

void readpcximage(FILE * file,void * target,int size)
{     
  unsigned char buf;
  unsigned int counter;
  int i=0;
  while(i<=size)           /* Image not entirely read? */
    {
      fread(&buf,1,1,file);      /* Get one byte */
      if ((buf&192)==192)        /* Check the 2 most significant bits */
	{                        /* We have 11xxxxxx */
	  counter=(buf&63);      /* Number of times to repeat next byte */
	  fread(&buf,1,1,file);  /* Get next byte */
	  for(;counter>0;counter--)   /* and copy it counter times */
	    {
	      memcpy((target+i),&buf,1);
	      i++;                /* increase the number of bytes written */
	    }
	}
      else
	{                        /* Just copy the byte */
	  
	  memcpy((target+i),&buf,1);
	  i++;                   /* Increase the number of bytes written */
	}
    }
}

To read the palette, we simply read the 768 last bytes of the file. But their range is 0..255, so we must divide them by 4 (or shift the bits 2 times to the right) to have a 0..63 range usuable with svgalib. With GGI, we will have to multiply them by 255 (or shift the bits 8 times to the left) to get a 0..65535 range.

8)The PCX reader.
Now, the programs! They read the pcx file given as argument and display it, provided it is not too large for our screen resolution. We will also write a little Makefile for more convenience.

pcx.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
  char signature;
  char version;
  char encoding;
  char bytes_per_pixel;
  unsigned short int xmin;
  unsigned short int ymin;
  unsigned short int xmax;
  unsigned short int ymax;
  unsigned short int vres;
  unsigned short int hres;
  char palette[48];
  char reserved;
  char color_layers;
  unsigned short int bytes_per_line;
  unsigned short int palette_type;
  char unused[58];
}PCX_Header;

void readpcximage(FILE * file,void * target,int size)
{     
  unsigned char buf;
  unsigned int counter;
  int i=0;
  while(i<=size)           /* Image not entirely read? */
    {
      fread(&buf,1,1,file);      /* Get one byte */
      if ((buf&192)==192)        /* Check the 2 most significant bits */
	{                        /* We have 11xxxxxx */
	  counter=(buf&63);      /* Number of times to repeat next byte */
	  fread(&buf,1,1,file);  /* Get next byte */
	  for(;counter>0;counter--)   /* and copy it counter times */
	    {
	      memcpy((target+i),&buf,1);
	      i++;                /* increase the number of bytes written */
	    }
	}
      else
	{                        /* Just copy the byte */
	  
	  memcpy((target+i),&buf,1);
	  i++;                   /* Increase the number of bytes written */
	}
    }
}

void * readpcx(FILE * file, char * palette,
            unsigned short int * lenght, unsigned short int * height)
/* Returns NULL if failed, otherwise a pointer to the loaded image */
{
  PCX_Header header;
  void * target;
  fseek(file,0,SEEK_SET);
  fread(&header,sizeof(PCX_Header),1,file);   /* read the header */
  /* Check if this file is in pcx format */
  if((header.signature!=0x0a)||(header.version!=5)) return(NULL); 
  else
    {    /* it is! */
      *lenght=header.xmax+1-header.xmin;        /* Return height and lenght */
      *height=header.ymax+1-header.ymin;
      target=(void *)malloc((*lenght)*(*height)); /* Allocate the sprite buffer */
      readpcximage(file,target,(*lenght)*(*height)); /* Read the image */
      fseek(file,-768,SEEK_END);
      fread(palette,1,768,file);   /* Get the palette */
      return(target);                    /* PCX succesfully read! */
    }
}

svgadisplaypcx.c:
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>

void * readpcx(FILE * file, char * palette,
            unsigned short int * lenght, unsigned short int * height);

void init_svgalib (GraphicsContext * physicalscreen);
void exit_svgalib();

GraphicsContext physicalscreen;

int main(int argc, char ** argv)
{
  FILE * file;
  void * image;
  int i;
  unsigned short int lenght, height;
  unsigned char palette[768];
  if (argc!=2) {printf("Usage: svgadisplaypcx filename.pcx.\n"); return(1);}
  if ((file=fopen(argv[1],"r"))==NULL) {printf("Can't open file!\n");return(1);}
  if ((image=readpcx(file,palette,&lenght,&height))==NULL)
    {printf("Error loading file!\n"); return(1);}
  fclose(file);
  printf("Image is %dx%d sized.\n",lenght,height);
  if((lenght>320)||(height>200)) {printf("Image is too big!\n");return(1);}
  for (i=0;i<768;i++) palette[i]=palette[i]>>2;
  init_svgalib(&physicalscreen);
  gl_setpalette(palette);
  gl_clearscreen(0);
  gl_putbox(0,0,lenght,height,image);
  vga_waitretrace();
  gl_copyscreen(&physicalscreen);
  getchar();
  exit_svgalib();
  return(0);
}

ggidisplaypcx.c:
#include <stdio.h>
#include <ggi/ggi.h>

void * readpcx(FILE * file, char * palette,
            unsigned short int * lenght, unsigned short int * height);

void init_ggi (ggi_visual_t * visual);
void exit_ggi (ggi_visual_t * visual);

ggi_visual_t vis;

int main(int argc, char ** argv)
{
  FILE * file;
  void * image;
  int i;
  unsigned short int lenght, height;
  unsigned char palette[768];
  ggi_color ggi_palette[768];
  if (argc!=2) {printf("Usage: svgadisplaypcx filename.pcx.\n"); return(1);}
  if ((file=fopen(argv[1],"r"))==NULL) {printf("Can't open file!\n");return(1);}
  if ((image=readpcx(file,palette,&lenght,&height))==NULL)
    {printf("Error loading file!\n"); return(1);}
  fclose(file);
  printf("Image is %dx%d sized.\n",lenght,height);
  if((lenght>320)||(height>200)) {printf("Image is too big!\n");return(1);}

  for (i=0;i<256;i++)
    {
      ggi_palette[i].r=palette[i*3]<<8;
      ggi_palette[i].g=palette[(i*3)+1]<<8;
      ggi_palette[i].b=palette[(i*3)+2]<<8;
    }
  init_ggi(&vis);
  ggiSetPalette(vis,0,256,ggi_palette);
  ggiSetGCForeground(vis,0);
  ggiFillscreen(vis);
  ggiPutBox(vis,0,0,lenght,height,image);
  ggiFlush(vis);
  ggiGetc(vis);
  exit_ggi(&vis);
  return(0);
}

Makefile:
CC=gcc
CFLAGS=-Wall
PROGSVGA=svgadisplaypcx
PROGGGI=ggidisplaypcx
LIBSVGA=-lvgagl -lvga
LIBGGI=-lggi
OBJECTSSVGA=svgadisplaypcx.o svgacommon.o pcx.o
OBJECTSGGI=ggidisplaypcx.o ggicommon.o pcx.o

all:svga ggi

svga:$(OBJECTSSVGA)
	$(CC) $(OBJECTSSVGA) $(LIBSVGA) -o $(PROGSVGA)

ggi:$(OBJECTSGGI)
	$(CC) $(OBJECTSGGI) $(LIBGGI) -o $(PROGGGI)

clean:
	rm -rf $(OBJECTSSVGA) $(OBJECTSGGI) $(PROGSVGA) $(PROGGGI)


Compilation is done by:
$make svga
To built the svgalib executable,
$make ggi
To built the GGI executable,
$make
To built both svgalib and GGI executables.

As you can see, most of the code is to test if there's no error during the loading process. In interactive programs it's very important, because the user will ALWAYS do his possible to make your program crash. In games, as data files are usually written by the programmer and accessed only by the program, we can avoid most of those tests.

Some explanations on the programs:

  void * image;
This is the core definition of an image, or a sprite. It's void typed because sprites can have one or several bytes per pixel, depending of the graphic depth. As it is a pointer, it must be allocated before we use it:

  target=(void *)malloc((*lenght)*(*height));
Now it is usuable. We do the memory allocation when we know the size of the sprite, in order to don't waste memory. After the allocation we can safely load the sprite and display it.

You have also certainly noticed that the pcx loading code is common to the two programs. Making portable code is important; As our game may use both svgalib and GGI, or even run on several operating systems we'll try to make most of the code common to the two libraries, making a difference only for the display code.

Right, that was quite a long part, wasn't it? Now you can make A LOT of extensions from these programs; load several sprites, make them move, etc... We'll do that next time; until then you can try to make it (hint: use as many void pointers as you need sprites). Don't hesitate to mail comments, corrections, questions to gnurou@linuxfan.com. You can also suggest a theme for the next part, which will be mainly about (advanced?) sprite manipulation. Expect it in middle July.

See you on next part!