Viewing PCX files

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


Comments

  • how 2 place .pcx

    Posted by Legacy on 05/24/2002 12:00am

    Originally posted by: killer spawn

    ok ive got about different chars that have nothing but .pcx files and ive never messed with them b4. how do i go about placing it in a unreal tournament file so i could play with the skin?  if any1 knows how email me at the addy upabove. thank you for any help.
    

    Reply
  • How to write Bits to Pcx files (256)

    Posted by Legacy on 05/19/2002 12:00am

    Originally posted by: Peter

      //----------------------------------------
    
    // RLE压缩
    LPBYTE ppcxBits=(LPBYTE)lpbmiHdr+sizeof(BITMAPINFOHEADER)+RasterImagePaletteSize();

    BYTE i(0);
    UINT lPos(0), rec(0);
    LPBYTE ppcxImg=new BYTE[pcxHdr.BytePerLine];
    for(int iY=0;iY<(-1)*lpbmiHdr->biHeight;iY++)
    {
    // ☆RLE编码,最大重复<=63☆
    lPos=0; rec=0;

    while(lPos<pcxHdr.BytePerLine)
    {
    i=0; // 重置步长
    while((ppcxBits[iY*pcxHdr.BytePerLine+lPos+i]==ppcxBits[iY*pcxHdr.BytePerLine+lPos+i+1])&&((lPos+i)<pcxHdr.BytePerLine)&&(i<63)) i++;
    if(i>0)
    {
    // 表明当前象素位置开始存在i个重复象素值,依次写入PCX图象数据Buffer
    // 1.重复次数
    ppcxImg[rec++]=i|0xc0;
    // 2.象素值
    ppcxImg[rec++]=ppcxBits[iY*pcxHdr.BytePerLine+lPos];

    lPos+=i; // lPos-记录当前扫描行中已经处理的字节数
    // rec -记录当前已经写入PCX文件的字节数
    }
    else
    {
    // 表明当前象素位置开始不存在重复象素值
    // 象素值大于0xc0(192),写标志0xc1
    if(ppcxBits[iY*pcxHdr.BytePerLine+lPos]&0xc0==0xc0) ppcxImg[rec++]=0xc1;
    ppcxImg[rec++]=ppcxBits[iY*pcxHdr.BytePerLine+lPos]; lPos++;
    }
    }

    pFile->Write(ppcxImg,rec);
    }
    delete []ppcxImg;

    // 写图象数据结束

    Is there any fault in my codes, could any one tell me, thank you.

    Reply
  • Load 24Bit PCX file ( RLE code)

    Posted by Legacy on 05/15/2002 12:00am

    Originally posted by: Qiu,Weiguo

    	else if(ppcxHdr->BitPlane==3)	// 8bit, 3plane, 24bit true color
    
    {
    // Get the compressed image
    LPBYTE lpBits = new BYTE[ppcxHdr->BytePerLine];
    long sigma(0), lDataPos(0), lPos(0);

    for ( int iY=0; iY < riSize.cy; iY++ )
    {
    sigma=0; // calculate sum of iX, Iter nums

    // because in bitmap bits order, it's blue=>green=>red
    // however pcx is red=>green=>blue so use decrease order
    for ( int Iter=ppcxHdr->BitPlane-1; Iter >= 0; Iter-- )
    {
    ZeroMemory(lpBits,ppcxHdr->BytePerLine);

    // Decompress the scan line
    int iX(0); lDataPos=0;

    // Pick color bits to tempory lpBits
    while ( iX<ppcxHdr->BytePerLine )
    {
    BYTE uiValue = ppcxImg[lPos++];
    if ( (uiValue & 0xc0) == 0xc0 ) // Two high bits are set = Repeat
    {
    uiValue = uiValue & 0x3f ; // Repeat how many times?
    BYTE Color = ppcxImg[lPos++]; // What color?

    if ( iX <= riSize.cx )
    { // Image data. Place in the raw bitmap.
    for ( BYTE bRepeat=0; bRepeat < uiValue; bRepeat++ )
    {
    lpBits[lDataPos++] = Color;
    iX++;
    }
    }
    else
    iX += uiValue; // Outside the image. Skip.
    }
    else
    {
    if ( iX <= riSize.cx )
    {
    lpBits[lDataPos++] = uiValue;
    iX++;
    }
    }
    }
    sigma+=iX; // accumulate iX

    // fill ppcxBits in dib structure ( ref: iterate row )
    for ( int ft=0; ft<ppcxHdr->BytePerLine; ft++)
    {
    //int it(Iter);
    //switch(Iter)
    //{
    //case 0: it=2; break;
    //case 1: it=1; break;
    //case 2: it=0; break;
    //}

    ppcxBits[clScanLineSize*iY+Iter/*it*/+3*ft]=lpBits[ft];
    }

    }
    // Pad the rest with zeros
    while( sigma < clScanLineSize )
    {
    ppcxBits[sigma++] = 0 ;
    }
    }
    delete []lpBits;
    }

    Reply
  • Help rotating PCX files!

    Posted by Legacy on 10/16/2001 12:00am

    Originally posted by: kkmelo

    Hello!
    Thanks for the code, but I need to rotate the images. Could anyone help me??

    Reply
  • PCX viewer w/ 1 bit

    Posted by Legacy on 05/17/2000 12:00am

    Originally posted by: Newbie

    Could someone please tell me how to get this to work with 1 bit images? All I get is gray boxes...

    Reply
  • Other source for PCX readers

    Posted by Legacy on 03/09/1999 12:00am

    Originally posted by: Robert Wolpov

    You can find source code for loading PCX images in the book:
    Encyclopedia of Graphics File Formats, Second Edition
    James D. Murray and William VanRyper
    O'Reilly & Associates, Inc.

    This book contains many other useful formats as well.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The impact of a data loss event can be significant. Real-time data is essential to remaining competitive. Many companies can no longer afford to rely on a truck arriving each day to take backup tapes offsite. For most companies, a cloud backup and recovery solution will eliminate, or significantly reduce, IT resources related to the mundane task of backup and allow your resources to be redeployed to more strategic projects. The cloud - can now be comfortable for you – with 100% recovery from anywhere all …

  • You probably have several goals for your patient portal of choice. Is "community" one of them? With a bevy of vendors offering portal solutions, it can be challenging for a hospital to know where to start. Fortunately, YourCareCommunity helps ease the decision-making process. Read this white paper to learn more. "3 Ways Clinicians can Leverage a Patient Portal to Craft a Healthcare Community" is a published document owned by www.medhost.com

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds