home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C!T ROM 2
/
ctrom_ii_b.zip
/
ctrom_ii_b
/
PROGRAM
/
C
/
ADDPCX
/
DOC.TXT
< prev
next >
Wrap
Text File
|
1988-04-18
|
26KB
|
547 lines
@CHAPTER TITLE = Working with PCX Files
@FIRSTPAR = The Picture file format used by ZSoft for their PC Paintbrush
paint program has become one of the more popular formats around. I
personally took an interest in it originally because I needed to get
diagrams into Ventura Publisher; which not only accepts .pcx files,
but will also expand and contract them sensibly.
The format turned out to be easy to work with. The files can be loaded
and written quickly, using a set of simple functions. Screen update
is fast, and the screen can easily be panned around a large picture.
Drawing operations for multi-color formats aren't as fast, but that's
due to the data structure that I've chosen.
I'd like to start by describing PCX pictures in general terms. The
Header and Color palette are complicated enough to deserve some more
detail. I'll finish with a description of the modules that I've written
to generate, write, read and display PCX pictures. You'll probably
want to make many improvements to them, but I'm confidant that they'll
get a PCX related project off the ground for you.
@MAJOR HEADING = The Basic Structure
A PCX file consists of a 128 byte header, followed by one or more
compressed picture planes. In memory, each plane is basically a bit
map, with each bit usually corresponding to a pixel on the screen. The
number of bits per pixel, pixels per byte, bytes per line, lines per
plane and planes per picture are all stored in the header.
On the EGA board, there are four planes (see figure 1.) Bit (X,Y)
in each plane corresponds to pixel (X,Y) on the screen. The binary
value of the four (X,Y) bits in each plane is used to index into the
color palette. On the CGA & HERC boards, only 1 plane is used. For
the 720x348 HERC and 640x200 CGA modes, each bit in the plane corresponds
to a single pixel on the screen. Using the 320x200 by 4 color CGA
mode, there is again only one plane, but each pixel is made up of
two adjacent bits.
It's possible to make a 1000x1000 by 2 color drawing which can be
displayed on a CGA board 640x200 dots at a time, then show it on a
Hercules board 720x348 dots at a time, but a picture isn't always
so portable. A 320x200 CGA picture has to be translated before it
can be shown in another mode.
As you can imagine, drawing the picture on the screen is easy <197>
all that's needed is a block move of each line of the picture to the
VRAM on the board. The trade off is that there has to be some interpretation
of the header information, and the pictures don't look quite the same
from one board to another due to changes in dot width and height.
@MAJOR HEADING = The Header
The structure for the header looks like this:
@LISTING = typedef struct {<R>
unsigned char red, green, blue;<R>
} TRIPLET;<R>
<R>
typedef struct {<R>
char maker,<R>
version,<R>
code,<R>
bpp;<R>
int x1, y1, x2, y2,<R>
hres, vres;<R>
TRIPLET triple[16];<R>
char vmode, <R>
nplanes;<R>
int bpl;<R>
char __unused[128-68];<R>
} PCXHDR;
The fields<B> maker<D>, <B>version<D>, <B>code<D> and <B>vmode<D>
all currently have fixed values <197> 10, 5, 1, & 0. They represent
the code for the program that generated the picture, its version number
and the type of compression that was applied to the picture planes;
<B>vmode<D> is currently ignored; and <B>__unused<D> is reserved for
future expansion.
<B>hres <D>and <B>vres<D> represent the number of dots, horizontal
and vertical, on the video board to be used. <B>nplanes <D>is the
number of picture planes used,<B> bpp<D> is the number of bits per
pixel and <B>bpl<D> is the number of bytes that are used to store
a horizontal line of the picture. (<B>x1,y1<D>)-(<B>x2,y2<D>) refer
to the upper left and lower right corners of the picture. For a standard
640x350x16 EGA picture, we would get the following: <B>hres<D>=640,
<B>vres<D>=350, <B>x1<D>=0, <B>y1<D>=0, <B>x2<D>=639, <B>y2<D>=349,
<B>nplanes<D>=4, <B>bpp<D>=1 and <B>bpl<D>=80.
It's interesting to note that PC PaintBrush seems to take these numbers
very seriously. If a picture is made after PaintBrush is set up to
use a HERC board, it won't even be recognized as a picture file if
PaintBrush is then set up to use an EGA board. That's a bit picky
considering that a HERC picture is black and white, and an EGA board
supports those colors.
So, if you plan to use the pictures that you generate on some existing
program, be careful to model your picture after one of the various
video board modes. To make a picture with a different size, only
(<B>x2, y2<D>) and <B>bpl<D> would have to change; and the easiest
way to add colors would be to add planes. Although the software that
I've written only supports a few of the standard configurations, it'll
probably be easy to modify.
@MAJOR HEADING = The Color Palette
The palette information is stored in the array of 16 triples. It's
the only part of the PCX format that's really tricky <197> being totally
board dependent. Perhaps Shannon at ZSoft can send Dave a letter explaining
it better. Here's the way I understand it:
The IBM EGA adapter has 16 single byte color registers, each of which
holds a two bit intensity value of from 0 to 3 for each color gun
(Red, Green, and Blue.) The intensity values are stored as pairs of
bits, like this: "xxRGBrgb". The first two bits, 7 & 6, are ignored. The
next three bits are the high bits of the intensity values for the
Red, Green & Blue guns. The last three bits are the low bits of the
same intensity values. This allows each of the 16 registers to contain
one of 64 possible color combinations.
The PCX header has a 16 element palette table, one element per color
register. Each element is a three byte structure (TRIPLET) which
holds an intensity value for each color gun. To set the color for
any given palette register, the Red, Green & Blue intensity bytes
are set with the desired intensity for that color multiplied by 85. (I
have no idea why we multiply by 85.) To set the EGA palette register,
we take the intensity value, divide by 85, twiddle the bits, then
take the easy route to the palette register by calling the BIOS Set
Palette function.
The CGA board has a Color Select Register at I/O location 0x3d9. The
bit configuration for this register is "xxCAIRGB". The first two
bits are ignored. The C bit selects one of two pre-defined palettes,
called Color Sets by IBM. Set 1 is Background, Green, Red & Brown. Set
2 is Background, Cyan, Magenta & White. The Background color is one
of 16 colors formed using the IRGB bits. I is an intensity bit, R,
G & B are enables for the Red, Green and Blue color guns, respectively. If
the A bit is set, it "Selects (an) Alternate, Intensified set of colors
in Grapics Mode" (see ref. 1.) To me, this translates into 4 color
sets and one of 16 background colors.
Storage of the CGA palette settings in the PCX header is simple: The
first byte of the first element in the palette table contains a background
color, multiplied by 16. The first byte of the second element in
the palette table apparently contains a three bit value, multiplied
by 32. (I haven't been able to figure out what to do with the third
bit.) At any rate, there are 8 such settings in the PCX headers palette
table, but it seems to me that only one of them can be in effect at
a time.
There are probably many more palette formats for other boards, but
I don't know of any documentation for them. In the code that accompanies
this article, the pcx_set_palette() function has a load_palette flag
which must be set in order to get the routine to tinker with the palette
registers. I didn't bother much with the palette for a number of
reasons; the first one being that the palette is automatically set
each time a graphics mode is selected; another being that Ventura
Publisher ignores it.
@MAJOR HEADING = Compressing a Plane
When in a file, each plane in the picture is compressed. The compression
is very simple; designed simply to take advantage of the fact that
a byte value will often repeat.
The read byte algorithm works like this: Read a byte. If the two
high order bits are not set ((byte & 0xc0) != 0xc0), then return the
byte. If the two high order bits are set ((byte & 0xc0) == 0xc0),
then the remaining bits represent a repeat count (count = byte & 0x3f)
and the next byte is the value that repeats. As such, the repeat
count can never be greater than 0x3f, or 63. The write byte algorithm
is obviously the opposite operation.
@MAJOR HEADING = The Software
Originally, what I wanted to do was copy schematics into Ventura. I
sat in front of the system for many sleepless nights decoding DXF
files, only to find in the end that the GEM files Ventura creates
from the DXF files don't properly scale text.
To make a long story short, I wrote most of a DXF to PCX converter,
then (balking at the prospect of having to write a font editor, create
fonts, and write the code to scale them,) scrapped the idea <197>
it was less time consuming to just chop the schematics into small
blocks. In the mean time, I'd written a (simple) board independent
graphics package, several test routines, and a slide show program. As
you've just guessed, text is not implemented.
Everything was compiled using Manx Aztec 'C 86, V. 4.10a. With the
exception of the code that handles flicker on the CGA board, it should
compile without complaint on most systems. Note, however, that it'll
only work in the large data model; not only because of the huge amount
of RAM that it uses, but because of the way I accessed the VRAM.
There isn't enough space to list all the code, so I'll document the
software here, and you can download the files from one of the following
sources: The file "$$$PCX.ARC" will be on the MicroC BBS long before
you see this; and I'll put it in one of the IBM forums on Compu-Serve,
in thanks to the many people who put together the EGA.ARC file (see
ref. 2.) Finally, there'll be a copy in the LISTINGS/IBM.ARC section
of BIX.
@MAJOR HEADING = The Functions
Basically, the functions can be divided into three groups. The lowest
level is the board level driver modules. There's one for the HERC,
EGA and CGA boards. To make it easy to use, the PCX module is set
up as just another video board. Each module has the same collection
of functions, each with the same name, although the function name
is prefixed with the board name to keep them distinct.
The next level is what I call the VGR modules, for Video GRaphics. It's
a simple interface between the driver modules and the next higher
level. This level is based on a bunch of macros to allow access to
the driver level while still being able to switch drivers at run-time. Using
these macros, object draw functions can be written which are independent
of the low level drivers. So far, I've put in Bressenham's line drawing
algorithm and an inefficient but functional irregular polygon fill.
There's a circle drawing algorithm in the May '83 issue of Doctor
Dobbs Journal which is likely to go in next.
The highest level is the Picture level. The functions at this level
operate on the following picture structure:
@LISTING = typedef struct { <R>
PCXHDR hdr;<R>
char **rows[4];<R>
} PCXPIC;
(I feel I should warn you that the following few paragraphs are exceptionally
boring. If you're not interested in how I chose to handle the pointer
and bit twiddling details, skip to the next heading...)
The PCXHDR is obvious enough, but the rows array deserves some explanation.
Figure 2 shows how I visualize it when drawing a 640x350x16 EGA-type
picture. A plane is a pointer to an array of rows, each of which
is a pointer to an array of characters. That array of characters
contains the bits which constitute a horizontal line of pixels.
This approach is relatively inefficient in that it forces several
32 bit pointer operations, but it's easy to work with. As you can
see, there's currently a hardwired limit of 4 planes, and the arrays
are dynamically allocated.
For all but the 320x200x4 CGA modes, we can read the bit at position
(<B>x,y<D>) on a given plane using the following expression:
@LISTING = !!(pcx_cpic<197>>>rows[plane][y][x>>>>3] & (0x80 >>>> (x & 7)));
The global variable <B>pcx_cpic<D> is defined in the PCX module as
a pointer to a PCXPIC picture (cpic == current picture) It's used
to tell the pixel routines what picture to work on. For the HERC
board, or the 640x200x2 CGA mode, plane is always zero; the 640x350x16
EGA mode requires that the operation be repeated once for each plane. Since
<B>rows[plane] <D>is a pointer to an array of rows, <B>rows[plane][y]
<D>is a pointer to the Yth row. All that's needed now is the byte
to look at in the row, and the bit in that byte. The low order three
bits of <B>x<D> (<B>x<D> & 7) select the bit, and the remaining bits
of <B>x<D> (<B>x<D> >>>> 3) become the byte offset for the row. The
above expression would return 1 if the bit at (<B>x,y<D>) is set,
or zero if it's not.
The 320x200x4 CGA mode is slightly more complicated since two adjacent
bits are used to select a color. The expression to return the color
of the pixel at location (<B>x,y<D>) in that mode would be:
@LISTING = i = (x & 0x03) <<<< 1;<R>
color = (pcx<197>>>rows[0][y][x>>>>2] & (0xc0 >>>> i)) >>>> (6<197>i);
Since each byte represents 4 pixels, <B>x<D>>>>>2 is used as the byte
offset for the row, and <B>x<D> & 0x03 is the number of the pixel
in the byte. Multiplying that number by 2 gives <B>i<D>, the number
of bits from bit 7 that we skip over to get to the first of the two
bits that select the color of the pixel. 0xc0 >>>> <B>i<D> makes a mask
for those bits. The big difference between this expression and the
previous one is that we need a two bit result, so we can't use the
!! operator to clean up after the bit-wise AND. Shifting to the right
6<197><B>i<D> bits does the job.
The only difference between the two expressions above and the ones
actually used in the EGA and CGA modules is that there aren't any
planes. The EGA board uses a plane register to enable each plane
and all four planes use the same memory locations. The HERC board
has two pages, which can be thought of as unrelated planes. I visualized
each horizontal row of pixels as being an array of columns, so the
arrays are called columns instead of rows. The array of pointers
to each row is dynamically allocated. The pointers are initialized
with sequential row addresses for the EGA board, and scattered addresses
for the HERC and CGA boards.
Although the pointer operations and bit twiddling never get more complicated
than that, some of the expressions get a bit dense, and aren't much
fun to read. Fortunately, all the low level operations are already
implemented; you can just throw them into your library and use them.
With that said, let's get down to function names and parameter lists...
@MAJOR HEADING = Using the Functions
Note that the declarations here don't match the ones in the modules.
Many functions that don't return anything are declared as returning
int. Most parameters to most of the functions are not range-checked.
@PROTO = void allocf(char *p);<R>
void map_not(int *map,int len);
These two come out of my library. allocf() checks the pointer p to
see if it's NULL. If so, it doesn't do anything. If not, it calls
free(). If free() returns an error code, an error message is produced,
and the program quits. map_not() inverts len ints in the bit-map
pointed at by map.
@PROTO = void cga_movmem(char far *s,char far *d,int n);<R>
void cga_peekb(char far *p);<R>
void cga_pokeb(char far *p,char b);
These functions are the same as movmem(), peekb() and pokeb(); except
that they wait for a horizontal retrace to start on the CGA board
before accessing it, to avoid flicker. The cga_movmem() function is
a straight block move, so you'll have to check for overlapping blocks
if you plan to move a source to a destination which are both in the
CGA VRAM.
@PROTO = void ega_select_plane(int plane);<R>
int pcx_select_plane(int plane);
The EGA VRAM holds 4 planes, each of them mapped into the same memory
space. ega_select_plane() is used to enable one or more planes. If
it's passed a negative number, the absolute value of that number is
used as an enable mask, ie: if <B>plane<D> == <197>0x0f, all 4 planes
are simultaneously enabled. This is handy when clearing the screen
since a single setmem() call can clear all four planes! If <B>plane<D>
is not negative, then it must be from 0 to 3, and only that plane
is enabled. pcx_select_plane() selects a plane for use by the VGR_ROW()
macro when drawing on a PCX picture.
@PROTO = void ega_set_palette(char reg,char red,char green,char blue);
Sets one of the 16 EGA palette registers. <B>reg<D> can have a value
of from 0 to 15. <B>red, green<D> and <B>blue<D> are the desired
intensities for each color gun, for that <B>reg<D>ister. They must
each have a value of from 0 to 3.
@PROTO = int herc_set_page(int page);
The HERC driver will default to page 0, but either of the two pages
can be selected using this function.
@PROTO = int *pcx_init(void);<R>
cga_init(void);<R>
ega_init(void);<R>
herc_init(void);<R>
<R>
int VGR_HRES,<R>
VGR_VRES,<R>
VGR_NCOLORS,<R>
VGR_NBPL; /* #bytes per line */
The _init() routines initialize all static local variables in a module,
allocate any required space (if it hasn't been allocated by a previous
_init() call,) and set up the VGR module to use the module that was
INITed. The VGR module, among other things, holds the four ints shown
above. The HERC and EGA modules always set these values, as they're
constants. The PCX module will set them only if pcx_cpic is non-null,
therefore pointing to a valid PCXPIC picture. The CGA module sets
them after a VGR_MODE() call. The _init() functions each return OK
or ERROR, with ERROR usually meaning that an out of memory condition
occurred.
@PROTO = int VGR_MODE(int m);
Once the VGR module has been set up, using the appropriate *_init()
function, the "mode" must be set. Since the _init() call set up the
array of pointers to functions in the VGR module, it's possible to
use the #defined macros in vgr.h. VGR_MODE() is one of those macros.
<B>m<D> must be one of the following:
@LISTING = MODE_TEXT0 <197> text, 80x25<R>
MODE_APA0 <197> APA, 640x350x16<R>
MODE_APA1 <197> APA, 720x348x2<R>
MODE_APA2 <197> APA, 640x200x2<R>
MODE_APA3 <197> APA, 320x200x4
When drawing on a PCX picture, the mode is only used to tell the PCX
module if it's producing a one or two bit per pixel picture; so available
PCX modes are effectively MODE_APA0 or MODE_APA3. Available EGA modes
are MODE_TEXT0 or MODE_APA0; for the CGA it's MODE_TEXT0, MODE_APA2
or MODE_APA3; and for HERC it's MODE_TEXT0 or MODE_APA1. ERROR or
OK is returned, with ERROR indicating an invalid mode number.
@PROTO = void VGR_CLEAR()<R>
void VGR_SET(int x,int y,int c)<R>
void VGR_CLR(int x,int y)<R>
void VGR_XOR(int x,int y,int c)<R>
int VGR_GET(int x,int y)<R>
void VGR_ROW(int r,char far *p,int n)<R>
void VGR_MOVE(char far *s,char far *d,int n)<R>
void VGR_PEEKB(char far *p)<R>
void VGR_POKEB(char far *p,int b)
These VGR_ macros allow access to the board independent functions.
_CLEAR clears the screen. _SET sets the color of the pixel at (<B>x,y<D>)
to <B>c<D>. Note that if <B>c<D> has a value of zero, it has the effect
of "clearing" the pixel. It's a good idea to not confuse the two operations
since the HERC module ignores the value of <B>c<D>. _CLR sets the
color of the pixel to zero. _XOR does a bit-wise exclusive or of the
color of the pixel with <B>c<D>. Again, the HERC module ignores <B>c<D>.
_GET returns the color of the pixel. _ROW moves <B>n<D> bytes from
location <B>p<D> to the VRAM row <B>r<D>. _MOVE, _PEEKB & _POKEB either
call movmem(), peekb() & pokeb(), or cga_movmem(), cga_peekb() & cga_pokeb();
depending on the driver module in use.
@PROTO = void vgr_fill(int x,int y,int color);<R>
void vgr_line(int x1,int y1,int x2,int y2,int color);<R>
void vgr_rectangle(int x1,int y1,int x2,int y2,int color);<R>
void vgr_point(int x2,int y2,int color);
vgr_line(), _rectangle() and _fill() do what they sound like. vgr_point()
does one of two things. If <B>color<D> is -1, the values (<B>x2,y2<D>)
are copied to the local static values (<B>x1,y1<D>). If <B>color<D>
is not -1, vgr_point() calls vgr_line(), then copies (<B>x2,y2<D>)
to (<B>x1,y1<D>). This makes it possible to draw lines between a
series of points.
@PROTO = int vgr_get_board(void);<R>
int vgr_mode(char mode);
vgr_get_board() attempts to figure out what kind of video graphics
board is in use. The return value is one of TYPE_UNKNOWN, TYPE_EGA,
TYPE_CGA, TYPE_MDA or TYPE_HERC. These values are defined in <B>vgr.h<D>.
vgr_mode() calls the BIOS set graphic mode function; INT 0x10, AH==0.
See ref. 2, page A-46 for more info on that BIOS call.
@PROTO = PCXPIC *pcx_init_pic(int hres,int vres,int nplanes);<R>
void pcx_free_pic(PCXPIC *pic);<R>
void pcx_invert_pic(PCXPIC *pic);
pcx_init_pic() dynamically allocates space for a PCXPIC structure,
and returns a pointer to it. <B>hres<D> & <B>vres<D> are the number
of dots horizontally and vertically; <B>nplanes<D> is the number of
planes in the picture. All arrays are dynamically allocated, and a
pointer to the result is returned. If an out of memory condition occurs,
any allocated arrays are de-allocated, and NULL is returned. pcx_free_pic()
is used to free up all the space allocated for a picture. pcx_invert_pic()
inverts all the bits in each row of the picture.
@PROTO = int pcx_read_pic(PCXPIC *pic,FILE *fp);<R>
int pcx_getc(int *c,int *n,FILE *fp,int maxn);<R>
int pcx_write_pic(PCXPIC *pic,FILE *fp);<R>
int pcx_xputc(int c,FILE *fp);<R>
int pcx_putc(int c,int n,FILE *fp);
These functions operate on a file which has been opened by one of
the STDIO buffered file open functions; <B>fp<D> is the file pointer
returned by the open call. <B>pic<D> is a pointer to a PCXPIC picture
structure. All these functions return OK or ERROR on failure.
pcx_read_pic() loads a picture from the file. The pic structure can
either be statically allocated, or dynamically allocated using the
pcx_init_pic(0,0,0) call. pcx_read_pic() automatically allocates
whatever arrays it needs to get the picture in. pcx_write_pic() writes
the picture to the file.
pcx_getc() reads a character or pair of characters from the file. The
character is placed in the integer pointed at by <B>c<D>, and a repeat
count of one or more is placed in the integer pointed at by <B>n<D>.
<B>maxn<D> is the maximum repeat count that the calling function wants
to receive.
pcx_write_pic() calls pcx_xputc(), which counts the number of times
it receives a given byte value, then calls pcx_putc() with that number.
If pcx_xputc() is called with <B>c<D> == -1, the buffered byte value
and count are passed immediately (flushed) to pcx_putc(). pcx_putc()
writes the byte <B>c<D> to the file, using a repeat count of <B>n<D>.
<B>n<D> may have a value of up to 32767; pcx_putc() will recursively
call itself with <B>n<D><<=63 until the correct total has been written
to the file.
@PROTO = void pcx_showpic(PCXPIC *pic,int hoffs,int voffs,int load_palette_flg);
This function uses VGR_ROW to copy rows from the <B>pic<D> picture
to the video board driver module in use. <B>hoffs<D> is an offset
from the start of each row in <B>pic<D>. <B>voffs<D> is an offset
which is applied to the row number. These two offsets allow the screen
to be panned around a large picture. The <B>load_palette_flg<D>, when
set, allows pcx_showpic() to set the hardware palette registers from
the PCXPIC palette table. Consider the palette functions to be unreliable.
@MAJOR HEADING = The Test Routine
As I'm writing this, PCX.EXE is bug free and running. It's essentially
a test routine which excercises the various modules. If executed without
command line parameters, it tries to figure out what kind of video
board is available. If a CGA/HERC/EGA board is found, it creates a
simple test picture (see figure 3,) displays it, then returns to DOS.
If it's invoked with a board name, CGA (640x200x2), EGA, HERC or CGA2
(320x200x4); it creates a picture for that board, and tries to display
it on that adapter.
If it's invoked with a board name followed by a file name, the picture
is saved, using the specified file name, after it's been displayed.
PCXSHOW.EXE, the slide show program, is in need of some upgrading
<197> it was written before the latest improvements to the PCX/VGR
modules. It'll be ready before you see this, and will be included
with the other files.
@MAJOR HEADING = Bibliography
@BIBN = 1
@BIB = The IBM Personal Computer XT Technical Reference Manual, April
1983 revision. Part Number 1502237.
@BIBN = 2
@BIB = The file EGA.ARC, which was found somewhere on Compu-Serve,
probably in one of the IBM forums.
@BIBN = 3
@BIB = ZSoft Technical Reference Manual. ZSoft Corporation, 1950
Spectrum Circle, Suite A-495, Marietta, GA 30067. Tel. (404) 428-0008.
Many thanks to Shannon.
@BIBN = 4
@BIB = A Hercules Primer, by Larry Fogg, MicroCornucopia, Jan-Feb
1988, page 26.
@BIBN = 5
@BIB = Tidbits, by Gary Entsminger, MicroCornucopia, Jan-Feb '88,
page 84.
@BIBN = 6
@BIB = Language Connections, by Gary Entsminger, Turbo Technix, Jan-Feb
'88, page 136.
@BIBN = 7
@BIB = MicroEMACS by Dan Laurence & others. Public domain text editor,
with 'C sources. Available on BIX in LISTINGS/IBM.ARC.