
                  |====================================|
                  |                                    |
                  |   TELEMACHOS proudly presents :    |
                  |                                    |
                  |    Part 3 of the PXDTECH serie -   |
                  |                                    |
                  |    Advanced .PCX handling in TP    |
                  |   (8 / 24 bit .PCX's of all res.)  |
                  |====================================|

               ___---__-->   The PXDTECH serie   <--__---___

<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>


INTRODUCTION
-------------

Hi guys!!  First of all - this is gonna be a quickie!!
I'll go over the theory pretty fast and leave the the hard work of under-
standing the stuff to you (but hey - that WAS the original purpose of the
PXDTECH series anyways... so I'm not going to feel guilty about it :)
Don't worry though - I have added so many comments to the sample code that
it's almost an insult to your intellect :) (none intended though....)

Todays topic is something as common as .PCX loading - the difference between
this doc and so many others is that THIS loader won't be such a Suck-Ass
loader as those described in most other docs.
In this document I have tried to follow up on the trend started with DIRECT-X.
As a result of this our .PCX loader will be more or less independant of such
things as screenmode, picture-depth (8 or 24bit) and picture resolution.
The idea is : The user wants to display a .PCX - so what are we waiting for ?
Lets display it!!! The user does'nt have to think about such simple problems
as what screenmode he is in - he wants the job done, and we just do it :)


I made the .PCX loader so it fits into the TMHICOL unit we developed in the
last TECH.
I have included the new version of that unit with this doc. Using this unit
nothing is easier than to display a .PCX in TP :) One line to set the screen-
mode and one to load the picture.. Check out the sample program to see how
incredibly easy it is :)

Also (to my BIG surprise) people started mailing me for some GetPixel routines.
I thought it would be a piece of cake to implement those after reading the
TECH #2 - but anyway, I made a few getPixels which are also included in this
release. They are all tested except the 24bit getpixel (as I have'nt got any
24bit modes on my card - but hey, they really should work :)


As always get this series from the Peroxide homepage at :

http://peroxide.home.ml.org

(The homepage is now partly optimized for Shockwave FLASH - get it, it rulz!)

Also I think I'll start putting this series on the usual FTP-sites known from
the PXDTUT series.


Mail me at :

tm@image.dk





OK - LETS GET STARTED.. WHAT'S UP ?
------------------------------------

Sounds nice enough ?? He heh, well - as you should know by now I'm not the
type that gives it ALL away for free. You'll have to work a bit for it :)

So - I have made about 2/3 (at least) of the features needed for a TOTALLY
independant .PCX loader.... Actually the loader I'm going to give you will
probably be every bit as advanced as you'll ever need - but those of you out
there who likes a challenge should really go for implementing the rest of the
features :)  (it's really not that hard...)

So : To sum it all up..


IMPLEMENTED
------------
 - ONE loader handles all .PCX types
 - Deals with both 8 and 24 bit .PCX's (no other types exist w. colors >= 256)
 - Can display 24bit images in both 24bit and 32bit mode. Ie. simulates a
   24bit mode on a 32bit display if no 24bit mode is available!
 - Shows ALL picture resolutions.
 - Works in ALL screen resolutions.
 - Can place the .PCX at any starting coordinate on the screen.
 - Can display pictures of lower resolution than the screen-mode (NOTE : see
   NEEDED section)
 - If picture is larger than the screen it is clipped (NOTE : see NEEDED sect.)


NEEDED
------
 - The loader only clips large pictures if they are placed at (0,0) - WHY ??
   (this is a "2 * one line" addition)
 - The loader cannot display pictures of lower bitdepth than the screenmode.
   Ie. it cannot load a 8bit .pcx to a screen in 15,16,24 or 32 bit mode.
   (hint : remember those "virtual pallettes" I talked about in TECH 2 ?)
 - The loader should scale pictures too large to fit on the screen..
   (THIS IS A LITTLE DIFFICULT I GUESS...GO FOR IT!!)
 - Can you speed up the code ??
   (hint : why calculate bank-nr each byte ??)






THEORY OF PCX-LOADING.
-----------------------


OK - the structure of a .PCX is VERY simple.

First there is a 128 byte header with details about the picture resolution
and other nifty stuff. (more on this later)

Immediately after this header follows the image data. The data is compressed
after this algorithm : (you read byte-by-byte from the file)

* If the top two bits are set in a byte it means that the NEXT byte should be
  drawn to the screen X times, where X is the value of the lower 6 bits of the
  first byte.

* If the top two bits IS'NT set in a byte it means that the byte should be
  considered raw data and drawn to the screen.

After the image data, a decimal 12 should appear to mark the beginning of a
256 color pallette (IF the picture is a 8bit picture - otherwise neither the
pallette or the decimal 12 should appear). The decimal 12 should appear 769
bytes counted backwards from the end of the file.

The last 768 bytes contains the data for the pallette stored as R,G,B, R,G,B,
and so on for all 256 colors (Note that 256 * 3 = 768).
The R,G and B values are stored as byte values ranging from 0..255 so you'll
have to scale them before feeding them to the DAC.



HOW TO CODE
------------

With this information we could code a crude .PCX loader that will load .PCX's
of the resolution 320X200X8bit to a screen in mode 13h :


PROCEDURE LoadPCX(Fname : string);
VAR
 temp, temp2 : byte;
 f : file;
 loop1 : integer;
 offset : word;
 R,G,B : byte;

BEGIN
  offset:=0;
  Assign(f, Fname);
  Reset(f,1);
  Seek(f,128); {ignore header}

  Repeat
  BEGIN
   Blockread(f,temp,1);
    if ((temp and $c0) = $c0) then   {if top two bits are set}
     BEGIN
      Blockread(f,temp2,1);
       for loop1:=1 to (temp and $3f) do {loop this many times (lo 6 bits)}
         BEGIN
          mem[$a000:offset]:=temp2;
          inc (offset);
         END;
     END
      else
     BEGIN             {if top two bits are not set - uncompressed data}
      mem[$a000:offset]:=temp;
      inc (offset);
     END;
  END;
 until (offset = 64000+1); {all data read and decoded}

Seek(f, filesize(f)-768);

For loop1:=0 to 255 DO {read the pallette}
 BEGIN
  Blockread(f,R,1);
  Blockread(f,G,1);
  Blockread(f,B,1);
  Setpal(loop1,R DIV 4, G DIV 4, B DIV 4);
 END;
Close(f);
END;



He heh.... while the code above works it's worth CRAP!!!
This loader is SO slow that loading a 8bit .PCX 320X200 to mode 13h takes
longer time than loading a 24bit .PCX in 800X600 with the loader included in
the TMHICOL unit..
The reason why I included it is that it's easier to point out what is
WRONG with the loader above than it is to describe all the features we need
to implement :)


Thing which are wrong :
-----------------------

 *  The loader reads a byte at a time from the file. Reading data is SLOW so
    what we really SHOULD do is load the image chunk by chunk from the file
    and then access the data from a small memory buffer. If we loaded fx. 2Kb
    at a time from the file and then decompressed the data from this buffer
    we would only have read 16 times from a 32Kb file where we would read
    32768 times using the method above :)

 *  The Loader can only load pictures of a specific size - and to a specific
    screen resolution (namely mode 13h). This is'nt cool - we need to take a
    look at the header :
    I have marked the fields we are interrested in with a '*'

            PcxHeaderT = Record
                 Manufacturer : byte;
             *   Version      : byte;
                 Encoding     : byte;
             *   BitsPerPixel : byte;
             *   Xmin         : word;
             *   Ymin         : word;
             *   Xmax         : word;
             *   Ymax         : word;
                 HDpi         : word;
                 VDpi         : word;
                 Colormap     : array[0..47] of byte;
                 Reserved     : byte; {always 0}
             *   NPlanes      : byte;
             *   BytesPerLine : word;
                 PaletteInfo  : word;
                 HscreenSize  : word;
                 VscreenSize  : word;
                 Filler       : Array[0..53] of byte; {all 0 - fill header}
               end;

    OK - we use this header to determine the size of the picture by looking
    at the Xmin, Xmax and Ymin, Ymax... We do *NOT* use the HScreensize,
    VScreensize as those field are NOT supported by many .PCX savers!!
    Often the Xmin, Ymin are set to 0 (I have'nt seen a pic where this was'nt
    the case) but better safe than sorry!)
    The version should always be 5 as this is the version used by both 8bit
    256 color and 24bit images.

    **************************************************
    IMPORTANT - BE SURE TO READ THE FOLLOWING LINES!!!
    **************************************************
    Now for the FIRST really nasty trap in .PCX decoding. The field
    'BytesPerLine' is ALWAYS set to an even number - even when the picture
    is'nt of an even width!!
    What does this mean ??  It means that even though a picture is only
    fx. 127 pixels wide, each scanline is stored as 128bytes! And therefore
    128 bytes must be decoded per scanline - but only the first 127 bytes
    contains valid picture data. The last byte is garbage!!!
    Don't ask me WHY they did it that way - also sounds totally stupid to
    me!!
    If the loader does'nt take this into considerations the image will screw
    up when not an even amount of pixels wide!!
    We solve this by making a decoding break at the end of each scanline -
    ie. the structure of the loader becomes :  (pseudo code)

             for (y = 1 to number of Ylines in picture) do
               decode 'BytesPerLine' bytes of data and draw Xsize bytes
               to the screen


    OK - this done we can now show pictures of any size smaller than 320X200
    in mode 13h


 *  We want to be able to display SVGA .PCX's
    Well - with the new structure this is easy. All that needs to be changed
    is the putpixel part where we now insert the inner part of a VESA-
    putpixel. (and remembers to check for bankswitch)
    Now we can handle all resolutions in 8bit as we use the header to get
    information about image size - and a VESA putpixel will also work in mode
    13h (as the whole mode lies in bank 0).


 *  We want to be able to display 24bit pictures too!
    Well - we need to take a few new fields into considerations. The field
    'NPlanes' tells us if the image is a multi-plane image - and thus if it's
    a 24bit picture. (3 planes)

    Now we need to change our scanline renderer a little so it'll draw ALL
    the planes. The planes are stored as R,B,G where first ALL R's from the
    scanline are compressed after the normal .PCX compression method, then
    all G's and last all B's. So when we decode, we first decode and store
    all R components of the scanline to memory, followed by the G and B
    components.
    While also each plane follows the rule about always storing an even amount
    of bytes per scanline there is NOT a decoding break between planes in a
    scanline. Ie. some of the compressed R-values COULD actually be G values
    (with the same value as R values). So if the plane ends while in a repeat
    loop of R components the rest of the loop should be considered a G loop :)
    (Am I being confusing here ?? )

    There IS a decoding break at the end of each completed SCANLINE though!
    So basicly all there is to doing 24bit images is to add another loop to
    the inner loop of the loader - namely a loop that loops from 0 to
    NPlanes - 1. (And then of course handling the 24/32bit mode - but this
    should be easy after reading PXDTECH #2)


MY LOADER
-----------

I have put all those things together in a nice .PCX loader that handles
about all .PCX's available.... Use this for an idea of how to combine all
the stuff mentioned above (or simply rip it off and use it as you like -
a gift from me to you :)


OK - shitload of code follows :


PROCEDURE VESALoadPCX(Fname : string;X,Y : word;pal : boolean);
{reads any .PCX file (8 or 24bit) of any size to any screen dimension }
{at any place - nice ehh ?? :)                                        }
TYPE
 tempBufferT = Array[0..2048] of byte;
 BufferT = ^TempBufferT;
 PcxHeaderT = Record
                 Manufacturer : byte;
                 Version      : byte;
                 Encoding     : byte;
                 BitsPerPixel : byte;
                 Xmin         : word;
                 Ymin         : word;
                 Xmax         : word;
                 Ymax         : word;
                 HDpi         : word;
                 VDpi         : word;
                 Colormap     : array[0..47] of byte;
                 Reserved     : byte; {always 0}
                 NPlanes      : byte;
                 BytesPerLine : word;
                 PaletteInfo  : word;
                 HscreenSize  : word;
                 VscreenSize  : word;
                 Filler       : Array[0..53] of byte; {all 0 - fill header}
               end;

VAR
 temp, temp2 : byte;        {data we load from pcx file}
 pcxheader : PcxHeaderT;    {header file stores info on .PCX size}
 f : file;                  {*.PCX file}
 loop1 : integer;           {used for color repeat loop}

 offset : longint;          {offset for start of each scanline}
 offset_in_line : longint;  {offset used within the scanlines}
 plotoffs : word;           {offset in the SVGA banks}
 RGBLoop  : byte;           {loop to make sure we load all planes in pic}
 BytesPerPixel : byte;      {8, 24 or 32 bit mode (1,3 or 4 bytes)}

 R,G,B : byte;              {R,G and B values read from pallette data}
 bank : word;               {bank number for a 32bit address}
 TempBuffer : BufferT;      {picture data}
 buffaddr : word;
 buffoffs : word;           {pointer to picture data}
 filsize : longint;         {how big is the .PCX file? (in bytes)}

 Bytesdecoded : word;       {how many bytes HAS been decoded in current line}
 DecodedInPlane : word;     {how many bytes HAS been decoded in current plane}
 Xres : word;               {X resolution of picture}
 Yres : word;               {Y resolution of picture}
 YLinecounter : integer;    {Which Y-line are we currently processing}

 BytesPerLine : word;
 LineSize     : word;


{This small procedure will refresh the data-buffer by loading 2Kb (or less)}
{from the picture file                                                     }
PROCEDURE RefreshDataBuffer;
BEGIN
  buffoffs := 0;
  if (filsize >= 2048) then
   begin
    BlockRead(f,mem[buffaddr:0],2048);
    filsize := filsize - 2048;
   end
  else
   begin
    BlockRead(f,mem[buffaddr:0],filsize);
    filsize := 0;
   end;
END;


BEGIN
  GetMem(TempBuffer,2048);                       {This will be our 2Kb data}
  buffaddr := Seg(TempBuffer^);                  {buffer                   }
  BytesPerPixel := TMVESA_BIT_DEPTH shr 3;
  offset := Longint(Y) * VESAModeInfo.BytesPerScanline +
            Longint(X) * BytesPerPixel;          {start offset of picture}
                                                 {on the screen          }

  If Exist(Fname) then                           {Check if the file exist}
  Assign(f, Fname) else
  Exit;

  Reset(f,1);
  BlockRead(f,pcxheader,128);                    {read the 128 byte header}

  Xres := (pcxheader.Xmax+1)-pcxheader.Xmin;
  Yres := (pcxheader.Ymax+1)-pcxheader.Ymin;
  BytesPerLine := pcxheader.BytesPerLine;
  LineSize := BytesPerLine * pcxheader.NPlanes;  {save some info we'll need}


  filsize := FileSize(f)-128;                    {bytes of image data     }
                                                 {(all - 128 bytes header)}


  {Now check if the image is a 8bit pallette image - and if we have CHOSEN}
  {to load the pallette (the boolean 'pal' flag)                          }
  if (pal) and (pcxheader.version = 5) and (pcxheader.NPlanes = 1) then
   begin
    Seek(f,FileSize(f)-768);                     {seek start of pallette}
      For loop1:=0 to 255 DO                     {read and set the pallette}
       begin
         Blockread(f,R,1);
         Blockread(f,G,1);
         Blockread(f,B,1);
         Setpal(loop1,R DIV 4, G DIV 4, B DIV 4); {.PCX stores RGB as 0..255}
       end;                                       {but the DAC wants them in}
   end;                                           {the range 0..63 - thus   }
                                                  {we divide by 4           }


  Seek(f,128);                                   {Seek to start of image data}

  RefreshDataBuffer;                             {Load first DataBuffer }

for YlineCounter := 1 to Yres do                 {main loop - load all lines}
begin
  bytesdecoded := 0;                             {need to decode BytesPerLine}
                                                 {* NPlanes  pr. scanline    }

  DecodedInPlane := 0;                           {Each plane is BytesPerLine}
                                                 {bytes big..                     }

  RGBloop := pcxheader.NPlanes-1;                {.PCX's are saved as RGB   }
                                                 {so multiplane starts with }
                                                 {the R byte in memory...   }
                                                 {(NOTE : when we putpixel  }
                                                 { we start with the B byte)}


  offset_in_line := offset+RGBLoop;              {Now - this is a little    }
                                                 {hard to grasp.. What does }
                                                 {all those offset's mean ? }
                                                 {Remember the offset : we  }
                                                 {made that the offset of   }
                                                 {the top-left pixel of the }
                                                 {picture. offset_in_line is}
                                                 {the offset in memory where}
                                                 {the first PLANE lies (if  }
                                                 {a multiplane image)...    }
                                                 {This is NOT the same as   }
                                                 {.PCX starts with the Rbyte}
                                                 {and not the B-byte...     }


  {Here goes the loop that draws all planes in ONE scanline}
  Repeat
    temp := mem[buffaddr:buffoffs];               {data from .pcx file}
    inc(buffoffs);
    if (buffoffs = 2048)  then RefreshDataBuffer; {load new buffer if needed}

       if ((temp and $c0) = $c0) then
                   {if top 2 bits are set..}
        BEGIN
          temp2 := mem[buffaddr:buffoffs];        {...then plot this byte...}
          inc(buffoffs);
          if (buffoffs = 2048)  then RefreshDataBuffer; {load new buffer}

            for loop1:=1 to (temp and $3f) do     {... this many times!}
                                                  {(value of lower 6 bits)}

               BEGIN  {This is just the inner stuff of a VESA putpixel}
                 bank := (offset_in_line) SHR (16-TMVESA_BankShiftModifier);
                 plotoffs := word(offset_in_line - (bank SHL (16-TMVESA_BankShiftModifier)));

                 IF (bank <> TMVESA_Cur_page) THEN
                 BEGIN
                  TMVESA_cur_page := bank;
                   ASM
                    Xor bl,bl
                    mov dx,bank
                    call [VESAModeInfo.BankSwitch]
                   END;
                 END;

                 {Only display the data if really IN picture, and ON screen}
                 if (DecodedInPlane < Xres) then
                 begin
                  ASM
                   MOV AX, $A000
                   MOV ES, ax
                   MOV DI, plotoffs
                   MOV AL, temp2
                   MOV ES:[DI], AL
                  END;
                 end;

                 inc(bytesdecoded);                   {counter for scanline}
                 inc(offset_in_line,BytesPerPixel);
                 inc(DecodedInPlane);                 {counter for plane   }
                 if (DecodedInPlane = BytesPerLine) then
                  begin    {a new plane has started}
                   dec(RGBLoop);
                   DecodedInPlane := 0;
                   offset_in_line := offset+RGBLoop;
                  end;
               END;
        END
       else
        BEGIN             {if top 2 bits are NOT set = uncompressed data}
          bank := (offset_in_line) SHR (16-TMVESA_BankShiftModifier);
          plotoffs := word(offset_in_line - (bank SHL (16-TMVESA_BankShiftModifier)));

          IF (bank <> TMVESA_Cur_page) THEN
           BEGIN
            TMVESA_cur_page := bank;
            ASM
             Xor bl,bl
             mov dx,bank
             call [VESAModeInfo.BankSwitch]
            END;
           END;

          If (DecodedInPlane < Xres) then

           begin
            ASM
             MOV AX, $A000
             MOV ES, ax
             MOV DI, plotoffs
             MOV AL, temp
             MOV ES:[DI], AL
            END;
           end;
         inc(bytesdecoded);
         inc(DecodedInPlane);
         inc(offset_in_line,BytesPerPixel);
         if (DecodedInPlane = BytesPerLine) then
            begin   {new plane has started}
             dec(RGBLoop);
             DecodedInPlane := 0;
             offset_in_line := offset+RGBLoop;
            end;
        END;

  {when we reach this point we have decoded all planes in ONE scanline}
  until (bytesdecoded = LineSize); {one scanline done!}

{We move ONE Y-position down on the screen. NOTE : no need for bankcheck}
{here because THIS offset is a linear 32bit offset in the screen        }
offset := offset + VESAModeInfo.BytesPerScanLine;
end; {all lines in picture done....}

Close(f);
{See?? Not so hard after all :)   We free the memory allocated for the  }
{data-buffer                                                            }
FreeMem(TempBuffer,2048);
END;





LAST REMARKS
-------------

Well, that's about all for now.
Hope you found this doc useful - and BTW : If you DO make anything public using
these techniques please mention me in your greets or where ever you see fit.
I DO love to see my name in a greeting :=)


Hope this was explained good enough for you all to understand. If not read it
again - it really IS pretty simple stuff!!! (when you get the hang of it)
Also, check out the sample program - it has TONS of comments :)

This was a pretty quick release from me - all written in a few hours after a
hard day at the university.
The next few months all you'll probably see from me will be rather quick
releases - I simple does'nt have the time to write any big 3d-tutorial-master-
pieces...
After christmas though I'll have plenty of time to code and then I plan to
release a big follow-up tutorial (possibly split up into two) to tut3 and tut4
(the 3d ones) about all sorts of more advanced 3d-stuff like the use of
matrices, faster rotations, cameras, clipping, object loading, quick sorting,
more poly-fills... you know - that kind of stuff..



Until then - have fun, and remember to visit the Peroxide Page often :)

Telemachos


PS. I HAVE bought my ticket to The Party 1998 - have you ????



