Displaying 256 Colour PCX Pictures

TAD

Introduction

This article is intended to help newbie 80x86 coders to load and display a 256 colour PCX on the screen using VGA Mode 13h. Like in most other things these days file formats are explained in jargon filled tomes with a little C or pseudo-code if you are lucky, but sadly almost never any 80x86. This is quite sad when you consider that most coders learn 80x86 especially for graphics and it's sheer speed advantage.

The PCX file format is (c) by ZSoft Corporation and has been around for at least 11 years, so it's a well proven and easy to use format for simple graphics. It offers a quick, but not too impressive, form of compression to help reduce disk space using the RLE method of encoding spans of identical pixels using a "run" count.

It is probably worth download the offical ZSoft documentation before reading this article as it will help fill any gaps in my explanation.

The PCX File Format Header

 Byte  Item           Size            Description
 ----  ----           ----            -----------
  0    Manufacturer   1 Byte          ID flag byte = 10 decimal (0A hex)
  1    Version        1 Byte          Version information
                                      0 = version 2.5
                                      2 = version 2.8 with palette info.
                                      3 = version 2.8 no palette
                                      5 = version 3.0
  2    Encoding        1 Byte         1 = .PCX run length encoding (RLE)
  3    BitsPerPixel    1 byte         Number of bits/pixel per plane
  4    Xmin            1 word         left co-ordinate of image
  6    Ymin            1 word         top co-ordinate of image
  8    Xmax            1 word         right co-ordinate of image
 10    Ymax            1 word         bottom co-ordinate of image
 12    HRes            1 word         Horizontal res. of creating device
 14    VRes            1 word         Vertical res. of creating device
 16    Colormap       48 bytes        EGA colour palette settings
 64    -Reserved-      1 byte
 65    NPlanes         1 byte         Number of colour planes
 66    BytesPerLine    1 word         Number of bytes per scan line
 68    PaletteInfo     1 word         1=color/bw, 2=greyscale palette
 70    Filler         58 bytes        padding bytes

The EGA/VGA 16 colour palette

The palette data for a 16 colour image is found at byte 16 in the 128 byte header. Although I will only give example code for 256 colour images, here is some info about 16 colour ones just in case you want to modify the code sometime in the future.

The 48 byte "Colormap" is made up from 16 triplets (3 byte items). Each byte can has a value from 0 to 255. For the EGA palette each one of the triplet bytes must be converted into one of 4 possible levels.

            Triplet byte             EGA Level
           -------------           -----------
              0 to 63 decimal          0
             64 to 127                 1
            128 to 192                 2
            193 to 255                 3

The VGA 256 colour palette

Because there are now 256 possible colours there are 256 triplets, so in total there are 768 bytes for the VGA palette information.

The 768 bytes of palette data is stored as the last 768 bytes in the file and is preceeded by the byte 12 decimal (hex 0C).

To determine if a 768 byte (256 colour) palette is present in the picture file first check that the "Version" byte in the header at byte 1 = 05 and then seek back from the EOF (end-of-file) 768 bytes and read these into memory.

Each byte in this 768 block has a value of 00 to FF hex (255 decimal). Because the VGA palette registers can only use a value of 00 to 3F hex (63 decimal), each byte must be divided by 4 before being sent to the VGA palette registers.

The RLE (Run Length Encoding)

This is a way to reduce long runs of an identical byte into a 2 byte pair with the first byte being a repeat (or "run") count and the second being the actual byte/pixel to repeat.

The encoding is primative by LZ compression standards, but it can still save a reasonable amount of bytes from the file size. This simple encoding scheme can be written in about 26 bytes of code.

                N                  Action
           ------------           --------
           00 to BF hex           Output a byte with the value N (00 to BF)
           C0 to FF hex           Use N as a "run" count and repeat the
                                  next pixel/byte N-C0 hex times

So if we read a byte N and it has a value below C0 hex (192 decimal) we treat it as a raw, literal byte/pixel and output it.

If the byte N is above or equal to C0 hex (192 decimal) then we subtract C0 hex from it to get a 6 bit count with a value of 00 to 3F hex (0 to 63) then following byte is then repeatedly written this number of times.

              [DS:SI] --> Address of a PCX compressed picture
                          (after the 128 byte header).
              [ES:DI] --> Address of output buffer (E.g. A000:0000 hex)
              DX = total length of uncompressed picture

 DepackRLE PROC
              CLD                     ; DF=1 make sure STOSB and LODSB
                                      ;      always move up in memory.
  @@next:
              MOV     CX, 0001h       ; assume a "run" of 1 byte
              LODSB                   ; AL = read the next byte()
              CMP     AL, 0C0h
              JB      @@raw1          ; is AL below C0 hex?

              SUB      AL,0C0h
              MOV      CL, AL         ; otherwise CL = AL - C0 hex

              LODSB                   ; AL = read the next byte()
  @@raw1:
              SUB      DX, CX         ; adjust total DX using CX count
              JC       @@corrupt      ; has DX overflowed ?
              REP      STOSB          ; repeat AL at [ES:DI], CX times
              JNZ      @@next         ; continue while DX <> 0
  @@corrupt:
              RET
 DepackRLE ENDP

The above code requires that the total size of the uncompressed picture be given in the DX register. This is normally 64000 for a 320x200 pixel image. This would decompress the entire image in one go.

You could of course decompress on an image on a line-by-line basis by calling "DepackRLE" 200 times with DX = 320 (where 320 = number of pixels per line).

Any single byte/pixel with a value between C0 and FF hex is prefixed by the byte C1 hex, this is a run of 1 byte.

Compressing an image using RLE (Run Length Encoding)

The following code can be used to compress the image data of a picture into the RLE format.

              [DS:SI] --> Address image/block to compress
              CX = Length of block to compress
              [ES:DI] --> Address of the output buffer

 PackRLE PROC
              PUSH    DI              ; keep start address
 @@pack:
              MOV     BL, 0C1h        ; Count = 1 + C0 hex
              LODSB                   ; get pixel/byte
              DEC     CX
              JZ      @@endrun        ; final pixel/byte?
 @@count:
              CMP     AL, [SI]        ; does it match AL?
              JNZ     @@endrun        ; end of run?
              CMP     BL, 0FFh
              JZ      @@endrun        ; does BL = maxium count?
              INC     BL
              INC     SI
              LOOP    @@count        ; otherwise continue match
 @@endrun:
              CMP     AL, 0C0h
              JAE     @@prefix       ; does pixel/byte need C1h prefix?
              CMP     BL, 0C1h
              JZ      @@rawbyte      ; BL = count of 1?
 @@prefix:
              XCHG    AX, BX
              STOSB                  ; Output byte(BL) "run" count + C0 hex
              XCHG    AX, BX
 @@rawbyte:
              STOSB                  ; Output byte(AL)
              TEST    CX, CX
              JNZ     @@pack         ; continue while CX<>0

              MOV     CX, DI
              POP     AX             ; [DS:AX] --> RLE block address
              SUB     CX, AX         ; CX = length of RLE block
              RET
 PackRLE ENDP

Loading the PCX file.

One of the quickest way to display a .PCX picture file is to read the entire file into memory and then decode it directly to the screen (or other buffer).

This seems little an easy task, after all a 320x200 pixel images only takes up 64000 bytes (hex FA00) and so fits easily into a 64Kb segment. Which is important if you are still using Real-Mode.

But, there is a problem.

For certain (highly detailed) picture images it is possible that the RLE compression will end up with a .PCX file which is bigger than 64Kb, even though the actual uncompressed RAW image is only 62.5 Kb (64000 bytes).

If we stick with loading the entire file, then decompress method we might need a 129 Kb buffer to handle the worst possible case of PCX file size. Or we could read a single byte at a time from the PCX file and decompress it that way. This would only require a 1 byte buffer, but of course would be very slow.

Perhaps the best solution is to use a 1 or 2 Kb cache into which you read 1 or 2Kb at a time then decompressing from this as needed. I have given some TASM source code which shows how this can be done. This method can be easily used to handle other large files in a similar way.

Dimensions of a PCX picture.

Of course you don't always want to use a 320x200 pixel image, you many want a smaller or larger size. In this case you need to discover the size of the PCX image from the 128-byte header.

You can find the size of the image like this:

              [DS:SI] --> Address of PCX header

              MOV     BX, [SI+8]      ; Xmax (right co-ordinate)
              SUB     BX, [SI+4]      ; - Xmin (left co-ordinate)
              INC     BX              ; plus 1

              MOV     CX, [SI+10]     ; Ymax (bottom co-ordinate)
              SUB     CX, [SI+6]      ; - Ymin (top co-ordinate)
              INC     CX              ; plus 1

              MOV     AX, BX
              MUL     CX              ; DX.AX = total pixels/bytes

This gives the dimensions of the PCX image, BX=width and CX=height, it also gives the total number of bytes/pixels in DX.AX which could be used to allocate memory.

Closing Words

There are far, far better file formats around such as the .GIF format which uses the LZW compression algorithm and so makes RLE look like the sad, overweight method it really is.

But because the PCX format is so simple with a fairly quick decompression stage and a vast number of paint programs supporting it, it can still be very useful, especially if you are new to 80x86 or programming in general.

You should be able to find the source for the previously mentioned PCX loaders somewhere with this article, probably in a ZIP file. It was written for TASM and so may require a little bit of "adjusting" so it will work with MASM too. Don't worry, I did NOT use IDEAL mode.

The source code should be fairly easy to understand and use. It is ONLY designed for 256 colour images and for 320x200 pixel images!! So please remember this when designing your graphics. Although this is quite limited as should be enough for newbie coders....

Enjoy.

Regards

TAD #:o)