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:
- Read a byte;
- If the two high bits are set, then N = the lower six bits, else Write this byte and Go to step 1;
- Read the next byte and Write it N times;
- Go to step 1.
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).
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.
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