Sunday, May 1, 2011

Windows Screen Capture sample code

Following code is a vc++ example for Screen Capture function.
Collected from http://www.ham-radio-deluxe.com/Support/SourceCode/WindowsScreenCapture.aspx
Introduction
The guts of this code is basic Windows programming, the clever part is saving to a BMP on disk then converting the BMP to JPG using custom code taken from www.codeproject.com .
The code uses a few custom classes for saving and converting from BMP to JPG, to do this see http://www.codeproject.com/KB/graphics/cximage.aspx . The code to save a BMP to a file is below. Take it, make it your own.
The main function is ScreenCapture() which I put in CMainFrame.

class CSaveBitmapToFile 
{
public:
 CSaveBitmapToFile();
 virtual ~CSaveBitmapToFile();

 PBITMAPINFO CreateBitmapInfoStruct(CWnd*, HBITMAP hBmp);
 void        CreateBMPFile(CWnd*, LPCTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC);
};


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSaveBitmapToFile::CSaveBitmapToFile()
{
}

CSaveBitmapToFile::~CSaveBitmapToFile()
{
}

//
// Create structures.
//
PBITMAPINFO CSaveBitmapToFile::CreateBitmapInfoStruct(CWnd* pParent, HBITMAP hBmp)
{
    BITMAP  bmp      = {0};
    PBITMAPINFO pbmi     = NULL;
    WORD  cClrBits = 0;

 //
    // Retrieve the bitmap's color format, width, and height.
 //
    if (!::GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
 {
  CErrorText err;
  CString    strText;

  strText.Format(_T("Error getting bitmap colour format, width and height: %s"), err.Text());
  pParent->MessageBox(strText, _T("Error"), MB_OK | MB_ICONERROR);
        return NULL;
 }

 //
    // Convert the color format to a count of bits.
 //
    cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);

    if (cClrBits == 1)
        cClrBits = 1;
    else if (cClrBits <= 4)
        cClrBits = 4;
    else if (cClrBits <= 8)
        cClrBits = 8;
    else if (cClrBits <= 16)
        cClrBits = 16;
    else if (cClrBits <= 24)
        cClrBits = 24;
    else cClrBits = 32;

 //
    // Allocate memory for the BITMAPINFO structure. (This structure
    // contains a BITMAPINFOHEADER structure and an array of RGBQUAD
    // data structures.)
 //
 if (cClrBits != 24)
 {
  pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << cClrBits));
 }

 //
 // There is no RGBQUAD array for the 24-bit-per-pixel format.
 //
 else
 {
         pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
 }

 //
    // Initialize the fields in the BITMAPINFO structure.
 ///
    pbmi->bmiHeader.biSize     = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth    = bmp.bmWidth;
    pbmi->bmiHeader.biHeight   = bmp.bmHeight;
    pbmi->bmiHeader.biPlanes   = bmp.bmPlanes;
    pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;

    if (cClrBits < 24)
        pbmi->bmiHeader.biClrUsed = (1 << cClrBits);

 //
    // If the bitmap is not compressed, set the BI_RGB flag.
 //
    pbmi->bmiHeader.biCompression = BI_RGB;

 //
    // Compute the number of bytes in the array of color
    // indices and store the result in biSizeImage.
    // For Windows NT/2000, the width must be DWORD aligned unless
    // the bitmap is RLE compressed. This example shows this.
    // For Windows 95/98, the width must be WORD aligned unless the
    // bitmap is RLE compressed.
 //
    pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8 * pbmi->bmiHeader.biHeight;

 //
    // Set biClrImportant to 0, indicating that all of the
    // device colors are important.
 //
 pbmi->bmiHeader.biClrImportant = 0;

 return pbmi;
}

//
// The following example code defines a function that initializes the remaining structures,
// retrieves the array of palette indices, opens the file, copies the data, and closes the file.
//
void CSaveBitmapToFile::CreateBMPFile(CWnd*       pParent,
           LPCTSTR     pszFile,
           PBITMAPINFO pbi,
           HBITMAP     hBMP,
           HDC         hDC)
{
 HANDLE    hf      = NULL;  // File handle
 BITMAPFILEHEADER hdr     = {0};  // Bitmap file-header
 PBITMAPINFOHEADER pbih    = NULL;  // Bitmap info-header
 LPBYTE    lpBits  = NULL;  // Memory pointer
 DWORD    dwTotal = 0;  // Total count of bytes
 DWORD    cb      = 0;  // Incremental count of bytes
 BYTE*    hp      = NULL;  // Byte pointer
 DWORD    dwTmp   = 0;

 pbih   = (PBITMAPINFOHEADER) pbi;
 lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);

 if (!lpBits)
 {
  pParent->MessageBox(_T("Error allocating temporary storage."),
       _T("Error"),
       MB_OK | MB_ICONERROR);
  return;
 }

 //
 // Retrieve the color table (RGBQUAD array) and the bits
 // (array of palette indices) from the DIB.
 //
 if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi, DIB_RGB_COLORS))
 {
  pParent->MessageBox(_T("Error retrieving colour table and bits."),
       _T("Error"),
       MB_OK | MB_ICONERROR);

  GlobalFree((HGLOBAL)lpBits);
  return;
 }

 //
 // Create the .BMP file.
 //
 hf = CreateFile(pszFile,
     GENERIC_READ | GENERIC_WRITE,
     (DWORD)0,
     NULL,
     CREATE_ALWAYS,
     FILE_ATTRIBUTE_NORMAL,
     (HANDLE) NULL);

 if (hf == INVALID_HANDLE_VALUE)
 {
  CString  strText;
  CErrorText error;

  strText.Format(_T("Error creating output file: %s -\n%s"),
        pszFile,
        error.Text());

  pParent->MessageBox(strText,
       _T("Error"),
       MB_OK | MB_ICONERROR);

  GlobalFree((HGLOBAL)lpBits);
  return;
 }

 //
 // BM 0x42 = "B" 0x4d = "M".
 //
 hdr.bfType = 0x4d42;

 //
 // Compute the size of the entire file.
 //
 hdr.bfSize      = (DWORD)(sizeof(BITMAPFILEHEADER) +  pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
 hdr.bfReserved1 = 0;
 hdr.bfReserved2 = 0;

 //
 // Compute the offset to the array of color indices.
 //
 hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof (RGBQUAD);

 //
 // Copy the BITMAPFILEHEADER into the .BMP file.
 //
 if (!WriteFile(hf,
       (LPVOID)&hdr,
       sizeof(BITMAPFILEHEADER),
       (LPDWORD) &dwTmp,  NULL))
 {
  pParent->MessageBox(_T("Error writing to output file."),
       _T("Error"),
       MB_OK | MB_ICONERROR);

  GlobalFree((HGLOBAL)lpBits);
  CloseHandle(hf);
  return;
 }

 //
 // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
 //
 if (!WriteFile(hf,
       (LPVOID)pbih,
       sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),
       (LPDWORD)&dwTmp,
       (NULL)))
 {
  pParent->MessageBox(_T("Error writing to output file."),
       _T("Error"),
       MB_OK | MB_ICONERROR);

  GlobalFree((HGLOBAL)lpBits);
  CloseHandle(hf);
  return;
 }

 //
 // Copy the array of color indices into the .BMP file.
 //
 dwTotal = cb = pbih->biSizeImage;
 hp      = lpBits;

 if (!WriteFile(hf,
       (LPSTR)hp,
       (int)cb,
       (LPDWORD)&dwTmp,
       NULL))
 {
  pParent->MessageBox(_T("Error writing to output file."),
       _T("Error"),
       MB_OK | MB_ICONERROR);

  GlobalFree((HGLOBAL)lpBits);
  CloseHandle(hf);
  return;
 }

 //
 // Close the .BMP file.
 //
 CloseHandle(hf);

 //
 // Free memory.
 //
 GlobalFree((HGLOBAL)lpBits);
}








The Code - from MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "HRDLogbook.h"

#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//
// Clipboard.
//
void CMainFrame::OnToolsScreenCapture()
{
 ScreenCapture(this, TRUE, FALSE);
}

//
// File.
//
void CMainFrame::OnToolsScreenCaptureFile()
{
 ScreenCapture(this, FALSE, TRUE);
}

void CMainFrame::ScreenCapture(CWnd*      pWnd,
          const BOOL bClipboard,
          const BOOL bFile)
{
 CRect rectClient(0,0,0,0);
 CRect rectWindow(0,0,0,0);

 pWnd->GetClientRect(&rectClient);
 pWnd->GetWindowRect(&rectWindow);

 BOOL bZoom          = pWnd->IsZoomed();
 int  x              = (rectWindow.Width()  - rectClient.Width()) / 2;
 int  y              = (rectWindow.Height() - rectClient.Height()) - x;
 HWND hDesktopWnd    = pWnd->m_hWnd;
 HDC  hDesktopDC     = ::GetDC(hDesktopWnd);
 HDC  hCaptureDC     = ::CreateCompatibleDC(hDesktopDC);

 int xSrc          = bZoom ? 0 : -x;
 int ySrc          = bZoom ? 0 : -y;
 int nScreenWidth  = bZoom ? (rectWindow.Width()  - x - x) : rectWindow.Width();
 int nScreenHeight = bZoom ? (rectWindow.Height() - y - x) : rectWindow.Height();

// nScreenWidth  = rectWindow.Width();
// nScreenHeight = rectWindow.Height();

 HBITMAP hCaptureBitmap = ::CreateCompatibleBitmap(hDesktopDC,
               nScreenWidth,
               nScreenHeight);
 SelectObject(hCaptureDC, hCaptureBitmap);

 BitBlt(hCaptureDC,
     0,
     0,
     nScreenWidth,
     nScreenHeight,
     hDesktopDC,
     xSrc,
     ySrc,
     SRCCOPY | CAPTUREBLT);

 //
 // Copy to clipboard.
 //
 if (bClipboard)
 {
  //
  // Open clipboard and empty its contents.
  //
  OpenClipboard();
  EmptyClipboard();

  //
  // Copy bitmap to clipboard and close it.
  //
  SetClipboardData(CF_BITMAP, hCaptureBitmap);
  CloseClipboard();
 }

 //
 // Save to file.
 //
 if (bFile)
 {
  CSaveBitmapToFile save;
  CString    strFilenameBmp;
  CString    strFilenameJpg;
  CString    strFolderBmp;
  CString    strFolderJpg;
  CTime    now = CTime::GetCurrentTime();
  PBITMAPINFO   pbi = save.CreateBitmapInfoStruct(this, hCaptureBitmap);

  if (pbi)
  {
   Dirs().GetFolderPath(CSIDL_MYPICTURES,    strFolderJpg);
   Dirs().GetFolderPath(CSIDL_LOCAL_APPDATA, strFolderBmp);

   strFilenameBmp.Format(_T("%s\\HRD-Logfile-Screenshot-%s.bmp"),
          strFolderBmp,
          now.Format(_T("%Y-%m-%d-%H%M%S")));

   strFilenameJpg.Format(_T("%s\\HRD-Logfile-Screenshot-%s.jpg"),
          strFolderJpg,
          now.Format(_T("%Y-%m-%d-%H%M%S")));

   CFileDialogSave dlg(strFilenameJpg,
        FD_JPG_FILTER,
        _T("Save Screen Capture file..."),
        this);
   if (dlg.DoModal() == IDOK)
   {
    CMyImage image;

    //
    // Create file.
    //
    save.CreateBMPFile(this,
           (LPCTSTR)strFilenameBmp,
           pbi,
           hCaptureBitmap,
           hCaptureDC);

    //
    // Now convert to JPG, 90% is good enough...
    //
    image.ConvertToJPG(strFilenameBmp,
           dlg.Filename(),
           90);

    try
    {
     CFile::Remove(strFilenameBmp);
    }
    catch(...)
    {
    }

    //
    // Display.
    //
    OwnShellExecuteEdit(this, dlg.Filename());
   }

   //
   // Tidy up.
   //
   LocalFree(pbi);
  }
 }

 ::ReleaseDC(hDesktopWnd,hDesktopDC);
 ::DeleteDC(hCaptureDC);
 ::DeleteObject(hCaptureBitmap);
}