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 …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds