Capturing a Window Image Into a Bitmap File, Supporting MS PaintBrush and All -- (Like the Thumbnail View of Windows Explorer)

Environment: VC++

Introduction

I have written this article to capture a Windows image into a bitmap file that will support all PaintBrush tools and Thumbnail Views of Windows Explorer. I have found many programmers suffering from this problem, including me, until I wrote this article.

The problem was that a Windows image is captured and also saved into a bitmap file. But, when it is opened with any paint tool, it will be seen as white only and also will not appear properly in the thumbnail view of Windows Explorer. But, after you save this bitmap file, using MS Paint or any other paint tool, it will open properly again as a common bitmap. Here, with this cool code, I have solved this problem. I hope it will be helpful to someone.

I also needed to capture an active window image and to save it into the ".bmp" file format. Therefore, I have written code to capture the window image and to save it into the BMP format.

Here, I used two functions, WndToBmpFile() and DDBToDIB(). Here is cool code for it.

About the Author

Hi! Guys,
I am not a well-experienced programmer but always try to do my best. So, that's why I have found this solution. I am working in an Indian Company as a senior programmer. We develop software and hardware related to video mixing and photography. My desire is to do a lot in the VC++ field especially in Graphics and Image Processing side. This is my beginning, so maybe you will not find many problems with my code. But, your appreciation will become an inspiration for me.

Code Compilation and Testing

I have compiled and tested well this code. So, don't bother about it!!

From:
Shailesh N. Kanzariya, shailesh_kanzariya@hotmail.com

//////////////////////////////////////////////////////////////////
Function Name :BOOL WndToBmpFile(CDC *pDC, CString szFile)
Parameters:
  CDC *pDC: is pointer to window DC whose image should be
    captured.
  CString szFile : is null terminated string with that name Bmp
    file should be saved.
//////////////////////////////////////////////////////////////////

//it will capture a wnd image and save it into a bmp file

BOOL WndToBmpFile(CDC *pDC, CString szFile)
{
//it will capture a wnd image and save it into a bmp file

  CString fname=szFile;
  CBitmap bmp,*pOldBmp;
  CRect rect;
  CWnd *pWnd;
  BOOL flg=0;
  CPalette pal;
  LOGPALETTE *pLp;

  if(pDC==NULL)               //if pDC is NULL return
    {
    return FALSE;
    }

  pWnd=pDC->GetWindow();      //Get Window of PDC

  pWnd->GetClientRect(&rect); //Get dimension of Window

  if(fname.IsEmpty())
    return FALSE;

  CDC memdc;

  memdc.CreateCompatibleDC(pDC);  //Make Compatible DC for memdc
  bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
    //Create Compatible DDB
  pOldBmp=memdc.SelectObject(&bmp);
  memdc.BitBlt(0,0,rect.Width(),rect.Height(),pDC,0,0,SRCCOPY);

//The following code will detect whether the BMP uses a Raster
//palette or not.

  if(pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE)
  {
    int nSize;
    nSize=sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * 256;
    pLp=(LOGPALETTE*) new BYTE[nSize];
    pLp->palVersion=0x300;
    pLp->palNumEntries=GetSystemPaletteEntries(
         pDC->m_hDC,0,255,pLp->palPalEntry);
    pal.CreatePalette(pLp);

    delete [] pLp;
  }
  memdc.SelectObject(pOldBmp);

//will convert bitmap from DDB to DIB see DDBToDIB()
// See DDBToDIB function for more..

  HANDLE hDIB=DDBToDIB(bmp,BI_RGB,&pal);
  if(hDIB==NULL)
    return FALSE;

//*************************************
//This code writes the BMP file

  CFile m_file;
  if(!m_file.Open(fname,CFile::modeWrite |
                  CFile::modeCreate,NULL))
    return FALSE;
  BITMAPFILEHEADER hdr;
  LPBITMAPINFOHEADER lpbi;
  lpbi=(LPBITMAPINFOHEADER ) hDIB;
  int nColors= 1 << lpbi->biBitCount;
  hdr.bfType= ((WORD) ('M' << 8) | 'B');
  hdr.bfSize=sizeof(hdr) + GlobalSize(hDIB);
  hdr.bfReserved1=0;
  hdr.bfReserved2=0;
  hdr.bfOffBits=(DWORD) sizeof(hdr) + nColors * sizeof(RGBQUAD);
  m_file.Write(&hdr,sizeof(hdr));
  m_file.Write(lpbi,GlobalSize(hDIB));
  m_file.Close();
//**************************************

//This is the tricky part of the code. It will open the BMP file
//again, but in Binary Mode. Then, it will read the first 14
//bytes from the bitmap file.
//It will change the 11th byte from 11 to 36.
//It will change the 14th byte from 4 to 0 because this is the
//basic requirement for the bitmap format.
//So, it will support all PaintBrush Tools and thumbnail views
//of Windows Explorer.

  CBinFile  m_tempFile;    //CBinFile is derived from CFile
  BYTE dummy=0;//14        //14
  BYTE pBuf[14];           //11
  
  BOOL fres=m_tempFile.Open(fname,CFile::modeReadWrite |
                                  CFile::typeBinary);

  if(fres==0)
    return FALSE;

  UINT tt=m_tempFile.Read(pBuf,14);
  pBuf[13]=dummy;//will replace from 04 to 00
  m_tempFile.SeekToBegin();
  m_tempFile.Write(pBuf,14);

  m_tempFile.Close();


  return flg;

//it will capture wnd and save into a bmp file
//End of the code

}

//////////////////////////////////////////////////////////////////
Function Name :HANDLE DDBToDIB(CBitmap &bitmap,
                               DWORD dwCompression,
                               CPalette *pPal)
Parameters:
  CBitmap &bitmap : Compatible Device Dependent Bitmap
  DWORD dwCompression : Compression format for Bitmap must not be
  BI_BITFIELDS in this case.
  CPalette *pPal : Pointer to Palette. If this is NULL, the
  default system palette will be used.

//////////////////////////////////////////////////////////////////
HANDLE DDBToDIB(CBitmap &bitmap, DWORD dwCompression,
                CPalette *pPal)
{

  BITMAP              bm;
  BITMAPINFOHEADER    bi;
  LPBITMAPINFOHEADER  lpbi;
  DWORD               dwLen;
  HANDLE              hDIB;
  HANDLE              handle;
  HDC                 hDC;
  HPALETTE            hPal;


  ASSERT( bitmap.GetSafeHandle() );

  // The function has no arg for bitfields
  if( dwCompression == BI_BITFIELDS )
    return NULL;

  // If a palette has not been supplied, use default palette
  hPal = (HPALETTE) pPal->GetSafeHandle();
  if (hPal==NULL)
    hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);

  // Get bitmap information
  bitmap.GetObject(sizeof(bm),(LPSTR)&bm);

  // Initialize the bitmap infoheader
  bi.biSize          = sizeof(BITMAPINFOHEADER);
  bi.biWidth         = bm.bmWidth;
  bi.biHeight        = bm.bmHeight;
  bi.biPlanes        = 1;
  bi.biBitCount      = bm.bmPlanes * bm.bmBitsPixel;
    //bm.bmPlanes    * bm.bmBitsPixel;
  bi.biCompression   = dwCompression;
  bi.biSizeImage     = 0;
  bi.biXPelsPerMeter = 0;
  bi.biYPelsPerMeter = 0;
  bi.biClrUsed       = 0;
  bi.biClrImportant  = 0;

  // Compute the size of the infoheader and the color table
  int nColors = (1 << bi.biBitCount);
  if( nColors > 256 )
    nColors = 0;
  dwLen  = bi.biSize + nColors * sizeof(RGBQUAD);

  // We need a device context to get the DIB from
  hDC = ::GetDC(NULL);
  hPal = SelectPalette(hDC,hPal,FALSE);
  RealizePalette(hDC);

  // Allocate enough memory to hold bitmap infoheader and
  // color table
  hDIB = GlobalAlloc(GMEM_FIXED,dwLen);

  if (!hDIB){
    SelectPalette(hDC,hPal,FALSE);
    ::ReleaseDC(NULL,hDC);
    return NULL;
  }

  lpbi = (LPBITMAPINFOHEADER)hDIB;

  *lpbi = bi;

  // Call GetDIBits with a NULL lpBits param, so the device
  // driver will calculate the biSizeImage field
  GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L,
                 (DWORD)bi.biHeight,
                 (LPBYTE)NULL, (LPBITMAPINFO)lpbi,
                 (DWORD)DIB_RGB_COLORS);

  bi = *lpbi;

  // If the driver did not fill in the biSizeImage field, then
  // compute it
  // Each scan line of the image is aligned on a DWORD (32bit)
  // boundary
  if (bi.biSizeImage == 0){
    bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31)
                        & ~31) / 8) * bi.biHeight;

    // If a compression scheme is used, the result may in fact
    // be larger
    // Increase the size to account for this.
    if (dwCompression != BI_RGB)
      bi.biSizeImage = (bi.biSizeImage * 3) / 2;
  }

  // Realloc the buffer so that it can hold all the bits
  dwLen += bi.biSizeImage;
  if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE))
    hDIB = handle;
  else{
    GlobalFree(hDIB);

    // Reselect the original palette
    SelectPalette(hDC,hPal,FALSE);
    ::ReleaseDC(NULL,hDC);
    return NULL;
  }

  // Get the bitmap bits
  lpbi = (LPBITMAPINFOHEADER)hDIB;

  // FINALLY get the DIB
  BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(),
        0L,                        // Start scan line
        (DWORD)bi.biHeight,        // # of scan lines
        (LPBYTE)lpbi               // address for bitmap bits
        + (bi.biSize + nColors * sizeof(RGBQUAD)),
        (LPBITMAPINFO)lpbi,        // address of bitmapinfo
        (DWORD)DIB_RGB_COLORS);    // Use RGB for color table

  if( !bGotBits )
  {
    GlobalFree(hDIB);

    SelectPalette(hDC,hPal,FALSE);
    ::ReleaseDC(NULL,hDC);
    return NULL;
  }

  SelectPalette(hDC,hPal,FALSE);
  ::ReleaseDC(NULL,hDC);

  return hDIB;

//End of the function
}

Downloads

Download Project Source - 128 Kb



Comments

  • same program in sdk

    Posted by pajaiswal on 09/05/2007 02:56am

    i need the sane program in SDK.can you help me.

    Reply
  • Small bug

    Posted by ryu on 11/08/2006 08:42pm

    The code: hdr.bfOffBits=(DWORD) sizeof(hdr) + nColors * sizeof(RGBQUAD);
    doesn't contain the sizeof BITMAPINFO and therefore, the offset bits are wrong.

    I think it should be:
    hdr.bfOffBits=(DWORD) sizeof(hdr) + sizeof(BITMAPINFO) + ((nColors-1) * sizeof(RGBQUAD));

    Btw, this is a great article. Thanks for sharing it with me :)

    Cheers :)

    Reply
  • Offset

    Posted by wuseltum on 06/29/2006 11:15am

    Thanks for your help - this was the first piece of code that worked for me ;) However I've got one problem: The bmp-file contains a certain displacement, a thin strip of the very right side of my window is displayed on the left in the bmp-file. Has anyone else encountered this problem? Thanks again!

    • Re: Offset

      Posted by wuseltum on 10/24/2006 07:57am

      Cool, that was it! Thanks a lot! :)

      Reply
    • Re: Offset

      Posted by zhouzhen1 on 10/08/2006 11:14am

      I got the same problem. And I find that the biSize is not included when calculating the header info. Actually it should be like this, hdr.bfOffBits=(DWORD) sizeof(hdr) + lpbi->biSize + nColors * sizeof(RGBQUAD);

      Reply
    Reply
  • GlobalFree after call to DDBToDIB

    Posted by jazee on 07/28/2005 01:21pm

    The hDIB should be freed after call to DDBToDIB() otherwise a memory leak occurs. In WndToBmpFile( ) Put GlobalFree( hDIB ); after m_file.Write(lpbi,GlobalSize(hDIB)); m_file.Close();

    Reply
  • Writing failed in 16 BPP

    Posted by yulaw_hieu on 05/05/2005 12:02am

    I try in 16 BPP mode and the image can't display

    • Fix for 16 bit image

      Posted by jazee on 07/28/2005 01:25pm

      The code has a bug in the offset of pBuf Should be 12 not 13. 
       The fix below will make the output match Photoshop 16 bit BMP. 
       (The only difference is file length is 2 bytes short.) 
       Photoshop and Paint open the output. 
       I used: 
       #define BMPSIZE 48 CFile m_tempFile; 
       //CBinFile is derived from CFile BYTE dummy=0; 
       BYTE pBuf[BMPSIZE];
       BOOL fres=m_tempFile.Open(strFile,CFile::modeReadWrite |CFile::typeBinary); 
       if(fres!=0) 
       { 
       UINT tt=m_tempFile.Read(pBuf,BMPSIZE); 
       pBuf[12]=dummy;/ /will replace from 04 to 00 
       pBuf[2]=(BYTE)0x38; 
       pBuf[10]=(BYTE)0x36; 
       pBuf[34]=(BYTE)0x02; 
       pBuf[38]=(BYTE)0x12; 
       pBuf[39]=(BYTE)0x0B; 
       pBuf[42]=(BYTE)0x12; 
       pBuf[43]=(BYTE)0x0B; 
       m_tempFile.SeekToBegin(); 
       m_tempFile.Write(pBuf,BMPSIZE); 
       }

      Reply
    Reply
  • Code is crashing....

    Posted by SanjeevKumar on 05/04/2004 11:08am

    Code is giving access violation at line
    
    int nColors= 1 << lpbi->biBitCount;
    
    Is there any solution for this ?

    Reply
  • Code is crashing....

    Posted by SanjeevKumar on 05/04/2004 11:07am

    Code is giving access violation at line
    
    int nColors= 1 << lpbi->biBitCount;
    
    Is there any solution for this ?

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

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds