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

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • As mobile devices have pushed their way into the enterprise, they have brought cloud apps along with them. This app explosion means account passwords are multiplying, which exposes corporate data and leads to help desk calls from frustrated users. This paper will discover how IT can improve user productivity, gain visibility and control over SaaS and mobile apps, and stop password sprawl. Download this white paper to learn: How you can leverage your existing AD to manage app access. Key capabilities to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds