This article was contributed by Pierre Fournier .
For my project, I needed to load PCX files and couldn’t find the source code to do it.
I took a documentation about the file format of PCX files and decided to give it a
shot. The effort was worth it! If you would like to know how to load and
display single plane PCX files (like I did a couple of weeks ago), then this one’s for
you.
In my demo, the actual function that reads and decompresses the PCX file is located in
my derived CMDIChildWnd class. 1 function and 3 data members have been added:
public: void LoadPCX( LPCSTR lpcszFilename ); protected: CSize BitmapSize; BITMAPINFO *psBmpInfo; BYTE *pabRawBitmap;
The function that reads the PCX file goes like this:
void CChildFrame::LoadPCX( LPCSTR lpcszFilename ) { // Standard PCX header struct PCXHead { char ID; char Version; char Encoding; char BitPerPixel; short X1; short Y1; short X2; short Y2; short HRes; short VRes; char ClrMap[16*3]; char Reserved1; char NumPlanes; short BPL; short Pal_t; char Filler[58]; } sHeader; // Open the file and put its entire content in memory FILE *pFile = fopen( lpcszFilename, "rb" ); if ( !pFile ) { MessageBox("Unable to open the PCX file"); return; } const long clFileSize = _filelength(_fileno(pFile)); BYTE *pabFileData = (BYTE *)new BYTE[ clFileSize ]; fread( pabFileData, clFileSize, 1, pFile ); fclose( pFile ); // Get the header memcpy( &sHeader, pabFileData, sizeof(sHeader) ); // Each scan line MUST have a size that can be divided by a 'long' data type int iScanLineSize = sHeader.NumPlanes * sHeader.BPL; ldiv_t sDivResult = ldiv( iScanLineSize, sizeof(long) ); if ( sDivResult.rem > 0 ) iScanLineSize = (iScanLineSize/sizeof(long)+1) * sizeof(long); // Set the bitmap size data member BitmapSize = CSize( sHeader.X2-sHeader.X1+1, sHeader.Y2-sHeader.Y1+1 ); const long clImageSize = iScanLineSize * BitmapSize.cy; // Set the bitmap information psBmpInfo = (BITMAPINFO *)new BYTE[ sizeof(BITMAPINFOHEADER) + (sizeof(RGBQUAD)*256) ]; psBmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); psBmpInfo->bmiHeader.biWidth = BitmapSize.cx; psBmpInfo->bmiHeader.biHeight = -BitmapSize.cy; psBmpInfo->bmiHeader.biPlanes = sHeader.NumPlanes; psBmpInfo->bmiHeader.biBitCount = sHeader.BitPerPixel; psBmpInfo->bmiHeader.biCompression = BI_RGB; psBmpInfo->bmiHeader.biSizeImage = 0; psBmpInfo->bmiHeader.biXPelsPerMeter = 0; psBmpInfo->bmiHeader.biYPelsPerMeter = 0; psBmpInfo->bmiHeader.biClrUsed = 0; psBmpInfo->bmiHeader.biClrImportant = 0; // Prepare a buffer large enough to hold the image pabRawBitmap = (BYTE *)new BYTE[ clImageSize ]; if ( !pabRawBitmap ) { MessageBox( "Can't allocate memory for the image" ); delete [] pabFileData; return; } // Get the compressed image long lDataPos = 0; long lPos = 128; // That's where the data begins for ( int iY=0; iY < BitmapSize.cy; iY++ ) { // Decompress the scan line for ( int iX=0; iX < sHeader.BPL; ) { UINT uiValue = pabFileData[lPos++]; if ( uiValue > 192 ) { // Two high bits are set = Repeat uiValue -= 192; // Repeat how many times? BYTE Color = pabFileData[lPos++]; // What color? if ( iX <= BitmapSize.cx ) { // Image data. Place in the raw bitmap. for ( BYTE bRepeat=0; bRepeat < uiValue; bRepeat++ ) { pabRawBitmap[lDataPos++] = Color; iX++; } } else iX += uiValue; // Outside the image. Skip. } else { if ( iX <= BitmapSize.cx ) pabRawBitmap[lDataPos++] = uiValue; iX++; } } // Pad the rest with zeros if ( iX < iScanLineSize ) { for ( ;iX < iScanLineSize; iX++ ) pabRawBitmap[lDataPos++] = 0; } } if ( pabFileData[lPos++] == 12 ) // Simple validation // Get the palette for ( short Entry=0; Entry < 256; Entry++ ) { psBmpInfo->bmiColors[Entry].rgbRed = pabFileData[lPos++]; psBmpInfo->bmiColors[Entry].rgbGreen = pabFileData[lPos++]; psBmpInfo->bmiColors[Entry].rgbBlue = pabFileData[lPos++]; psBmpInfo->bmiColors[Entry].rgbReserved = 0; } delete [] pabFileData; // Resize/Repaint the window const CSize BorderSize( GetSystemMetrics( SM_CXEDGE ) + GetSystemMetrics( SM_CXFRAME ) - 1, GetSystemMetrics( SM_CYEDGE ) + GetSystemMetrics( SM_CYFRAME ) - 1 ); SetWindowPos( NULL, 0, 0, BitmapSize.cx + (BorderSize.cx*2), BitmapSize.cy + (BorderSize.cy*2)+GetSystemMetrics(SM_CYCAPTION), SWP_NOMOVE | SWP_NOZORDER ); }
Now that we have the PCX loaded in memory, it is ready to be displayed.
BOOL CChildFrame::OnEraseBkgnd(CDC* pDC)
{
if ( pabRawBitmap && psBmpInfo )
// Display the image
SetDIBitsToDevice( *pDC, 0, 0,
BitmapSize.cx, BitmapSize.cy, 0, 0,
0, BitmapSize.cy,
pabRawBitmap, psBmpInfo, DIB_RGB_COLORS);
return CMDIChildWnd::OnEraseBkgnd(pDC);;
}
Downloads
Download source (MSVC++ 6.0) – 33 KB
Download demo project – 10 KB