home *** CD-ROM | disk | FTP | other *** search
- MCGA Graphics Tutorial
- Lesson #4
- by Jim Cook
-
- Now for the fun stuff.........
- Whenever you design a graphics package you have to decide what format to
- store your images in. There are too many to count on both hands, and
- for the topic of this lesson I have to come up with one that is easy to
- handle and can be represented quickly. Here's what I've come up with...
- NO format. We are going to basically emulate the PutImage and GetImage
- found in TP's BGI. I chose this because it's easiest to understand and
- it can be put on the screen very quickly. As a matter of fact we can
- put a complete screen image up 32 times per second on my 386/33mhz.
- Most of the games we write will rely on a much smaller animation scale,
- but may involve up to 100 animations at a time.
-
- OK, you may ask, how do I save an image to disk in an uncompressed
- format? Good question, most paint programs do not directly support this
- format, but most do support PCX files. We will write a program that
- puts a .PCX (or it's smaller cousin .PCC) graphic on the screen. From
- here we will cut the chunk of the picture we want and save it to the
- disk. In order to accomplish all of this, we need to understand PCX
- files. Fasten your seatbelts!
-
- PCXHeader = record
- Signature : Byte; { $0A }
- Version : Byte; { varies }
- Encoding : Byte; { $01 }
- BitsPerPixel : Byte; { $08 for 1 byte per pixel }
- XMin,YMin, { Upper-left corner (0,0) }
- XMax,YMax : Integer; { Lower-right corner }
- HRes,VRes : Integer; { resolution 320 x 200 }
- Palette : Array [0..47] of byte; { colors }
- Reserved : Byte;
- Planes : Byte;
- BytesPerLine : Integer;
- PaletteType : Integer;
- Filler : Array [0..57] of byte;
- end;
-
- This is the first 128 bytes of any PCX (or PCC) file. The Signature is
- always $0A and the Encoding is always $01, at least for now. The rest
- of the header is self-explanatory. Well, there is one catch, when this
- format was developed, the geniuses at ZSoft didn't plan for pictures
- with 256 colors or more. There are 3 bytes per color in MCGA mode so
- the measely 48 bytes set aside in the header only allows for 16 colors.
- ZSoft ended up putting the 768 bytes of palette data at the end of the
- file and the Palette area in the header is available real estate.
-
- Immediately following the header is the body of the PCX file. The body
- is compressed by a technique called run-length compression. For many of
- you, this is the first time you have looked at compression so I'll take
- this slow. It is not important to understand this for the purpose of
- our graphic library, but it is vital if you work with other formats.
- Most use some type of run-length compression. TIFF and GIF use LZW
- compression, by the way.
-
- This is the flowchart (ugh) for unpacking one row of a PCX file:
-
- --------> Read in a byte X
- / |
- / Are the two high bits set?
- / [ 1 1 ? ? ? ? ? ? ] <- binary representation of X
- / / Yes \ No
- | X := X and $3F Write X to screen
- | | |
- ^ Get next byte Y |
- | | |
- | Write byte Y to the /
- | screen X times /
- | \ /
- \ Done with row?
- \ | No | Yes
- \ / |
- -----<------ | Exit
-
- That's how to unpack 1 row of a PCX, to unpack the whole picture:
-
- For I := 1 to NumRows do UnpackRow;
-
- Along with some housekeeping to keep track of advancing down the screen
- memory. I know you want the code so here goes:
-
- Unit Lib04;
-
- interface
-
- type
- PCXHeaderPtr= ^PCXHeader;
- PCXHeader = record
- Signature : Char;
- Version : Char;
- Encoding : Char;
- BitsPerPixel : Char;
- XMin,YMin,
- XMax,YMax : Integer;
- HRes,VRes : Integer;
- Palette : Array [0..47] of byte;
- Reserved : Char;
- Planes : Char;
- BytesPerLine : Integer;
- PaletteType : Integer;
- Filler : Array [0..57] of byte;
- end;
-
- Procedure DisplayPCXPas (X,Y:Integer;Buf:Pointer);
- Procedure DisplayPCX (X,Y:Integer;Buf:Pointer);
-
- implementation
-
- uses
- Dos;
-
- Procedure ExtractLine (BytesWide:Integer;Var Source,Dest:Pointer);
- var
- DestIdx,
- SourceIdx : Integer;
- InCode,
- RunCount : Byte;
- begin
- DestIdx := 0;
- SourceIdx := 0;
-
- While DestIdx < BytesWide do begin
- InCode := Mem [Seg(Source^):Ofs(Source^)+SourceIdx];
- Inc (SourceIdx);
-
- If (InCode and $C0) = $C0 then begin
- RunCount := InCode and $3F;
- InCode := Mem [Seg(Source^):Ofs(Source^)+SourceIdx];
- Inc (SourceIdx);
- FillChar (Mem[Seg(Dest^):Ofs(Dest^)+DestIdx],RunCount,InCode);
- Inc (DestIdx,RunCount);
- end
- Else begin
- Mem [Seg(Dest^):Ofs(Dest^)+DestIdx] := InCode;
- Inc (DestIdx);
- end;
- end;
- If Odd (BytesWide) then Source := Ptr(Seg(Source^),Ofs(Source^)+SourceIdx+2)
- else Source := Ptr(Seg(Source^),Ofs(Source^)+SourceIdx);
- Dest := Ptr(Seg(Dest^),Ofs(Dest^)+DestIdx);
- end;
-
- Procedure ExtractLineASM (BytesWide:Integer;Var Source,Dest:Pointer);
- var
- DestSeg,
- DestOfs,
- SourceSeg,
- SourceOfs : Word;
- begin
- SourceSeg := Seg (Source^);
- SourceOfs := Ofs (Source^);
- DestSeg := Seg (Dest^);
- DestOfs := Ofs (Dest^);
-
- asm
- push ds
- push si
-
- mov ax,DestSeg
- mov es,ax
- mov di,DestOfs { es:di -> destination pointer }
- mov ax,SourceSeg
- mov ds,ax
- mov si,SourceOfs { ds:si -> source buffer }
-
- mov bx,di
- add bx,BytesWide { bx holds position to stop for this row }
- xor cx,cx
-
- @@GetNextByte:
- cmp bx,di { are we done with the line }
- jbe @@ExitHere
-
- lodsb { al contains next byte }
-
- mov ah,al
- and ah,0C0h
- cmp ah,0C0h
- jne @@SingleByte
- { must be a run of bytes }
- mov cl,al
- and cl,3Fh
- lodsb
- rep stosb
- jmp @@GetNextByte
-
- @@SingleByte:
- stosb
- jmp @@GetNextByte
-
- @@ExitHere:
- mov SourceSeg,ds
- mov SourceOfs,si
- mov DestSeg,es
- mov DestOfs,di
-
- pop si
- pop ds
- end;
-
- If Odd(BytesWide) then Source := Ptr (SourceSeg,SourceOfs+2)
- else Source := Ptr (SourceSeg,SourceOfs);
-
- Dest := Ptr (DestSeg,DestOfs);
- end;
-
- Procedure DisplayPCX (X,Y:Integer;Buf:Pointer);
- var
- I,NumRows,
- BytesWide : Integer;
- Header : PCXHeaderPtr;
- DestPtr : Pointer;
- Offset : Word;
- begin
- Header := Ptr (Seg(Buf^),Ofs(Buf^));
- Buf := Ptr (Seg(Buf^),Ofs(Buf^)+128);
- Offset := Y * 320 + X;
- NumRows := Header^.YMax - Header^.YMin + 1;
- BytesWide := Header^.XMax - Header^.XMin + 1;
- For I := 1 to NumRows do begin
- DestPtr := Ptr ($A000,Offset);
- ExtractLineASM (BytesWide,Buf,DestPtr);
- Inc (Offset,320);
- end;
- end;
-
- Procedure DisplayPCXPas (X,Y:Integer;Buf:Pointer);
- var
- I,NumRows,
- BytesWide : Integer;
- Header : PCXHeaderPtr;
- DestPtr : Pointer;
- Offset : Word;
- begin
- Header := Ptr (Seg(Buf^),Ofs(Buf^));
- Buf := Ptr (Seg(Buf^),Ofs(Buf^)+128);
- Offset := Y * 320 + X;
- NumRows := Header^.YMax - Header^.YMin + 1;
- BytesWide := Header^.XMax - Header^.XMin + 1;
- For I := 1 to NumRows do begin
- DestPtr := Ptr ($A000,Offset);
- ExtractLine (BytesWide,Buf,DestPtr);
- Inc (Offset,320);
- end;
- end;
- end.
-
- That's the unit to put a PCX on the screen. There are two routines to
- put up a picture. One is written entirely in Pascal and the other is an
- assembly/Pascal hybrid. I was very suprised to find the hybrid
- performed as well as the 100% assembly routine I wrote. Following is a
- routine to put the unit to the test.
-
- Program MCGATest;
-
- uses
- Crt,Dos,MCGALib,MCGAPCX;
-
- var
- Stop,
- Start : LongInt;
- Regs : Registers;
- PicBuf : Pointer;
- const
- NumTimes = 10;
-
- Procedure LoadBuffer (S:String;Buf:Pointer);
- var
- F : File;
- BlocksRead : Word;
- begin
- Assign (F,S);
- Reset (F,1);
- BlockRead (F,Buf^,65000,BlocksRead);
- Close (F);
- end;
-
- Function Tick : LongInt;
- begin
- Regs.ah := 0;
- Intr ($1A,regs);
- Tick := Regs.cx shl 16 + Regs.dx;
- end;
-
- Procedure ShowAndTell;
- var
- Ch : Char;
- begin
- TextMode (3);
- WriteLn ('We just displayed a PCX file to the screen ',NumTimes,' times.');
- WriteLn ('Routine took ',(Stop-Start)/NumTimes:6:4,' ticks or ');
- WriteLn ((Stop-Start)/18.2/NumTimes:4:3,' seconds per image!');
- WriteLn ('That''s about ',18.2/((Stop-Start)/NumTimes):6:4,' times per second.');
- Repeat Until Keypressed;
- While Keypressed do Ch := Readkey;
- end;
-
- Procedure Control;
- var
- I : Integer;
- begin
- SetGraphMode ($13);
- LoadBuffer ('E:\NAVAJO.PCX',PicBuf);
- Start := Tick;
- For I := 1 to NumTimes do
- DisplayPCXPas (0,0,PicBuf);
- Stop := Tick;
- ShowAndTell;
-
- SetGraphMode ($13);
- Start := Tick;
- For I := 1 to NumTimes do
- DisplayPCX (0,0,PicBuf);
- Stop := Tick;
- ShowAndTell;
- end;
-
- Procedure Init;
- begin
- Randomize;
- GetMem (PicBuf,65500);
- end;
-
- Begin
- Init;
- Control;
- End.
-
- Please run the above program and drop me a line on how it performs. I
- am very curious to learn how it behave on different machines. Leave me
- a message showing CPU, speed and how many images per second you
- obtained. Kinda like this:
-
- <----- 386, 33 mhz ----->
- Pascal method ............................. 0.8585 per second
- Pascal/Asm hybrid.......................... 8.6667 per second
-
- The first thing you will notice is, we are not setting the palette. The
- picture will appear in an ugly color unless the PCX you try to display
- is drawn using the default palette. Also, it does not support pictures
- larger then 65500 bytes. I'll finish up this discussion next time.
-
- Hack on,
- jim
-