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: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • A modern mobile IT strategy is no longer an option, it is an absolute business necessity. Today's most productive employees are not tied to a desk, an office, or a location. They are mobile. And your company's IT strategy has to be ready to support them with easy, reliable, 24/7 access to the business information they need, from anywhere in the world, across a broad range of communication devices. Here's how some of the nation's most progressive corporations are meeting the many needs of their mobile workers …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds