Click here to Skip to main content
15,891,766 members
Articles / Desktop Programming / MFC

Easy to Use Class for ScreenCapture to Printer

Rate me:
Please Sign up or sign in to vote.
4.14/5 (29 votes)
9 May 2004CPOL3 min read 169.4K   3.9K   38  
A class for easily capturing screen and printing to default printer
/////////////////////////////////////////////////////////////////////////////
//
// CPrntScreen Class : A Print Screen Class
// ----------------------------------------
//
// Version 1.2
/////////////////////////////////////////////////////////////////////////////
//
// PrntScreen.cpp : implementation file
//
/////////////////////////////////////////////////////////////////////////////
//
// How to use:
// In your application, add
//     #include "PrntScreen.h"
//
// For capturing and printing, add:
//
//     CPrntScreen * ScrCap;
//     ScrCap = new CPrntScreen("Impossible to print!","Error!");
//     ScrCap->DoPrntScreen(1,0,true);
//     delete ScrCap;
//     ScrCap = NULL;
//
//  or:
//
//     CPrntScreen * ScrCap;
//     ScrCap = new CPrntScreen();
//     ScrCap->DoPrntScreen();
//     delete ScrCap;
//     ScrCap = NULL;
//
/////////////////////////////////////////////////////////////////////////////


#include "stdafx.h"
#include "PrntScreen.h"


// --------------------------------------------------------------<CPrntScreen>---
//                              CPrntScreen::CPrntScreen
// Effect: Constructor
//
// Parameters:
//
//       char* sErrTxt:  Pointer to the text to be shown in a message box
//                       in case, printing went wrong.
//                       Omitting or passing NULL uses default text.
//
//       char* sCaption: Pointer to text for the Caption of MessageBox,
//                       which will be shown, if printing went wrong.
//                       Omitting or passing NULL uses default caption.
// ------------------------------------------------------------------------------

CPrntScreen::CPrntScreen(char* sErrTxt, char * sCaption)
{
  if (sErrTxt!=NULL)
    sErrortext=sErrTxt;
  else
    sErrortext="Unable to print screen!";

  if (sCaption!=NULL)
    sMsgCaption=sCaption;
  else
    sMsgCaption="CPrntScreen";
}


// -------------------------------------------------------------<~CPrntScreen>---
//                              CPrntScreen::~CPrntScreen
// Effect: Destructor
// ------------------------------------------------------------------------------

CPrntScreen::~CPrntScreen()
{
}


// --------------------------------------------------------------<DoPrntScreen>---
//  CPrntScreen::DoPrntScreen()
//  ---------------------------
//
//  Effect: Capture the desktop screen and prints to default printer
//
//  Parameters:
//      int fArea:        determines the area of screen to be captured;
//                        can be 0, 1 or 2:
//                        0 = Whole screen (Default)
//                        1 = Foreground window
//                        2 = Foreground window - Client area
//
//      int fOrientation: determines the print orientation;
//                        can be:  1 or 2
//                        1 (DMORIENT_PORTRAIT)  = Portrait (Default)
//                        2 (DMORIENT_LANDSCAPE) = Landscape
//                        This parameter is only used, if bDialog=false
//
//      bool bDialog:     Flag, which determines, if a printer selection
//                        dialog should be shown  or the default
//                        printer shall be used.
//                        false = use default printer (Default)
//                        true  = show printer dialog
//
// -----------------------------------------------------------------------------
void CPrntScreen::DoPrntScreen(int fArea, int fOrientation, bool bDialog)
{
  CDC     screenDC,memDC;
  HDC     hscreenDC;
  HWND    wHndle;
  CSize   sizeClient, sizeScreen;
  RECT    rectClient;
  POINT   pt1, pt2;
  int     nX, nY, nX2, nY2;       // coordinates of rectangle to grab

  //determine the area of screen to be captured
  //-------------------------------------------
  switch (fArea)
  {
  case 0:
      //Capture Whole screen
      wHndle=NULL;
      hscreenDC = ::GetDC(wHndle);
      screenDC.Attach(hscreenDC);
      sizeClient.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeClient.cy = screenDC.GetDeviceCaps(VERTRES);
      sizeScreen.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeScreen.cy = screenDC.GetDeviceCaps(VERTRES);
      screenDC.DPtoLP(&sizeScreen);
      break;

  case 1:
      //Determine screen size
      wHndle=NULL;
      hscreenDC = ::GetDC(wHndle);
      screenDC.Attach(hscreenDC);
      sizeScreen.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeScreen.cy = screenDC.GetDeviceCaps(VERTRES);
      screenDC.DPtoLP(&sizeScreen);
      screenDC.Detach();
      ReleaseDC(wHndle,hscreenDC);

      // Get Foreground window
      wHndle=GetForegroundWindow();
      hscreenDC = ::GetWindowDC(wHndle);
      screenDC.Attach(hscreenDC);

      // Calculate resolution of capture area (Window)
      sizeClient.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeClient.cy = screenDC.GetDeviceCaps(VERTRES);
      GetWindowRect(wHndle,&rectClient);
      nX    = rectClient.left;
      nY    = rectClient.top;
      nX2   = rectClient.right;
      nY2   = rectClient.bottom;

      //Let's check if dialog was pulled partially outside of view
      if ((nX < 0)||(nY < 0)||(nX2 > sizeClient.cx) || (nY2 > sizeClient.cy))
      {
        //in this case, we will force the dialog to screen center first
        
        HWND hwndOwner; 
        RECT rc, rcDlg, rcOwner; 
 
        // Get the desktop and dialog box rectangles. 
        hwndOwner = GetDesktopWindow(); 
        GetWindowRect(hwndOwner, &rcOwner); 
        GetWindowRect(wHndle, &rcDlg); 
        CopyRect(&rc, &rcOwner); 
 
        // Offset the owner and dialog box rectangles so that 
        // right and bottom values represent the width and 
        // height, and then offset the owner again to discard 
        // space taken up by the dialog box. 
        OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); 
        OffsetRect(&rc, -rc.left, -rc.top); 
        OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); 

        // The new position is the sum of half the remaining 
        // space and the owner's original position. 
        SetWindowPos(wHndle, 
            HWND_TOP, 
            rcOwner.left + (rc.right / 2), 
            rcOwner.top + (rc.bottom / 2), 
            0, 0,          // ignores size arguments 
            SWP_NOSIZE); 

        //Update window
        UpdateWindow(wHndle);

        //and retrieve rectangles again
        GetWindowRect(wHndle,&rectClient);
        nX    = rectClient.left;
        nY    = rectClient.top;
        nX2   = rectClient.right;
        nY2   = rectClient.bottom;
      }

      //Limit to screen size
      if (nX < 0) nX = 0;
      if (nY < 0) nY = 0;
      if (nX2 > sizeClient.cx) nX2 = sizeClient.cx;
      if (nY2 > sizeClient.cy) nY2 = sizeClient.cy;
      sizeClient.cx = nX2 - nX;
      sizeClient.cy = nY2 - nY;
      break;

  case 2:
      //Determine screen size
      wHndle=NULL;
      hscreenDC = ::GetDC(wHndle);
      screenDC.Attach(hscreenDC);
      sizeScreen.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeScreen.cy = screenDC.GetDeviceCaps(VERTRES);
      screenDC.DPtoLP(&sizeScreen);
      screenDC.Detach();
      ReleaseDC(wHndle,hscreenDC);

      // Get Foreground window - Client area
      wHndle=GetForegroundWindow();
      hscreenDC = ::GetDC(wHndle);
      screenDC.Attach(hscreenDC);

      // Calculate resolution of capture area (Client)
      sizeClient.cx = screenDC.GetDeviceCaps(HORZRES);
      sizeClient.cy = screenDC.GetDeviceCaps(VERTRES);
      GetClientRect(wHndle,&rectClient);
      nX    = rectClient.left;
      nY    = rectClient.top;
      nX2   = rectClient.right;
      nY2   = rectClient.bottom;

      //Let's check if dialog was pulled partially outside of view
      if ((nX < 0)||(nY < 0)||(nX2 > sizeClient.cx) || (nY2 > sizeClient.cy))
      {
        //in this case, we will force the dialog to screen center first
        
        HWND hwndOwner; 
        RECT rc, rcDlg, rcOwner; 
 
        // Get the desktop and dialog box rectangles. 
        hwndOwner = GetDesktopWindow(); 
        GetWindowRect(hwndOwner, &rcOwner); 
        GetWindowRect(wHndle, &rcDlg); 
        CopyRect(&rc, &rcOwner); 
 
        // Offset the owner and dialog box rectangles so that 
        // right and bottom values represent the width and 
        // height, and then offset the owner again to discard 
        // space taken up by the dialog box. 
        OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); 
        OffsetRect(&rc, -rc.left, -rc.top); 
        OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); 

        // The new position is the sum of half the remaining 
        // space and the owner's original position. 
        SetWindowPos(wHndle, 
            HWND_TOP, 
            rcOwner.left + (rc.right / 2), 
            rcOwner.top + (rc.bottom / 2), 
            0, 0,          // ignores size arguments 
            SWP_NOSIZE); 

        //Update window
        UpdateWindow(wHndle);

        //and retrieve rectangles again
        GetWindowRect(wHndle,&rectClient);
      }

      //Limit client area and to screen size
      pt1.x = rectClient.left;
      pt1.y = rectClient.top;
      pt2.x = rectClient.right;
      pt2.y = rectClient.bottom;
      ClientToScreen(wHndle,&pt1);
      ClientToScreen(wHndle,&pt2);
      nX  = pt1.x;
      nY  = pt1.y;
      nX2 = pt2.x;
      nY2 = pt2.y;
      if (nX < 0) nX = 0;
      if (nY < 0) nY = 0;
      if (nX2 > sizeClient.cx) nX2 = sizeClient.cx;
      if (nY2 > sizeClient.cy) nY2 = sizeClient.cy;
      sizeClient.cx = nX2 - nX;
      sizeClient.cy = nY2 - nY;
      break;

  default:
     return;
  }

  //Screen Capture to image
  CBitmap *oldImage = new CBitmap,srcImage;
  memDC.CreateCompatibleDC(&screenDC);
  screenDC.DPtoLP(&sizeClient);
  srcImage.CreateCompatibleBitmap(&screenDC,sizeClient.cx,sizeClient.cy);
  oldImage = memDC.SelectObject(&srcImage);
  memDC.BitBlt(0,0,sizeClient.cx,sizeClient.cy,&screenDC,0,0,SRCCOPY);
  screenDC.Detach();
  ReleaseDC(wHndle,hscreenDC);


  // Here starts the printing part
  //-------------------------------
  double zoomx, zoomy, scrXY, prnXY;
  PRINTDLG pd;
  LPDEVMODE lpDevMode;
  CSize sizePrn;
  DOCINFO di;
  CDC *pDC = new CDC;

  //Clear PrintDialog structure
  memset( &pd, 0, sizeof(PRINTDLG) ) ;

  //Fill out PrintDialog structure
  pd.lStructSize = sizeof(PRINTDLG);
  pd.hDC = NULL;
  pd.hwndOwner = NULL;
  pd.hInstance = NULL;
  pd.nMaxPage = 1;
  pd.nMinPage = 1;
  pd.nFromPage = 1;
  pd.nToPage = 1;
  pd.nCopies = 1;
  pd.hDevMode = NULL;
  pd.hDevNames = NULL;
  if (bDialog)
    pd.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE;
  else
    pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;

  //Call PrintDialog
  if(PrintDlg(&pd))
  {
    pDC->Attach(pd.hDC);

    //Check, if printer is capable for raster operations
    int iRasterCaps=GetDeviceCaps(pd.hDC,RASTERCAPS);
    if  ((iRasterCaps&RC_STRETCHBLT)==0)
    {
      //No, it isn�t, so error message and off we are
      MessageBox(NULL,_TEXT(sErrortext),_TEXT(sMsgCaption),MB_OK|MB_ICONERROR);
    }
    else
    { // OK so far, now we can start printing

      // Only if default printer is used, we adjust 
      // the orientation as given by parameter
      if (!bDialog)
      {
          //Check if orientation parameter is valid
          if ((fOrientation!=DMORIENT_LANDSCAPE)&&(fOrientation!=DMORIENT_PORTRAIT))
            fOrientation=DMORIENT_PORTRAIT;

          // Adjust desired orientation
          lpDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
          if(lpDevMode != NULL)
          {
            lpDevMode->dmOrientation=(short)fOrientation;
            pDC->ResetDC(lpDevMode);
            ::GlobalUnlock(pd.hDevMode);
          }
      }

      // Get printers page dimensions
      sizePrn.cx = pDC->GetDeviceCaps(HORZRES);
      sizePrn.cy = pDC->GetDeviceCaps(VERTRES);

      //Clean page
      pDC->PatBlt( 0, 0, sizePrn.cx, sizePrn.cy, WHITENESS);

      // Set magnitude of stretching 
      // (in relation of screen to captured area to page size)
      scrXY = (double)sizeScreen.cx/(double)sizeScreen.cy;
      prnXY = (double)sizePrn.cx/(double)sizePrn.cy;
      if (scrXY>prnXY) 
      {
        zoomx = ((double)sizeClient.cx/(double)sizeScreen.cx);
        zoomy = ((double)sizeClient.cy/(double)sizeScreen.cy)*(prnXY/scrXY);
      }
      else
      {
        zoomx = ((double)sizeClient.cx/(double)sizeScreen.cx)*(scrXY/prnXY);
        zoomy = ((double)sizeClient.cy/(double)sizeScreen.cy);
      }

      //Limit to zoom x1
      if (zoomx>1.0)
      { 
        zoomy=zoomy/zoomx;
        zoomx=1.0;
      }
      if (zoomy>1.0)
      { 
        zoomx=zoomx/zoomy;
        zoomy=1.0;
      }

      //now set OUR page size
      sizePrn.cx = (long)(sizePrn.cx*zoomx);
      sizePrn.cy = (long)(sizePrn.cy*zoomy);
      pDC->LPtoDP(&sizePrn);

      // Start printing
      di.cbSize = sizeof(DOCINFO);
      di.lpszDocName = sMsgCaption;
      di.lpszOutput = (LPTSTR) NULL;
      di.lpszDatatype = (LPTSTR) NULL;
      di.fwType = 0;

      ::StartDoc(pd.hDC, &di);
      ::StartPage(pd.hDC);

      //This was the old, not always working version. 
      // pDC->StretchBlt(0,0,sizePrn.cx,sizePrn.cy,&memDC,
      //                 0,0,sizeClient.cx,sizeClient.cy,SRCCOPY); 


      //This is the new version following the suggestion of Dieter Hammer (thanks). 
      //I shamlessly copied his code here.
      //---------------------------------------------------------------------------
      //Copy the bitmap (Device dependant bitmap or DDB)
      //to a Device indepedant bitmap (DIB)
      CPalette pal;
      LPVOID lpDIBBits;
      LPBITMAPINFOHEADER lpbi;
      HANDLE hDIB = DDBToDIB(srcImage, BI_RGB, &pal );
      lpbi = (LPBITMAPINFOHEADER)hDIB;
      int nColors = lpbi->biClrUsed ? lpbi->biClrUsed : 1 << lpbi->biBitCount;
      BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB;
      if( bmInfo.bmiHeader.biBitCount > 8 )
      {
        lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors 
                    + bmInfo.bmiHeader.biClrUsed) 
                    + ((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0));
      }
      else
      {
        lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
      }
      HDC hDC = pDC->GetSafeHdc();

      //Copy de DIB to the printer device
      //Using DIB rather than DDB because DDB can
      //have an unpredicable behavior with network printer !!!!!
      StretchDIBits(
                     hDC,              // hDC
                     0,                // DestX
                     0,                // DestY
                     (int) sizePrn.cx, // nDestWidth
                     (int) sizePrn.cy, // nDestHeight
                     0, // SrcX
                     0, // SrcY
                     sizeClient.cx,    // wSrcWidth
                     sizeClient.cy,    // wSrcHeight
                     lpDIBBits,        // lpBits
                     &bmInfo,          // lpBitsInfo
                     DIB_RGB_COLORS,   // wUsage
                     SRCCOPY);         // dwROP

      ::EndPage(pd.hDC);
      ::EndDoc(pd.hDC);
      GlobalFree(hDIB);
    }
  }
  else if ((!bDialog)||(CommDlgExtendedError()!=0))
  {
     MessageBox(NULL,_TEXT(sErrortext),_TEXT(sMsgCaption),MB_OK|MB_ICONERROR);
  }

  //Cleanup
  DeleteDC(pd.hDC);
  delete pDC;
  memDC.SelectObject(oldImage);

}


//This below code is entirely from Zafir Anjum, posted August 5, 1998 at CodeGuru.
//See http://www.codeguru.com/Cpp/G-M/bitmap/article.php/c1765 for details.
//
HANDLE CPrntScreen::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 defaul palette
	hPal = (HPALETTE) pPal->GetSafeHandle();
	if (hPal==NULL)
		hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);

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

	// Initialize the bitmapinfoheader
	bi.biSize		= sizeof(BITMAPINFOHEADER);
	bi.biWidth		= bm.bmWidth;
	bi.biHeight 		= bm.bmHeight;
	bi.biPlanes 		= 1;
	bi.biBitCount		= 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 bitmapinfoheader 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 infact 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;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Brazil Brazil
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions