Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

A C++ PCX File Viewer for Windows 3


JUL91: A C++ PCX FILE VIEWER FOR WINDOWS 3

A C++ PCX FILE VIEWER FOR WINDOWS 3

Paul Chui

Paul develops computer applications for the aviation industry at KPMG Peat Marwick in San Mateo, Calif., and is the coauthor of the Turbo C++ Disktutor. He can be reached on CompuServe at 76077, 3162.


Some years ago, ZSoft developed its PC Paintbrush program as the PC's answer to the Macintosh's MacDraw. Although many graphics programs rival PC Paintbrush in features and popularity, its PCX file format has transcended the program. With each successive version, the PCX file format has adapted to new standards in display hardware to the point that many DOS programs now use PCX files as a standard medium for exchanging graphics.

Windows provides excellent support for bitmap graphics. The object-oriented system Windows uses to manage bitmaps makes them especially easy to work with. To use a PCX bitmap in Windows, however, you must first translate it into the Windows bitmap format. In this article, I present a PCX file viewer implemented as a C++ class that creates a Windows bitmap object from a PCX file. The code was developed under Borland C++ 2.0, a logical step forward for Windows C programmers.

Run Length Encoding

Run Length Encoding (RLE) is a data compression algorithm that takes advantage of repeated patterns. RLE has been discussed previously in DDJ (see "Run Length Encoding" by Robert Zigon, February 1989, and "RLE Revisited" by Phil Daley, May 1989); I'll use a simple example to demonstrate the algorithm. Consider a 640 x 480 monochrome picture that is to be copied to disk. Such an image would take 307,200 (640 * 480) bits, or 38,400 bytes. Now suppose that the image is composed entirely of white pixels. This file would contain 38,400 bytes of 1s. This image, however, can be compactly represented in 2 bytes; the first representing a repeat byte with the value of 38,400, and the second byte representing the repeated data (in this case, 1).

Because RLE takes advantage of repeated patterns, compression of random data is not nearly so effective. Therefore, PCX images are encoded using a variant of the RLE scheme. In PCX files, if a byte has the two high bits set, then it is a repeat byte. Otherwise, the byte is a single data byte. The general algorithm is:

  1. Read a byte;
  2. If the two high bits are set, then N = the lower six bits, else Write this byte and Go to step 1;
  3. Read the next byte and Write it N times;
  4. Go to step 1.
Note that since only six bits are used for the repeat count, the largest possible repeat count is 63. Consequently, at least 4 bytes are needed to encode a scanline that's 640 pixels wide.

Figure 1 shows how a single scanline is compressed in PCX. Because the two high bits are set in the first byte, the lower six bits hold a repeat count. The repeat count is 63 (111111b). A repeat byte of 0s follows. The third byte is a repeat count of 17 (010001b) followed by another repeat byte of 0s. The total is 80 bytes of 0s or 640 black pixels.

Figure 1: Compression of a single scanline of 640 black pixels in PCX

  Hex:     FF           00        D1           00
  Binary:  11 | 111111  00000000  11 | 010001  00000000

There is another quirk in PCX encoding. To encode a data byte with the two high bits set, PCX uses a repeat count of one followed by the data. This could lead to encoded files two times larger than the raw bitmap! For most nonpathological cases, however, PCX compression works well.

Windows Device-Independent Bitmaps

The Windows Device-Independent Bitmap (DIB) format, introduced with Windows 3, overcomes the limitations of the old Windows bitmap format (see the text box entitled "Windows Device-Dependent Bitmaps," which accompanies this article), but you pay a penalty for device independence -- DIBs are more complex. You must set up at least three structures to tell Windows about a new DIB.

The BITMAPINFO, RGBQUAD, and BITMAPINFOHEADER structures, which are defined in WINDOWS.H, are shown in Figure 2. BITMAPINFO has two fields: A header BITMAPINFOHEADER and a color table RGBQUAD[ ]. BITMAPINFOHEADER contains information about the dimensions and format of the image. Each RGBQUAD structure in BITMAPINFO defines a single color. The size of the color table is determined by the number of colors in the image.

Figure 2: The BITMAPINFO, RGBQUAD, and BITMAPINFOHEADER structures as defined in WINDOWS.H

  BITMAPINFO structure:
    BITMAPINFOHEADER     bmiHeader
    RGBQUAD              bmiColors[]

  RGBQUAD structure:
      BYTE            rgbBlue          Blue intensity
      BYTE            rgbGreen         Green intensity
      BYTE            rgbRed           Red intensity
      BYTE            rgbReserved      Not used, set to 0

  BITMAPINFOHEADER structure:
      DWORD           biSize           Size of BITMAPINFOHEADER (bytes)
      DWORD           biWidth          Width of bitmap (pixels)
      DWORD           biHeight         Height of bitmap (pixels)
      WORD            biPlanes         Always 1
      WORD            biBitCount       Bits per pixel
                                       1 = monochrome
                                       4 = 16 color
                                       8 = 256 color
                                       24 = 16 million color
      DWORD           biCompression    Type of compression
                                       0 = no compression
                                       1 = 8-bits/pixel RLE
                                       2 = 4-bits/pixel RLE
      DWORD           biSizeimage      Size of bitmap bits (bytes)
                                        (required if compression used)
      DWORD           biXPelsPerMeter  Horizontal resolution
                                        (pixels/meter)
      DWORD           biYPelsPerMeter  Vertical resolution
                                        (pixels/meter)
      DWORD           biClrUsed        Colors used
      DWORD           biClrImportant   Number of important colors

The PCX Class

The PCX class is defined in SHOWPCX. CPP (see Listing One, page 97) decodes and displays PCX files in Windows. It has three main user methods: Read; Display; and Handle. PCX::Read opens the PCX file and reads its 128-byte header (see the text box entitled "The PCX Header"). From the header, PCX::Read decides if it should create a monochrome, 16-color, or 256-color decoder. The decoder creates a device-independent bitmap and stores the bitmap handle in a private variable, hBitmap. A call to PCX::Display will put the DIB into a Windows display context. If you need to obtain the handle for the DIB, use PCX::Handle.

The PCX class knows little about the format of PCX images. It leaves the hard work to the DecodePCX class. DecodePCX defines the read_pcx_line method, which decompresses a scanline from the the PCX file. But you can't make a DecodePCX object because it is an abstract class. That is, DecodePCX declares but does not define a MakeDIB method. DecodePCX is used by deriving child classes that each define a MakeDIB method. These children are Decode-MonoPCX, Decode16PCX, and Decode-256PCX. Each MakeDIB method decodes and creates a different type of bitmap.

Decoding Monochrome Images

Creating a monochrome DIB from a PCX file is straightforward. The method DecodeMonoPCX::MakeDIB in Listing One creates the monochrome DIB. Most of the code is dedicated to setting up the header. To create the DIB header, first create a BITMAPINFO structure. This structure contains a variable size color table. The size of this color table depends on the image type. For a monochrome bitmap, the size of the BITMAPINFO header is: sizeof (BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD). There are two RGBQUAD entries in the color table, one for white and another for black.

Some small but important differences exist between DIB and PCX images. The length of each DIB scanline is always a multiple of 4 bytes, but the length of a PCX scanline can be a multiple of 2 bytes. The ALIGN_DWORD macro compensates for this difference. This macro is used to calculate the size of the image buffer needed by the DIB: image_size = ALIGN_DWORD (BytesLine) * height;. In this statement, BytesLine is the number of bytes in each PCX scanline, and height is the number of scanlines in the image.

After allocating memory for the image buffer, a loop is used to read each line of the PCX file until the image is complete. The origin of DIB starts at the lower-left corner of the image, not the upper-left corner as in PCX files. So the first line read from the PCX file is copied into the last line of the DIB. Once the bitmap image and header information is filled in, a call to Window's Create-DIBitmap creates the bitmap.

Decoding 16-Color Images

Translating a monochrome PCX file to a Windows DIB is relatively clean because their image formats are similar. Unfortunately, translating 16-color DIBs requires some bit twiddling. Four bits per pixel are used for 16-color DIBs, but PCX files are arranged with 1 bit per pixel and four interleaving color scanlines. Decode 16PCX::MakeDIB reads groups of four PCX scanlines, then tests a bit from each scanline to create the appropriate 4-bit representation of a DIB pixel. This requires significantly more processing than the monochrome decoder.

There are 16 entries in the color table. This table is filled with the palette copied from the PCX header. If the PCX file has no palette information, the decoder uses literal RGB values.

Decoding 256-Color Images

The MCGA/VGA can display 256 colors out of a palette of 16 million at 320 x 200 resolution. However, Windows does not come with a display driver for this mode. If you want to see Windows in 256 colors, you'll need more capable graphics hardware (such as a Super VGA or 8514/A).

More Details.

As with the monochrome images, translating 256-color PCX images into Windows DIB bits is elementary. Both images use an 8-bits-per-pixel format. Therefore, the decompressed bits from the PCX file are simply copied into the DIB image buffer.

More Details.

Color Palettes

A PCX file uses a maximum of 256 colors out of a palette of approximately 16 million colors. And as just mentioned, display adapters such as the 8514/A can display 256 colors simultaneously out of a possible 16 million. When you display a 256-color image, you want the system to use the palette specified by your image. In a multitasking environment like Windows, this can be a detriment to other programs that have their own palettes. Fortunately, Windows' palette manager can be employed to settle these conflicts.

In order to accommodate programs that have important color information, Windows allows each program to create its own logical palette. Logical palettes allow an application to use as many colors as required, with minimal interference to colors used by other applications. When an application is active, Windows will satisfy its palette requests by exactly matching the system palette to the logical palette. Windows can also approximate logical palettes for inactive windows by mapping those colors to the closest colors in the current system palette.

Decode 256PCX creates a Windows logical palette out of the PCX color palette. Unlike the 16-color palette, PCX does not keep 256-color palettes in the header. This palette is stored as 256 red, green, and blue bytes at the end of the PCX file. To get the extended palette, go to the 769th byte from the end of the PCX file. This byte should contain a OCh to verify the start of the palette. The actual palette entries follow.

Decode 256PCX::MakeDIB passes the PCX palette to make_palette, which creates a Windows logical palette. Before creating the bitmap, you must notify Windows of the new palette. A call to SelectPalette identifies the logical palette for the device context. RealizePalette tells Windows to map the system palette into the logical palette.

The File Viewer

After the decoder is implemented, we need only to add a few lines of code to our main Windows template to complete the file viewer (see PCXWIN.CPP in Listing Two, page 100 and PCXWIN.H in Listing Three, page 101). The main C++ Windows module is written similar to a main C Windows module. I did not encapsulate WinMain or WndProc into a C++ class because this does not simplify the main Windows message loop for me. A notable C++ feature used in the main module is the Scroller class (SCROLLER.CPP, Listing Four, page 101), which encapsulates all scrollbar-related functions and variables so that the main module is not littered with global variables. Scroller::Size notifies Scroller when the size of the window has changed. The methods Scroller::Horz and Scroller:: Vert handle the window's horizontal and vertical scrolling, respectively.

The main module also handles a Windows palette message. The WM_QUERY-NEWPALETTE message is received from Windows just before the application becomes active. This allows it to realize its logical palette. Windows also sends another message, WM_PALETTECHANGED, just after the system palette has changed. This message should be processed if you want Windows to approximate your palette colors when the window is in the background. WM_PALETTECHANGED can be ignored, however, if you care about the appearance of your window only when it is active.

A Word about Tools

The PCXWIN.MAK make file (see Listing Seven, page 102) was created from a Borland Project file using the PRJ2MAK utility. If you prefer to compile the viewer under the Borland Programmer's Platform, just create a project containing the CPP, DEF, and RC files, select the Window App option from the Options/Applications menu, and compile. (The DEF and RC files for this project are presented in Listings Five and Six, page 101, respectively.) The - =PCXWIN.SYM option tells Borland C++ to precompile the header files to a binary symbol file. You can remove this from the make file, but I strongly recommend you use it for your own projects. Much of the work in compiling a Windows program is recompiling the WINDOWS.H header file.

Most C++ programs include numerous class declarations in header files. In addition, C++ syntax is more difficult to parse than C syntax, heavily penalizing compiler time. When changes are made to a source module, Borland C++ checks the time stamp on the header files against the precompiled header symbol file. If the headers have not been changed, the symbol file is loaded without the need to parse the header file source. This brings the lines necessary to recompile PCXWIN.CPP (Listing Two) from 3800 lines down to around 250 lines, which just about eliminates the programmer's coffee break. Notice, too, that PCXWIN.DEF (Listing Five) does not contain any EXPORT functions. C++ function name mangling makes exporting linker names difficult. The solution is to use the _export type qualifier in the function definitions.

Also note that the file viewer works in Windows standard or enhanced mode because Borland C++ does not support real-mode programs. The PRODMODE statement in PCXWIN.DEF tells the linker to tag the file viewer as a protected-mode program. Windows real-mode compatibility may be a critical issue for some, but there is a Windows programmers' movement to do away with real mode completely. The debate continues on the importance of Windows real-mode compatibility.

Possible Enhancements

I took a minimalist approach to the file viewer. It's really only a test program for the PCX class. But adding features to this application could be done very cheaply. Because the PCX class creates a Windows bitmap, clipboard support was added simply by using SetClip-boardData in the main module. With minimum effort, you can add a file dialog box, printing capability, and so on. If you have a little more time, you can also add color dithering to allow 256-color images to be viewed on standard VGA displays. But as one of my college professors was fond of saying, I'll leave the rest to you as an exercise.

References

Microsoft Windows Software Development Kit Reference Manual. Redmond, Wash.: Microsoft Press.

PC Paintbrush Technical Reference Manual. Marietta, Ga.: ZSoft Corp.

Petzold, Charles. Programming Windows. Redmond, Wash.: Microsoft Press, 1990.

The PCX Header

PCX files start with a 128-byte header. The PCXHEADER structure is defined in Listing One. The pcxManufacturer field is always \xA. This is a "magic number" used to identify a PCX file. The pcxVersion identifies what version of PC Paintbrush or compatible program created this file. The version ID is shown in Table 1.

Table 1: PCX version ID

  ID  PC Paintbrush Version
  -------------------------

  0   2.5
  2   2.8 with palette
  3   2.8 without palette
  5   3.0

The pcxEncoding field should have the value 1. At this time, run length encoding is the only scheme PCX files use. The pcxBitsPerPixel field tells you how many consecutive bits in the file represent one pixel on the screen (see Table 2).

Table 2: Bits per pixel

Bits/Pixel  Colors (Display Mode)
---------------------------------

  1         Monochrome, EGA/VGA
             16 color
  2         CGA 4 color
  8         MCGA/VGA 256 color

The next four fields (pcxXmin, pcxYmin, pcxXmax, and pcxYmax) define the limits of the image. The dimensions of the image will be pcxXmax-pcxXmin+1 by pcxYmax-pcxYmin+1. The lower limits of pcxXmin and pcxYmin are usually both 0.

The pcxHres and pcxVres fields specify the resolution of the screen that was used to create the file. These fields can be ignored. The pcxPalette field contains the color palette. The pcxReserved field usually contains the BIOS video mode but is not documented as such. It should be ignored.

The pcxPlanes field says how many hardware color planes the target video mode has. PCX files store this information by interleaving color scanlines. For example, the beginning of a PCX image in EGA 16-color mode appears in Figure 3. The image has two blue pixels, three green pixels, two red pixels, and one magenta (red+blue) pixel. The image is 640 pixels (80 bytes = 640 pixels/8 bytes/pixel) wide. The actual number of bytes in the file per scanline is probably less than 80 bytes because of compression.

The pcxBytesPerLine field tells you how many bytes are in each scanline. This value will depend on the bits per pixel and the x dimensions of the image. This field is always even. The pcxPaletteInfo field is for VGA images. The value is 1 for grayscale and 2 for color images.

The rest of the 128-byte header is reserved for future additions to the PCX format. The actual bits of the PCX bitmap immediately follow the 128-byte header.

--P.C.

Windows Device-Dependent Bitmaps

Unlike GIF, which was designed as a generic graphics format, PCX file formats have evolved by closely paralleling changing PC graphics hardware. Decompressed PCX images are basically copies of the video display buffer. This is also true of Windows Device-Dependent Bitmaps (DDBs).

For monochrome images, device dependence is not a problem. All monochrome bitmap devices use a single bit to represent each pixel on the image. Unlike the obvious solution used for monochrome images, the structure of color images is not a simple matter of black and white (excuse the pun). Video hardware designers have come up with ways to stuff as many colors into as few bytes as possible. Pity the poor programmer. For example, the EGA 16-color video mode uses 1 bit per pixel and four hardware "color planes" (red, green, blue, intensity). Combinations of the bits from each plane are used to create 16 possible colors. PCX files and Windows DDBs emulate color planes by storing them as interleaving color scanlines. MC-GA/VGA 256-color modes don't have color planes, but use 8 bits per pixel. Not by coincidence, PCX files use 8 bits per pixel for 256-color images.

DDBs and PCX files have the same basic image format. EGA 16-color DDBs have the same color scanline interlacing as the PCX format. This makes translating from a PCX file directly to a DDB undeservedly easy. The process involves unpacking the RLE for each line in the PCX file and then setting the unpacked bits directly to the DDB; see Example 1 . The function read_pcx_line decodes a single scanline from the PCX file. The same code constructs both monochrome and 16-color images. For 16-color images, the number of color planes (byPlanes) is 4. The image is monochrome if the number of color planes is 1.

Example 1: Setting unpacked bits directly to the DDB

  BYTE huge* lpImage = new BYTE[lImageSize];
  int h, line, plane;
  for (h=0, line=0; h<wHeight; ++h, line+=byPlanes)
      for (plane=0; plane<byPlanes; ++plane)
          read_pcx_line(lpImage+(lBmpBytesLine*(line+plane)));
  HBITMAP hBitmap = CreateBitmap (wWidth, wHeight, byPlanes, 1, lpImage);

This seems too good to be true. Here's the curve: DDBs interlace color scanlines for small bitmaps, but if they're greater than 65,535 bytes, color planes are interlaced. So the revised code fragment should look something like Example 2.

--P.C.

Example 2: Revising the code in Example 1 for bitmaps greater than 65,535 bytes

    BYTE huge* lpImage = new BYTE[lImageSize];
    int h, line, plane;
    if (lImageSize < 65535L)
    // Interlaced color scanlines
    for (h=0, line=0; h<wHeight; ++h, line+=byPlanes)
        for (plane=0; plane<byPlanes; ++plane)
  read_pcx_line(lpImage+(LONG(iBmpBytesLine)*(line+plane)));
    else
    // Interlaced color planes
    for (h=0, line=0; h<wHeight; ++h, line+=wHeight)
        for (plane=0; plane<byPlanes; ++plane)
           read_pcx_line (lpImage+(lBmpBytesLine*(plane*wHeight+h)));
    HBITMAP hBitmap = CreateBitmap (wWidth, wHeight, byPlanes, 1,
  lpImage);


_A C++ PCX FILE VIEWER FOR WINDOWS 3_
by Paul Chui


[LISTING ONE]
<a name="0198_001a">

#include <windows.h>
#include "pcxwin.h"

#include <io.h>

#define ALIGN_DWORD(x) (((x)+3)/4 * 4)

struct PCXRGB { BYTE r, g, b; };

struct PCXHEADER {
        BYTE     pcxManufacturer;
        BYTE     pcxVersion;
        BYTE     pcxEncoding;
        BYTE     pcxBitsPixel;
        int      pcxXmin, pcxYmin;
        int      pcxXmax, pcxYmax;
        int      pcxHres, pcxVres;
        PCXRGB   pcxPalette[16];
        BYTE     pcxReserved;
        BYTE     pcxPlanes;
        int      pcxBytesLine;
        int      pcxPaletteInfo;
        BYTE     pcxFiller[58];
};

///////////////////////////////////////////////////////////////////////////
// NOTES: Decoder creates a DIB and possibly a PALETTE, but does not delete
// either. It is the responsibility of the Decoder's user to delete them.
///////////////////////////////////////////////////////////////////////////
class DecodePCX {
public:
        DecodePCX(int hfile, PCXHEADER& pcxHeader);
virtual HBITMAP  MakeDIB(HDC hdc) = 0;
        HPALETTE Palette();
protected:
        WORD     read_pcx_line(BYTE huge* pLine);
        BOOL NEAR PASCAL next_data();

        int      hFile;               // Handle to the open PCX file
        HPALETTE hPalette;            // Handle to Palette

        PCXHEADER header;
        int      BytesLine;           // Bytes/Line in PCX file
        WORD     width;               // width in pixels
        WORD     height;              // height in scan lines

        BYTE     byData;              // Current data byte
        int      iDataBytes;          // Current unread data buffer size
};

HPALETTE DecodePCX::Palette() { return hPalette; }
class DecodeMonoPCX : public DecodePCX {
public:
        DecodeMonoPCX(int hfile, PCXHEADER& pcxHeader) :
            DecodePCX(hfile, pcxHeader) { }
        HBITMAP  MakeDIB(HDC hdc);
};
class Decode16PCX: public DecodePCX {
public:
        Decode16PCX(int hfile, PCXHEADER& pcxHeader) :
            DecodePCX(hfile, pcxHeader) { }
        HBITMAP  MakeDIB(HDC hdc);
};
class Decode256PCX: public DecodePCX {
public:
        Decode256PCX(int hfile, PCXHEADER& pcxHeader) :
            DecodePCX(hfile, pcxHeader) { }
        HBITMAP  MakeDIB(HDC hdc);
private:
        HPALETTE make_palette(RGBQUAD* pColors);
};

///////////////////////////////////////////////////////////////////////////
// PCX Methods
///////////////////////////////////////////////////////////////////////////
PCX::PCX()
{
    hBitmap     = 0;
    hPalette    = 0;
    hFile       = 0;

    wWidth      = 0;
    wHeight     = 0;
}
PCX::~PCX()
{
    if (hBitmap) DeleteObject(hBitmap);
    if (hPalette) DeleteObject(hPalette);
}

/*************************************************************************
    METHOD: BOOL PCX::Read(LPSTR lpszFileName, HDC hdc)
    PURPOSE: Creates a DIB from a PCX file
    PARAMETERS: LPSTR lpszFileName    PCX file name
                HDC   hdc             A compatible DC for the DIB
    RETURN: TRUE if DIB was created, otherwise FALSE
    NOTES: ZSoft documents a CGA palette type that is not support here.
*************************************************************************/
BOOL PCX::Read(LPSTR lpszFileName, HDC hdc)
{
    // Delete the bitmap and reset variables
    if (hBitmap)
    {
        DeleteObject(hBitmap);
        hBitmap = 0;            // So we know the bitmap has been deleted
    }
    if (hPalette)
    {
        DeleteObject(hPalette);
        hPalette = 0;           // So we know the palette has been deleted
    }
    wWidth  = 0;
    wHeight = 0;
    OFSTRUCT OfStruct;
    if ((hFile=OpenFile(lpszFileName, &OfStruct, OF_READ)) == -1)
    {
        ErrorMessage("Unable to open file.");
        return FALSE;
    }
    PCXHEADER header;
    if (_lread(hFile,(LPSTR)&header,sizeof(PCXHEADER)) != sizeof(PCXHEADER))
    {
        ErrorMessage("Error reading PCX file header.");
        return FALSE;
    }
    if(header.pcxManufacturer != 0x0a)
    {
        _lclose(hFile);
        ErrorMessage("Not a PCX file.");
        return FALSE;
    }
    wWidth  = header.pcxXmax - header.pcxXmin + 1;
    wHeight = header.pcxYmax - header.pcxYmin + 1;

    DecodePCX* Decoder;

    /* Determine PCX file type and create a decoder */

    // 256-color file
    if (header.pcxBitsPixel == 8 && header.pcxPlanes == 1)
        Decoder = new Decode256PCX(hFile, header);
    else
    // 16-color file
    if (header.pcxBitsPixel == 1 && header.pcxPlanes == 4)
        Decoder = new Decode16PCX(hFile, header);
    else
    // monochrome file
    if (header.pcxBitsPixel == 1 && header.pcxPlanes == 1)
        Decoder = new DecodeMonoPCX(hFile, header);
    else
        ErrorMessage("Unsupported PCX format.");

    if (!Decoder)
    {
        ErrorMessage("Cannot create PCX decoder.");
        _lclose(hFile);
        return FALSE;
    }
    SetCursor( LoadCursor(NULL,IDC_WAIT) );
    // Create the bitmap
    hBitmap = Decoder->MakeDIB(hdc);
    hPalette = Decoder->Palette();
    SetCursor( LoadCursor(NULL,IDC_ARROW) );
    delete Decoder;
    _lclose(hFile);
    return (hBitmap) ? TRUE : FALSE;
}

/*************************************************************************
    METHOD: BOOL PCX::Display(HDC hdc, POINT& pos, RECT& rect)
    PURPOSE: Displays the DIB
    PARAMETERS: HDC   hdc             DC on which DIB is displayed
                POINT pos             Destination positions
                RECT  rect            Clipping rectangle on source
    RETURN: TRUE if DIB was displayed, otherwise FALSE
    NOTES: Works for MM_TEXT mode only
*************************************************************************/
BOOL PCX::Display(HDC hdc, POINT& pos, RECT& rect)
{
    BOOL bltOk = FALSE;
    if (hBitmap)
    {
        HBITMAP hdcBitmap  = CreateCompatibleDC(hdc);
        HBITMAP hOldBitmap = SelectObject(hdcBitmap, hBitmap);
        bltOk = BitBlt(hdc, rect.left,rect.top,rect.right,rect.bottom,
                                             hdcBitmap,pos.x,pos.y, SRCCOPY);
        SelectObject(hdcBitmap, hOldBitmap);
        DeleteDC(hdcBitmap);
    }
    return bltOk;
}

///////////////////////////////////////////////////////////////////////////
// DecodePCX Methods
///////////////////////////////////////////////////////////////////////////

/*************************************************************************
    METHOD: DecodePCX::DecodePCX(int hfile, PCXHEADER& pcxHeader)
    PURPOSE: Constructor
    PARAMETERS: int       hfile            Handle to open PCX file
                PCXHEADER pcxHeader        PCX header
*************************************************************************/
DecodePCX::DecodePCX(int hfile, PCXHEADER& pcxHeader)
{
    hFile = hfile;
    // Reset file pointer
    if (_llseek(hFile, sizeof(PCXHEADER), 0) == -1)
        ErrorMessage("Error positioning past header.");
    header      = pcxHeader;
    hPalette    = 0;
    BytesLine   = header.pcxBytesLine;
    width       = header.pcxXmax - header.pcxXmin + 1;
    height      = header.pcxYmax - header.pcxYmin + 1;
    byData      = 0;
    iDataBytes  = 0;
}

/*************************************************************************
    METHOD: WORD DecodePCX::read_pcx_line(BYTE huge* lpLineImage)
    PURPOSE: Decode a PCX RLE scanline
    PARAMETERS: BYTE huge* lpLineImage   Destination of decoded scanline
    RETURN: Number of bytes decoded
*************************************************************************/
WORD DecodePCX::read_pcx_line(BYTE huge* lpLineImage)
{
    for (WORD n=0; n<BytesLine; )
    {
        if (!next_data()) return n;
        // If the two high bits are set...
        if (byData >= 0xc0)
        {
            // Get duplication count from lower bits
            BYTE run_len = byData & 0x3f;
            // Set run_len bytes
            if (!next_data()) return n;
            while(run_len--) lpLineImage[n++]=byData;
        }
        else
            // Set this byte
            lpLineImage[n++] = byData;
    }
    if (n != BytesLine)
        ErrorMessage("PCX Read Error.");
    return n;
}

/**************************************************************************
    METHOD: BOOL NEAR PASCAL DecodePCX::next_data()
    PURPOSE: Read a byte from the file and set to byData
    RETURN: FALSE on read error
    NOTES: The output byte is written to byData
**************************************************************************/
BOOL NEAR PASCAL DecodePCX::next_data()
{
    static BYTE  fileBuf[5120];
    static int   index = 0;
    if (iDataBytes == 0)
    {
        if ((iDataBytes = _read(hFile, fileBuf, sizeof(fileBuf))) <= 0)
            return FALSE;
        index = 0;
    }
    --iDataBytes;
    byData = *(fileBuf+(index++));
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// DecodeMonoPCX Methods
///////////////////////////////////////////////////////////////////////////

/**************************************************************************
    METHOD: HBITMAP DecodeMonoPCX::MakeDIB(HDC hdc)
    PURPOSE: Make monochrome DIB
    PARAMETERS: HDC hdc           Handle to compatible DC
    RETURNS: Handle to DIB, NULL on error
**************************************************************************/
HBITMAP DecodeMonoPCX::MakeDIB(HDC hdc)
{
    int h;
    LONG lDIBBytesLine = ALIGN_DWORD(BytesLine);
    LONG image_size = lDIBBytesLine*height;
    // Allocate memory for the image buffer
    GlobalCompact(image_size);
    HANDLE hImageMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, image_size);
    if (!hImageMem)
    {
        ErrorMessage("Out of memory."); return NULL;
    }
    BYTE huge* lpImage = (BYTE huge*) GlobalLock(hImageMem);
    if (!lpImage)
    {
        ErrorMessage("Memory error.");  return NULL;
    }
    for (h=height-1; h>=0; --h)
        read_pcx_line(lpImage+(lDIBBytesLine*h));
    // Create the DIB header
    PBITMAPINFO pBmi = (PBITMAPINFO)
        new BYTE[ sizeof(BITMAPINFOHEADER)+2*sizeof(RGBQUAD) ];
    if (!pBmi)
    {
        ErrorMessage("Out of memory.");
        GlobalUnlock(hImageMem);
        GlobalFree(hImageMem);
        return NULL;
    }
    PBITMAPINFOHEADER pBi = &pBmi->bmiHeader;
    pBi->biSize          = sizeof(BITMAPINFOHEADER);
    pBi->biWidth         = width;
    pBi->biHeight        = height;
    pBi->biPlanes        = 1;
    pBi->biBitCount      = 1;
    pBi->biCompression   = 0L;
    pBi->biSizeImage     = 0L;
    pBi->biXPelsPerMeter = 0L;
    pBi->biYPelsPerMeter = 0L;
    pBi->biClrUsed       = 0L;
    pBi->biClrImportant  = 0L;
    // Copy PCX Palette into DIB color table
    pBmi->bmiColors[0].rgbBlue     = header.pcxPalette[0].b;
    pBmi->bmiColors[0].rgbGreen    = header.pcxPalette[0].g;
    pBmi->bmiColors[0].rgbRed      = header.pcxPalette[0].r;
    pBmi->bmiColors[1].rgbBlue     = header.pcxPalette[1].b;
    pBmi->bmiColors[1].rgbGreen    = header.pcxPalette[1].g;
    pBmi->bmiColors[1].rgbRed      = header.pcxPalette[1].r;
    HBITMAP hBitmap = CreateDIBitmap(hdc, pBi, CBM_INIT,
                                        (LPSTR)lpImage, pBmi, DIB_RGB_COLORS);
    delete pBmi;
    // Free image buffer
    GlobalUnlock(hImageMem);
    GlobalFree(hImageMem);
    return hBitmap;
}

///////////////////////////////////////////////////////////////////////////
// Decode16PCX Methods
///////////////////////////////////////////////////////////////////////////

/**************************************************************************
    METHOD: HBITMAP Decode16PCX::MakeDIB(HDC hdc)
    PURPOSE: Make 16-color DIB
    PARAMETERS: HDC hdc           Handle to compatible DC
    RETURNS: Handle to DIB, NULL on error
**************************************************************************/
HBITMAP Decode16PCX::MakeDIB(HDC hdc)
{
    LONG lDIBBytesLine = ALIGN_DWORD( (width+1)/2 );
    LONG image_size = lDIBBytesLine*height;
    // Allocate memory for the image buffer
    GlobalCompact(image_size);
    HANDLE hImageMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, image_size);
    if (!hImageMem)
    {
        ErrorMessage("Out of memory."); return NULL;
    }
    BYTE huge* lpImage = (BYTE huge*) GlobalLock(hImageMem);
    if (!lpImage)
    {
        ErrorMessage("Memory error.");  return NULL;
    }
    // 16 color PCX files interleve scanlines for each color
    BYTE *npPlane[4];
    for (int h=0; h<4; ++h)
        npPlane[h] = new BYTE[BytesLine];
    if (!npPlane[0] || !npPlane[1] || !npPlane[2] || !npPlane[3])
    {
        GlobalUnlock(hImageMem);
        GlobalFree(hImageMem);
        return NULL;
    }
    // 16 color DIB bitmaps have 4 bits per pixel
    for (h=height-1; h>=0; --h)
    {
        read_pcx_line(npPlane[0]);
        read_pcx_line(npPlane[1]);
        read_pcx_line(npPlane[2]);
        read_pcx_line(npPlane[3]);
        LONG l = (LONG) h * lDIBBytesLine;
        for (int m=0; m<BytesLine; ++m)
        {
            BYTE r = npPlane[0][m];
            BYTE g = npPlane[1][m];
            BYTE b = npPlane[2][m];
            BYTE i = npPlane[3][m];
            // Combine a bit from each 4 scan lines into a 4-bit nibble
            BYTE nibbles = 0;
            for (int k=0; k<4; ++k)
            {
                nibbles = 0;
                // If the most significant bit is set...
                // Set the appropriate bit in the higher order nibble
                if (r & '\x80') nibbles |= 0x10;
                if (g & '\x80') nibbles |= 0x20;
                if (b & '\x80') nibbles |= 0x40;
                if (i & '\x80') nibbles |= 0x80;

                r<<=1; g<<=1; b<<=1; i<<=1;
                // Repeat for the lower order nibble
                if (r & '\x80') nibbles |= 0x01;
                if (g & '\x80') nibbles |= 0x02;
                if (b & '\x80') nibbles |= 0x04;
                if (i & '\x80') nibbles |= 0x08;
                r<<=1; g<<=1; b<<=1; i<<=1;
                *(lpImage + l++) = nibbles;
            }
        }
    }
    for (h=0; h<4; ++h)
        delete npPlane[h];
    // Create the DIB header
    PBITMAPINFO pBmi = (PBITMAPINFO)
       new BYTE[ sizeof(BITMAPINFOHEADER)+16*sizeof(RGBQUAD) ];
    if (!pBmi)
    {
        ErrorMessage("Out of memory.");
        GlobalUnlock(hImageMem);
        GlobalFree(hImageMem);
        return NULL;
    }
    PBITMAPINFOHEADER pBi = &pBmi->bmiHeader;
    pBi->biSize          = sizeof(BITMAPINFOHEADER);
    pBi->biWidth         = width;
    pBi->biHeight        = height;
    pBi->biPlanes        = 1;
    pBi->biBitCount      = 4;
    pBi->biCompression   = 0L;
    pBi->biSizeImage     = 0L;
    pBi->biXPelsPerMeter = 0L;
    pBi->biYPelsPerMeter = 0L;
    pBi->biClrUsed       = 0L;
    pBi->biClrImportant  = 0L;
    if (header.pcxVersion == 3)
    // No PCX palette, use literal color values
    {
        DWORD* clrTab = (DWORD*)pBmi->bmiColors;
        clrTab[0]  = 0x000000L;
        clrTab[1]  = 0x000080L;
        clrTab[2]  = 0x008000L;
        clrTab[3]  = 0x008080L;
        clrTab[4]  = 0x800000L;
        clrTab[5]  = 0x800080L;
        clrTab[6]  = 0x808000L;
        clrTab[7]  = 0x808080L;
        clrTab[8]  = 0xc0c0c0L;
        clrTab[9]  = 0x0000ffL;
        clrTab[10] = 0x00ff00L;
        clrTab[11] = 0x00ffffL;
        clrTab[12] = 0xff0000L;
        clrTab[13] = 0xff00ffL;
        clrTab[14] = 0xffff00L;
        clrTab[15] = 0xffffffL;
    }
    else
    // Copy PCX palette to DIB color table
    {
        for (int i=0; i<16; ++i)
        {
            pBmi->bmiColors[i].rgbBlue     = header.pcxPalette[i].b;
            pBmi->bmiColors[i].rgbGreen    = header.pcxPalette[i].g;
            pBmi->bmiColors[i].rgbRed      = header.pcxPalette[i].r;
            pBmi->bmiColors[i].rgbReserved = 0;
        }
    }
    HBITMAP hBitmap = CreateDIBitmap(hdc, pBi, CBM_INIT,
                                        (LPSTR)lpImage, pBmi, DIB_RGB_COLORS);
    delete pBmi;
    // Free image buffer
    GlobalUnlock(hImageMem);
    GlobalFree(hImageMem);
    return hBitmap;
}

///////////////////////////////////////////////////////////////////////////
// Decode256PCX Methods
///////////////////////////////////////////////////////////////////////////

/**************************************************************************
    METHOD: HBITMAP Decode256PCX::MakeDIB(HDC hdc)
    PURPOSE: Make 256-color DIB
    PARAMETERS: HDC hdc           Handle to compatible DC
    RETURNS: Handle to DIB, NULL on error
**************************************************************************/
HANDLE Decode256PCX::MakeDIB(HDC hdc)
{
    LONG lDIBBytesLine = ALIGN_DWORD(BytesLine);
    LONG image_size = lDIBBytesLine*height;
    // Allocate memory for the image buffer
    GlobalCompact(image_size);
    HANDLE hImageMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, image_size);
    if (!hImageMem)
    {
        ErrorMessage("Out of memory."); return NULL;
    }
    BYTE huge* lpImage = (BYTE huge*) GlobalLock(hImageMem);
    if (!lpImage)
    {
        ErrorMessage("Memory error.");  return NULL;
    }
    for (int h=height-1; h>=0; --h)
        read_pcx_line(lpImage+(lDIBBytesLine*h));
    // Create the DIB header
    PBITMAPINFO pBmi = (PBITMAPINFO)
        new BYTE[ sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD) ];
    if (!pBmi)
    {
        ErrorMessage("Out of memory.");
        GlobalUnlock(hImageMem);
        GlobalFree(hImageMem);
        return NULL;
    }
    PBITMAPINFOHEADER pBi = &pBmi->bmiHeader;
    pBi->biSize          = sizeof(BITMAPINFOHEADER);
    pBi->biWidth         = width;
    pBi->biHeight        = height;
    pBi->biPlanes        = 1;
    pBi->biBitCount      = 8;
    pBi->biCompression   = 0L;
    pBi->biSizeImage     = 0L;
    pBi->biXPelsPerMeter = 0L;
    pBi->biYPelsPerMeter = 0L;
    pBi->biClrUsed       = 0L;
    pBi->biClrImportant  = 0L;
    // Look for the palette at the end of the file
    if (_llseek(hFile, -769L, 2) == -1)
        ErrorMessage("Error seeking palette.");
    // It should start with a 0Ch byte
    BYTE Id256Pal;
    if (!(_read(hFile, &Id256Pal, 1) == 1 && Id256Pal == '\xc'))
        ErrorMessage("No palette found.");
    PCXRGB* PalPCX = new PCXRGB[256];
    if (_read(hFile, PalPCX, 768) != 768)
        ErrorMessage("Error reading palette.");
    // Copy PCX palette to DIB color table
    for (int i=0; i<256; ++i)
    {
        pBmi->bmiColors[i].rgbBlue     = PalPCX[i].b;
        pBmi->bmiColors[i].rgbGreen    = PalPCX[i].g;
        pBmi->bmiColors[i].rgbRed      = PalPCX[i].r;
        pBmi->bmiColors[i].rgbReserved = 0;
    }
    delete PalPCX;
    if (hPalette)
        DeleteObject(hPalette);
    // Create and set logical palette
    if ((hPalette = make_palette(pBmi->bmiColors)) != NULL)
    {
        SelectPalette(hdc, hPalette, 0);
        RealizePalette(hdc);
    }
    else
    {
        ErrorMessage("Cannot create palette");
    }
    HBITMAP hBitmap = CreateDIBitmap(hdc, pBi, CBM_INIT,
                                        (LPSTR)lpImage, pBmi, DIB_RGB_COLORS);
    delete pBmi;
    // Free image buffer
    GlobalUnlock(hImageMem);
    GlobalFree(hImageMem);
    return hBitmap;
}

/**************************************************************************
    METHOD: HPALETTE Decode256PCX::make_palette(RGBQUAD* pColors)
    PURPOSE: Make 256-color Logical Palette
    PARAMETERS: RGBQUAD[256] pColors       Palette colors
    RETURNS: Handle to Palette, NULL on error
**************************************************************************/
HPALETTE Decode256PCX::make_palette(RGBQUAD* pColors)
{
    if (!pColors)
        return NULL;
    PLOGPALETTE pPal = (PLOGPALETTE)
        new BYTE[ sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
    if (!pPal)
        return NULL;
    pPal->palNumEntries = 256;
    pPal->palVersion    = 0x300;
    for (int i=0; i<256; ++i)
    {
        pPal->palPalEntry[i].peRed   = pColors[i].rgbRed;
        pPal->palPalEntry[i].peGreen = pColors[i].rgbGreen;
        pPal->palPalEntry[i].peBlue  = pColors[i].rgbBlue;
        pPal->palPalEntry[i].peFlags = 0;
    }
    HPALETTE hPal = CreatePalette(pPal);
    delete pPal;
    return hPal;
}





<a name="0198_001b">
<a name="0198_001c">
[LISTING TWO]
<a name="0198_001c">

#include <windows.h>
#include "pcxwin.h"

#include <stdlib.h>

static char szAppName[] = "PCXWIN";

#define MAXPATH 80
static char szFileName[MAXPATH+1] = "";
static char szUntitled[] = "PCXWIN - (Untitled)";

// Function Prototypes
int DoKeyDown(HWND hwnd, WORD wVkey);
int DoFileOpenDlg(HANDLE hInst, HWND hwnd);

LONG FAR PASCAL _export WndProc(HWND hwnd, WORD message,
                                                     WORD wParam, LONG lParam);
BOOL FAR PASCAL _export FileOpenDlgProc(HWND hDlg, WORD message,
                                                     WORD wParam, LONG lParam);
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR, int nCmdShow)
{
    if (!hPrevInstance)
    {
        WNDCLASS wndclass;
        wndclass.style          = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc    = WndProc;
        wndclass.cbClsExtra     = 0;
        wndclass.cbWndExtra     = 0;
        wndclass.hInstance      = hInstance;
        wndclass.hIcon          = LoadIcon(hInstance, "PCXWIN");
        wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground  = GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName   = "PCXWIN";
        wndclass.lpszClassName  = szAppName;
        RegisterClass(&wndclass);
    }
    HWND hwnd = CreateWindow(
                    szAppName, szUntitled,
                    WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    NULL, NULL, hInstance, NULL
                );
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    HANDLE hAccel = LoadAccelerators(hInstance, szAppName);
    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(hwnd, hAccel, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return msg.wParam;
}
LONG FAR PASCAL _export WndProc(HWND hwnd, WORD message,
                                                     WORD wParam, LONG lParam)
{
    static HANDLE hInst;
    static PCX* pcx;
    static Scroller* scroll;
    HDC    hdc;
    switch(message)
    {
        case WM_CREATE :
            hInst = ((LPCREATESTRUCT) lParam)->hInstance;
            pcx    = new PCX;
            scroll = new Scroller(hwnd);
            return 0L;
        case WM_DESTROY :
            delete pcx;
            delete scroll;
            PostQuitMessage(0);
            return 0L;
        case WM_PAINT :
            PAINTSTRUCT ps;
            hdc = BeginPaint(hwnd, &ps);
            RECT rcClient;
            GetClientRect(hwnd, &rcClient);
            pcx->Display(hdc, scroll->Pos(), rcClient);
            EndPaint(hwnd, &ps);
            return 0L;
        case WM_QUERYNEWPALETTE:
            if (pcx->Palette())
            {
                hdc = GetDC(hwnd);
                SelectPalette(hdc, pcx->Palette(), 0);
                BOOL b = RealizePalette(hdc);
                ReleaseDC(hwnd, hdc);
                if (b)
                {
                    InvalidateRect(hwnd, NULL, 1);
                    return 1L;
                }
            }
            return 0L;
        case WM_SIZE :
            scroll->Size(pcx->Size());
            return 0L;
        case WM_VSCROLL :
            scroll->Vert(wParam, LOWORD(lParam));
            return 0L;
        case WM_HSCROLL :
            scroll->Horz(wParam, LOWORD(lParam));
            return 0L;
        case WM_KEYDOWN :
            return DoKeyDown(hwnd, wParam);
        case WM_COMMAND :
            switch (wParam)
            {
                case IDM_OPEN :
                    if (DoFileOpenDlg(hInst, hwnd))
                    {
                        hdc = GetDC(hwnd);
                        if (pcx->Read(szFileName, hdc))
                        {
                          char wtext[70];
                          wsprintf(wtext, "PcxWin - %.40s (%u x %u)",
                            AnsiUpper(szFileName),pcx->Width(), pcx->Height());
                          SetWindowText(hwnd, wtext);
                        }
                        else
                        {
                            SetWindowText(hwnd, szUntitled);
                        }
                        ReleaseDC(hwnd, hdc);
                        POINT ptNewPos = {0,0};
                        scroll->Pos(ptNewPos);
                        scroll->Size(pcx->Size());
                    }
                    InvalidateRect(hwnd, NULL, TRUE);
                    break;
                case IDM_ABOUT:
                    MessageBox(hwnd, "PCXWIN (c) Paul Chui, 1991",
                        "About PCXWIN...", MB_OK | MB_ICONINFORMATION);
                    break;
                case IDM_EXIT :
                    DestroyWindow(hwnd);
                    break;
                case IDM_COPY :
                    OpenClipboard(hwnd);
                    EmptyClipboard();
                    SetClipboardData(CF_BITMAP, pcx->Bitmap());
                    CloseClipboard();
                }
                return 0L;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}
int DoKeyDown(HWND hwnd, WORD wVkey)
{
    switch (wVkey)
    {
    case VK_HOME  : SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L);      break;
    case VK_END   : SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L);   break;
    case VK_PRIOR : SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L);   break;
    case VK_NEXT  : SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L); break;
    case VK_UP    : SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0L);   break;
    case VK_DOWN  : SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0L); break;
    case VK_LEFT  : SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0L);   break;
    case VK_RIGHT : SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L); break;
    }
    return 0;
}
BOOL DoFileOpenDlg(HANDLE hInst, HWND hwnd)
{
    FARPROC lpfnFileOpenDlgProc = MakeProcInstance((FARPROC)FileOpenDlgProc,
                                                                        hInst);
    BOOL bReturn = DialogBox(hInst, "FileOpen", hwnd, lpfnFileOpenDlgProc);
    FreeProcInstance(lpfnFileOpenDlgProc);
    return bReturn;
}
BOOL FAR PASCAL FileOpenDlgProc(HWND hDlg, WORD message, WORD wParam, LONG)
{
    switch(message)
    {
        case WM_INITDIALOG :
            SendDlgItemMessage(hDlg, IDD_FNAME, EM_LIMITTEXT, MAXPATH, 0L);
            SetDlgItemText(hDlg, IDD_FNAME, szFileName);
            return TRUE;
        case WM_COMMAND :
            switch(wParam)
            {
                case IDOK :
                    GetDlgItemText(hDlg, IDD_FNAME, szFileName, MAXPATH);
                    EndDialog(hDlg, TRUE);
                    return TRUE;
                case IDCANCEL :
                    szFileName[0] = '\0';       // erase the string
                    EndDialog(hDlg, FALSE);
                    return TRUE;
            }
    }
    return FALSE;
}





<a name="0198_001d">
<a name="0198_001e">
[LISTING THREE]
<a name="0198_001e">

#ifndef PCXWIN_H
#define PCXWIN_H

#define IDM_OPEN    0x10
#define IDM_EXIT    0x11
#define IDM_ABOUT   0x12
#define IDM_COPY    0x20
#define IDD_FNAME   0x20

class PCX {
public:
        PCX();
       ~PCX();
virtual BOOL     Read(LPSTR lpszFileName, HDC theHdc);
virtual BOOL     Display(HDC hdc, POINT& pos, RECT& rect);
        POINT    Size();
        WORD     Width();
        WORD     Height();
        HBITMAP  Bitmap();
        HPALETTE Palette();
private:
        WORD     wWidth, wHeight;
        HBITMAP  hBitmap;
        HPALETTE hPalette;
        int      hFile;               // Input file handle
};
inline POINT    PCX::Size()    { POINT p = {wWidth,wHeight}; return p; }
inline WORD     PCX::Width()   { return wWidth; }
inline WORD     PCX::Height()  { return wHeight; }
inline HBITMAP  PCX::Bitmap()  { return hBitmap; }
inline HPALETTE PCX::Palette() { return hPalette; }

class Scroller {
public:
        Scroller(HWND hwnd);
        int      Size(POINT& ptImgSize);
        int      Vert(WORD wSBcode, WORD wSBPos);
        int      Horz(WORD wSBcode, WORD wSBPos);
        POINT    Pos();
        POINT    Pos(POINT& ptNewPos);
private:
        HWND     hClientWnd;
        POINT    ptPos;               // Current Scroll position
        POINT    ptMax;               // Max Scroll range
        POINT    ptInc;               // Scroll increment
        POINT    ptClient;            // Size of client window
};
inline POINT Scroller::Pos() { return ptPos; }
inline POINT Scroller::Pos(POINT& ptNewPos) { return ptPos = ptNewPos; }
inline void ErrorMessage(PSTR message)
{
    MessageBox(NULL, (LPSTR) message, (LPSTR) "Error", MB_OK|MB_ICONEXCLAMATION);
}
/* The standard max and min macros are undefined by BC++ because
   they may conflict with class-defined macros with the same names. */
#define MAX(a,b)            (((a) > (b)) ? (a) : (b))
#define MIN(a,b)            (((a) < (b)) ? (a) : (b))
#endif




<a name="0198_001f">
<a name="0198_0020">
[LISTING FOUR]
<a name="0198_0020">

#include <windows.h>
#include "pcxwin.h"

////////////////////////  Class Scroller  ////////////////////////////////
Scroller::Scroller(HWND hwnd)
{
    ptPos.x = 0; ptPos.y = 0;
    ptMax.x = 0; ptMax.y = 0;
    ptInc.x = 0; ptInc.y = 0;

    RECT rect;
    GetClientRect(hwnd, &rect);
    ptClient.x = rect.right; ptClient.y = rect.bottom;
    hClientWnd = hwnd;
}
int Scroller::Size(POINT& ptImgSize)
{
    RECT rect;
    GetClientRect(hClientWnd, &rect);
    ptClient.x = rect.right; ptClient.y = rect.bottom;
    ptMax.x = MAX(0, ptImgSize.x - ptClient.x);
    ptPos.x = MIN(ptPos.x, ptMax.x);
    SetScrollRange(hClientWnd, SB_HORZ, 0, ptMax.x, FALSE);
    SetScrollPos(hClientWnd, SB_HORZ, ptPos.x, TRUE);
    ptMax.y = MAX(0, ptImgSize.y - ptClient.y);
    ptPos.y = MIN(ptPos.y, ptMax.y);
    SetScrollRange(hClientWnd, SB_VERT, 0, ptMax.y, FALSE);
    SetScrollPos(hClientWnd, SB_VERT, ptPos.y, TRUE);
    return 0;
}
int Scroller::Vert(WORD wSBcode, WORD wSBPos)
{
    switch (wSBcode)
    {
        case SB_LINEUP :
            ptInc.y = -1;
            break;
        case SB_LINEDOWN :
            ptInc.y = 1;
            break;
        case SB_PAGEUP :
            ptInc.y = MIN(-1, -ptClient.y/4);
            break;
        case SB_PAGEDOWN :
            ptInc.y = MAX(1, ptClient.y/4);
            break;
        case SB_TOP :
            ptInc.y = -ptInc.y;
            break;
        case SB_BOTTOM :
            ptInc.y = ptMax.y - ptPos.y;
            break;
        case SB_THUMBPOSITION :
            ptInc.y = wSBPos - ptPos.y;
            break;
        default :
            ptInc.y = 0;
    }
    if (( ptInc.y = MAX(-ptPos.y, MIN(ptInc.y, ptMax.y - ptPos.y)) ) != 0)
    {
        ptPos.y += ptInc.y;
        ScrollWindow(hClientWnd, 0, -ptInc.y, NULL, NULL);
        SetScrollPos(hClientWnd, SB_VERT, ptPos.y, TRUE);
        UpdateWindow(hClientWnd);
    }
    return 0;
}
int Scroller::Horz(WORD wSBcode, WORD wSBPos)
{
    switch (wSBcode)
    {
        case SB_LINEUP :
            ptInc.x = -1;
            break;
        case SB_LINEDOWN :
            ptInc.x = 1;
            break;
        case SB_PAGEUP :
            ptInc.x = MIN(-1, -ptClient.x/4);
            break;
        case SB_PAGEDOWN :
            ptInc.x = MAX(1, ptClient.x/4);
            break;
        case SB_THUMBPOSITION :
            ptInc.x = wSBPos - ptPos.x;
            break;
        default :
            ptInc.x = 0;
    }
    if (( ptInc.x = MAX(-ptPos.x, MIN(ptInc.x, ptMax.x - ptPos.x)) ) != 0)
    {
        ptPos.x += ptInc.x;
        ScrollWindow(hClientWnd, -ptInc.x, 0, NULL, NULL);
        SetScrollPos(hClientWnd, SB_HORZ, ptPos.x, TRUE);
        UpdateWindow(hClientWnd);
    }
    return 0;
}




<a name="0198_0021">
<a name="0198_0022">
[LISTING FIVE]
<a name="0198_0022">

NAME        PCXWIN

DESCRIPTION 'PCX Viewer (c) Paul Chui, 1991'
EXETYPE     WINDOWS
STUB        'WINSTUB.EXE'
CODE        PRELOAD MOVEABLE DISCARDABLE
DATA        PRELOAD MOVABLE MULTIPLE
HEAPSIZE    1046
STACKSIZE   8192
PROTMODE




<a name="0198_0023">
<a name="0198_0024">
[LISTING SIX]
<a name="0198_0024">

#include <windows.h>
#include "pcxwin.h"

PCXWin MENU
BEGIN
   POPUP   "&File"
   BEGIN
    MENUITEM        "&Open"                 IDM_OPEN
    MENUITEM        SEPARATOR
    MENUITEM        "E&xit"                 IDM_EXIT
    MENUITEM        "A&bout PCXWIN..."      IDM_ABOUT
    END
   POPUP   "&Edit"
   BEGIN
        MENUITEM        "&Copy\tCtrl+Ins"     IDM_COPY
    END
END
FILEOPEN DIALOG DISCARDABLE LOADONCALL PURE MOVEABLE 10, 35, 129, 56
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | 0x80L
CAPTION "Open File"
BEGIN
  CONTROL "File &name:" -1, "STATIC", WS_CHILD | WS_VISIBLE, 8, 7, 56, 12
  CONTROL "" IDD_FNAME, "EDIT", WS_CHILD | WS_VISIBLE | WS_BORDER |
                                            WS_TABSTOP | 0x80L, 7, 15, 116, 12
  CONTROL "OK" IDOK, "BUTTON", WS_CHILD | WS_VISIBLE |
                                            WS_TABSTOP, 15, 36, 40, 12
  CONTROL "Cancel" IDCANCEL, "BUTTON", WS_CHILD | WS_VISIBLE |
                                            WS_TABSTOP, 69, 36, 40, 12
END
PCXWin ACCELERATORS
{
    VK_INSERT, IDM_COPY,    VIRTKEY, CONTROL
}




<a name="0198_0025">
<a name="0198_0026">
[LISTING SEVEN]
<a name="0198_0026">

.AUTODEPEND
#       *Translator Definitions*
CC = bccx +PCXWIN.CFG
TASM = TASM
TLINK = tlink
#       *Implicit Rules*
.c.obj:
  $(CC) -c {$< }
.cpp.obj:
  $(CC) -c {$< }
#       *List Macros*
Link_Exclude =  \
  pcxwin.res
Link_Include =  \
  pcxwin.obj \
  showpcx.obj \
  scroller.obj \
  pcxwin.def
#       *Explicit Rules*
pcxwin.exe: pcxwin.cfg $(Link_Include) $(Link_Exclude)
  $(TLINK) /v/x/n/c/Twe/P-/LC:\CPP\LIB @&&|
c0ws.obj+
pcxwin.obj+
showpcx.obj+
scroller.obj
pcxwin
        # no map file
cwins.lib+
import.lib+
maths.lib+
cs.lib
pcxwin.def
|
  RC -T pcxwin.res pcxwin.exe
#       *Individual File Dependencies*
pcxwin.obj: pcxwin.cpp
showpcx.obj: showpcx.cpp
scroller.obj: scroller.cpp
pcxwin.res: pcxwin.rc
        RC -R -IC:\CPP\INCLUDE -FO pcxwin.res PCXWIN.RC
#       *Compiler Configuration File*
pcxwin.cfg: pcxwin.mak
  copy &&|
-v
-W
-H=PCXWIN.SYM
-IC:\CPP\INCLUDE
-LC:\CPP\LIB
| pcxwin.cfg



Example 1:

    BYTE huge* lpImage = new BYTE[lImageSize];
    int h, line, plane;
    for (h=0, line=0; h<wHeight; ++h, line+=byPlanes)
        for (plane=0; plane<byPlanes; ++plane)
            read_pcx_line(lpImage+(lBmpBytesLine*(line+plane)));
    HBITMAP hBitmap = CreateBitmap(wWidth, wHeight, byPlanes, 1, lpImage);



Example 2:

    BYTE huge* lpImage = new BYTE[lImageSize];
    int h, line, plane;
    if (lImageSize < 65535L)
    // Interlaced color scanlines
    for (h=0, line=0; h<wHeight; ++h, line+=byPlanes)
        for (plane=0; plane<byPlanes; ++plane)
read_pcx_line(lpImage+(LONG(iBmpBytesLine)*(line+plane)));
    else
    // Interlaced color planes
    for (h=0, line=0; h<wHeight; ++h, line+=wHeight)
        for (plane=0; plane<byPlanes; ++plane)
           read_pcx_line(lpImage+(lBmpBytesLine*(plane*wHeight+h)));
    HBITMAP hBitmap = CreateBitmap(wWidth, wHeight, byPlanes, 1, lpImage);


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.