home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / MCGA#04.ZIP / LESSON04.TXT < prev    next >
Encoding:
Text File  |  1992-04-13  |  10.7 KB  |  347 lines

  1.                            MCGA Graphics Tutorial
  2.                                  Lesson #4
  3.                                 by Jim Cook
  4.  
  5. Now for the fun stuff.........
  6. Whenever you design a graphics package you have to decide what format to
  7. store your images in.  There are too many to count on both hands, and
  8. for the topic of this lesson I have to come up with one that is easy to
  9. handle and can be represented quickly.  Here's what I've come up with...
  10. NO format.  We are going to basically emulate the PutImage and GetImage
  11. found in TP's BGI.  I chose this because it's easiest to understand and
  12. it can be put on the screen very quickly.  As a matter of fact we can
  13. put a complete screen image up 32 times per second on my 386/33mhz.
  14. Most of the games we write will rely on a much smaller animation scale,
  15. but may involve up to 100 animations at a time.
  16.  
  17. OK, you may ask, how do I save an image to disk in an uncompressed
  18. format?  Good question, most paint programs do not directly support this
  19. format, but most do support PCX files.  We will write a program that
  20. puts a .PCX (or it's smaller cousin .PCC) graphic on the screen.  From
  21. here we will cut the chunk of the picture we want and save it to the
  22. disk.  In order to accomplish all of this, we need to understand PCX
  23. files.  Fasten your seatbelts!
  24.  
  25.   PCXHeader   =  record
  26.                    Signature      :  Byte;    { $0A }
  27.                    Version        :  Byte;    { varies }
  28.                    Encoding       :  Byte;    { $01 }
  29.                    BitsPerPixel   :  Byte;    { $08 for 1 byte per pixel }
  30.                    XMin,YMin,                 { Upper-left corner (0,0)  }
  31.                    XMax,YMax      :  Integer; { Lower-right corner }
  32.                    HRes,VRes      :  Integer; { resolution 320 x 200 }
  33.                    Palette        :  Array [0..47] of byte;  { colors }
  34.                    Reserved       :  Byte;
  35.                    Planes         :  Byte;
  36.                    BytesPerLine   :  Integer;
  37.                    PaletteType    :  Integer;
  38.                    Filler         :  Array [0..57] of byte;
  39.                  end;
  40.  
  41. This is the first 128 bytes of any PCX (or PCC) file.  The Signature is
  42. always $0A and the Encoding is always $01, at least for now.  The rest
  43. of the header is self-explanatory.  Well, there is one catch, when this
  44. format was developed, the geniuses at ZSoft didn't plan for pictures
  45. with 256 colors or more.  There are 3 bytes per color in MCGA mode so
  46. the measely 48 bytes set aside in the header only allows for 16 colors.
  47. ZSoft ended up putting the 768 bytes of palette data at the end of the
  48. file and the Palette area in the header is available real estate.
  49.  
  50. Immediately following the header is the body of the PCX file.  The body
  51. is compressed by a technique called run-length compression.  For many of
  52. you, this is the first time you have looked at compression so I'll take
  53. this slow.  It is not important to understand this for the purpose of
  54. our graphic library, but it is vital if you work with other formats.
  55. Most use some type of run-length compression.  TIFF and GIF use LZW
  56. compression, by the way.
  57.  
  58. This is the flowchart (ugh) for unpacking one row of a PCX file:
  59.  
  60.            -------->  Read in a byte X
  61.          /                   |
  62.        /           Are the two high bits set?
  63.      /          [ 1  1  ?  ?  ?  ?  ?  ? ]  <- binary representation of X
  64.    /               / Yes                \ No
  65.    |        X := X and $3F         Write X to screen
  66.    |              |                      |
  67.    ^       Get next byte Y               |
  68.    |              |                      |
  69.    |       Write byte Y to the          /
  70.    |       screen X times             /
  71.    |              \                 /
  72.     \                Done with row?
  73.       \                | No   | Yes
  74.         \              /      |
  75.           -----<------        | Exit
  76.  
  77. That's how to unpack 1 row of a PCX, to unpack the whole picture:
  78.  
  79.                 For I := 1 to NumRows do UnpackRow;
  80.  
  81. Along with some housekeeping to keep track of advancing down the screen
  82. memory.  I know you want the code so here goes:
  83.  
  84. Unit Lib04;
  85.  
  86. interface
  87.  
  88. type
  89.   PCXHeaderPtr=  ^PCXHeader;
  90.   PCXHeader   =  record
  91.                    Signature      :  Char;
  92.                    Version        :  Char;
  93.                    Encoding       :  Char;
  94.                    BitsPerPixel   :  Char;
  95.                    XMin,YMin,
  96.                    XMax,YMax      :  Integer;
  97.                    HRes,VRes      :  Integer;
  98.                    Palette        :  Array [0..47] of byte;
  99.                    Reserved       :  Char;
  100.                    Planes         :  Char;
  101.                    BytesPerLine   :  Integer;
  102.                    PaletteType    :  Integer;
  103.                    Filler         :  Array [0..57] of byte;
  104.                  end;
  105.  
  106. Procedure DisplayPCXPas (X,Y:Integer;Buf:Pointer);
  107. Procedure DisplayPCX    (X,Y:Integer;Buf:Pointer);
  108.  
  109. implementation
  110.  
  111. uses
  112.   Dos;
  113.  
  114. Procedure ExtractLine (BytesWide:Integer;Var Source,Dest:Pointer);
  115. var
  116.   DestIdx,
  117.   SourceIdx   :  Integer;
  118.   InCode,
  119.   RunCount    :  Byte;
  120. begin
  121.   DestIdx := 0;
  122.   SourceIdx := 0;
  123.  
  124.   While DestIdx < BytesWide do begin
  125.     InCode := Mem [Seg(Source^):Ofs(Source^)+SourceIdx];
  126.     Inc (SourceIdx);
  127.  
  128.     If (InCode and $C0) = $C0 then begin
  129.       RunCount := InCode and $3F;
  130.       InCode := Mem [Seg(Source^):Ofs(Source^)+SourceIdx];
  131.       Inc (SourceIdx);
  132.       FillChar (Mem[Seg(Dest^):Ofs(Dest^)+DestIdx],RunCount,InCode);
  133.       Inc (DestIdx,RunCount);
  134.       end
  135.     Else begin
  136.       Mem [Seg(Dest^):Ofs(Dest^)+DestIdx] := InCode;
  137.       Inc (DestIdx);
  138.       end;
  139.     end;
  140.   If Odd (BytesWide) then Source := Ptr(Seg(Source^),Ofs(Source^)+SourceIdx+2)
  141.                      else Source := Ptr(Seg(Source^),Ofs(Source^)+SourceIdx);
  142.   Dest   := Ptr(Seg(Dest^),Ofs(Dest^)+DestIdx);
  143. end;
  144.  
  145. Procedure ExtractLineASM (BytesWide:Integer;Var Source,Dest:Pointer);
  146. var
  147.   DestSeg,
  148.   DestOfs,
  149.   SourceSeg,
  150.   SourceOfs   :  Word;
  151. begin
  152.   SourceSeg := Seg (Source^);
  153.   SourceOfs := Ofs (Source^);
  154.   DestSeg   := Seg (Dest^);
  155.   DestOfs   := Ofs (Dest^);
  156.  
  157.   asm
  158.     push  ds
  159.     push  si
  160.  
  161.     mov   ax,DestSeg
  162.     mov   es,ax
  163.     mov   di,DestOfs     { es:di -> destination pointer }
  164.     mov   ax,SourceSeg
  165.     mov   ds,ax
  166.     mov   si,SourceOfs   { ds:si -> source buffer }
  167.  
  168.     mov   bx,di
  169.     add   bx,BytesWide   { bx holds position to stop for this row }
  170.     xor   cx,cx
  171.  
  172.   @@GetNextByte:
  173.     cmp   bx,di          { are we done with the line }
  174.     jbe   @@ExitHere
  175.  
  176.     lodsb                { al contains next byte }
  177.  
  178.     mov   ah,al
  179.     and   ah,0C0h
  180.     cmp   ah,0C0h
  181.     jne   @@SingleByte
  182.                          { must be a run of bytes }
  183.     mov   cl,al
  184.     and   cl,3Fh
  185.     lodsb
  186.     rep   stosb
  187.     jmp   @@GetNextByte
  188.  
  189.   @@SingleByte:
  190.     stosb
  191.     jmp   @@GetNextByte
  192.  
  193.   @@ExitHere:
  194.     mov   SourceSeg,ds
  195.     mov   SourceOfs,si
  196.     mov   DestSeg,es
  197.     mov   DestOfs,di
  198.  
  199.     pop   si
  200.     pop   ds
  201.   end;
  202.  
  203.   If Odd(BytesWide) then Source := Ptr (SourceSeg,SourceOfs+2)
  204.                     else Source := Ptr (SourceSeg,SourceOfs);
  205.  
  206.   Dest := Ptr (DestSeg,DestOfs);
  207. end;
  208.  
  209. Procedure DisplayPCX (X,Y:Integer;Buf:Pointer);
  210. var
  211.   I,NumRows,
  212.   BytesWide   :  Integer;
  213.   Header      :  PCXHeaderPtr;
  214.   DestPtr     :  Pointer;
  215.   Offset      :  Word;
  216. begin
  217.   Header := Ptr (Seg(Buf^),Ofs(Buf^));
  218.   Buf := Ptr (Seg(Buf^),Ofs(Buf^)+128);
  219.   Offset := Y * 320 + X;
  220.   NumRows := Header^.YMax - Header^.YMin + 1;
  221.   BytesWide := Header^.XMax - Header^.XMin + 1;
  222.   For I := 1 to NumRows do begin
  223.     DestPtr := Ptr ($A000,Offset);
  224.     ExtractLineASM (BytesWide,Buf,DestPtr);
  225.     Inc (Offset,320);
  226.     end;
  227. end;
  228.  
  229. Procedure DisplayPCXPas (X,Y:Integer;Buf:Pointer);
  230. var
  231.   I,NumRows,
  232.   BytesWide   :  Integer;
  233.   Header      :  PCXHeaderPtr;
  234.   DestPtr     :  Pointer;
  235.   Offset      :  Word;
  236. begin
  237.   Header := Ptr (Seg(Buf^),Ofs(Buf^));
  238.   Buf := Ptr (Seg(Buf^),Ofs(Buf^)+128);
  239.   Offset := Y * 320 + X;
  240.   NumRows := Header^.YMax - Header^.YMin + 1;
  241.   BytesWide := Header^.XMax - Header^.XMin + 1;
  242.   For I := 1 to NumRows do begin
  243.     DestPtr := Ptr ($A000,Offset);
  244.     ExtractLine (BytesWide,Buf,DestPtr);
  245.     Inc (Offset,320);
  246.     end;
  247. end;
  248. end.
  249.  
  250. That's the unit to put a PCX on the screen.  There are two routines to
  251. put up a picture.  One is written entirely in Pascal and the other is an
  252. assembly/Pascal hybrid.  I was very suprised to find the hybrid
  253. performed as well as the 100% assembly routine I wrote.  Following is a
  254. routine to put the unit to the test.
  255.  
  256. Program MCGATest;
  257.  
  258. uses
  259.   Crt,Dos,MCGALib,MCGAPCX;
  260.  
  261. var
  262.   Stop,
  263.   Start       :  LongInt;
  264.   Regs        :  Registers;
  265.   PicBuf      :  Pointer;
  266. const
  267.   NumTimes    = 10;
  268.  
  269. Procedure LoadBuffer (S:String;Buf:Pointer);
  270. var
  271.   F           :  File;
  272.   BlocksRead  :  Word;
  273. begin
  274.   Assign (F,S);
  275.   Reset (F,1);
  276.   BlockRead (F,Buf^,65000,BlocksRead);
  277.   Close (F);
  278. end;
  279.  
  280. Function Tick : LongInt;
  281. begin
  282.   Regs.ah := 0;
  283.   Intr ($1A,regs);
  284.   Tick := Regs.cx shl 16 + Regs.dx;
  285. end;
  286.  
  287. Procedure ShowAndTell;
  288. var
  289.   Ch     :  Char;
  290. begin
  291.   TextMode (3);
  292.   WriteLn ('We just displayed a PCX file to the screen ',NumTimes,' times.');
  293.   WriteLn ('Routine took ',(Stop-Start)/NumTimes:6:4,' ticks or ');
  294.   WriteLn ((Stop-Start)/18.2/NumTimes:4:3,' seconds per image!');
  295.   WriteLn ('That''s about ',18.2/((Stop-Start)/NumTimes):6:4,' times per second.');
  296.   Repeat Until Keypressed;
  297.   While Keypressed do Ch := Readkey;
  298. end;
  299.  
  300. Procedure Control;
  301. var
  302.   I :  Integer;
  303. begin
  304.   SetGraphMode ($13);
  305.   LoadBuffer ('E:\NAVAJO.PCX',PicBuf);
  306.   Start := Tick;
  307.   For I := 1 to NumTimes do
  308.     DisplayPCXPas (0,0,PicBuf);
  309.   Stop := Tick;
  310.   ShowAndTell;
  311.  
  312.   SetGraphMode ($13);
  313.   Start := Tick;
  314.   For I := 1 to NumTimes do
  315.     DisplayPCX (0,0,PicBuf);
  316.   Stop := Tick;
  317.   ShowAndTell;
  318. end;
  319.  
  320. Procedure Init;
  321. begin
  322.   Randomize;
  323.   GetMem (PicBuf,65500);
  324. end;
  325.  
  326. Begin
  327.   Init;
  328.   Control;
  329. End.
  330.  
  331. Please run the above program and drop me a line on how it performs.  I
  332. am very curious to learn how it behave on different machines.  Leave me
  333. a message showing CPU, speed and how many images per second you
  334. obtained.  Kinda like this:
  335.  
  336.                           <----- 386, 33 mhz ----->
  337.         Pascal method ............................. 0.8585 per second
  338.         Pascal/Asm hybrid.......................... 8.6667 per second
  339.  
  340. The first thing you will notice is, we are not setting the palette.  The
  341. picture will appear in an ugly color unless the PCX you try to display
  342. is drawn using the default palette.  Also, it does not support pictures
  343. larger then 65500 bytes.  I'll finish up this discussion next time.
  344.  
  345. Hack on,
  346. jim
  347.