Render to Printer        

In this article I like to explain how to print the render window content based on Ogre Wiki Tutorial Framework.

You have to prepare the base class of the Tutorial Framework for windows. Just add a new variable

New variable for BaseApplication class
size_t mWindowHnd;

to BaseApplication class declaration.

To set this variable you just have to modify createFrameListener method

Modified createFrameListener method
void BaseApplication::createFrameListener(void)
{
	Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
	OIS::ParamList pl;
	std::ostringstream windowHndStr;

	mWindow->getCustomAttribute("WINDOW", &mWindowHnd);
	windowHndStr << mWindowHnd;
	pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

	mInputManager = OIS::InputManager::createInputSystem( pl );

	mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, true ));
	mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, true ));

	mMouse->setEventCallback(this);
	mKeyboard->setEventCallback(this);

	//Set initial mouse clipping size
	windowResized(mWindow);

	//Register as a Window listener
	Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);

#ifdef OGRE_EXTERNAL_OVERLAY
	OgreBites::InputContext input;
	input.mAccelerometer = NULL;
	input.mKeyboard = mKeyboard;
	input.mMouse = mMouse;
	input.mMultiTouch = NULL;

	mTrayMgr = new OgreBites::SdkTrayManager("InterfaceName", mWindow, input, this);
#else
	mTrayMgr = new OgreBites::SdkTrayManager("InterfaceName", mWindow, mMouse, this);
#endif
	mTrayMgr->showFrameStats(OgreBites::TL_BOTTOMLEFT);
	mTrayMgr->showLogo(OgreBites::TL_BOTTOMRIGHT);
	mTrayMgr->hideCursor();

	// create a params panel for displaying sample details
	Ogre::StringVector items;
	items.push_back("cam.pX");
	items.push_back("cam.pY");
	items.push_back("cam.pZ");
	items.push_back("");
	items.push_back("cam.oW");
	items.push_back("cam.oX");
	items.push_back("cam.oY");
	items.push_back("cam.oZ");
	items.push_back("");
	items.push_back("Filtering");
	items.push_back("Poly Mode");

	mDetailsPanel = mTrayMgr->createParamsPanel(OgreBites::TL_NONE, "DetailsPanel", 200, items);
	mDetailsPanel->setParamValue(9, "Bilinear");
	mDetailsPanel->setParamValue(10, "Solid");
	mDetailsPanel->hide();

	mRoot->addFrameListener(this);
#ifdef OGRE_EXTERNAL_OVERLAY
	mSceneMgr->addRenderQueueListener(mOverlaySystem);
#endif
}


In the beginning you have to override the OIS keyPressed event handler to fetch the P key for printing:

Override key pressed event
bool TutorialApplication::keyPressed( const OIS::KeyEvent &arg )
{
   if (mTrayMgr->isDialogVisible()) return true;   // don't process any more keys if dialog is up

   // ...

   return BaseApplication::keyPressed(arg);
}


Before we can start printing, we have to declare the used pixel format for windows:

Prepare pixel format
PIXELFORMATDESCRIPTOR pfd;
   ZeroMemory(&pfd, sizeof(pfd));
   pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
   pfd.nVersion = 1;
   pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_SUPPORT_GDI | PFD_DEPTH_DONTCARE;
   pfd.iPixelType = PFD_TYPE_RGBA;
   pfd.cColorBits = 24;


This function is required for setting up DIB sections to draw an image on printer device.

Now, it's time to fetch the pressed P key and setup the printer selection dialog

Select printer
if (arg.key == OIS::KC_P)
   {
      PRINTDLG pd;
      // Initialize PRINTDLG
      ZeroMemory(&pd, sizeof(pd));
      pd.lStructSize = sizeof(pd);
      pd.hwndOwner   = NULL;
      pd.hDevMode    = NULL;     // Don't forget to free or store hDevMode.
      pd.hDevNames   = NULL;     // Don't forget to free or store hDevNames.
      pd.Flags       = PD_RETURNDC | PD_ALLPAGES; 
      pd.nCopies     = 1;
      pd.nFromPage   = 0xFFFF; 
      pd.nToPage     = 0xFFFF; 
      pd.nMinPage    = 1; 
      pd.nMaxPage    = 0xFFFF; 

      if (PrintDlg(&pd) == TRUE) 
      {
          // Printer selected start printing
          // ...
      }
   }


In the next step we have to prepare the printing document and receive the window size and page size.

Prepare printing document
// Zero and then initialize the members of a DOCINFO structure.
         DOCINFO di = {0};
         di.cbSize = sizeof(DOCINFO);
         di.lpszDocName = ("Ogre");
         di.lpszOutput = (LPTSTR) NULL;
         di.lpszDatatype = (LPTSTR) NULL;
         di.fwType = 0;

         // Get the dimensions of the image and the page
         int iImgWidth = mWindow->getWidth();
         int iImgHeight = mWindow->getHeight();
         int iPageWidth = GetDeviceCaps(pd.hDC, HORZRES);
         int iPageHeight = GetDeviceCaps(pd.hDC, VERTRES);


To print the rendered image we have to copy the image to a windows bitmap. We have to prepare a few things before we can copy the image data:

Setup bitmap
// Initialize the bitmap header.
         BITMAPINFO bmi;
         ZeroMemory(&bmi, sizeof(bmi));
         bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
         bmi.bmiHeader.biWidth = iImgWidth;
         bmi.bmiHeader.biHeight = iImgHeight;
         bmi.bmiHeader.biPlanes = 1;
         bmi.bmiHeader.biBitCount = 24;
         bmi.bmiHeader.biCompression = BI_RGB;

         // Create the surface.
         void* pBits;
         HDC hdcWindow = GetWindowDC((HWND)mWindowHnd);
         HBITMAP hBitmap = CreateDIBSection(hdcWindow, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);

         // Create memory dc for painting
         HDC hdcMemory = CreateCompatibleDC(hdcWindow);
         SelectObject(hdcMemory, hBitmap);


The bitmap is now ready to store the rendered image.

Copy image
// Copy render image to bitmap
         Ogre::PixelFormat format = Ogre::PF_BYTE_RGBA;
         size_t outBytesPerPixel = Ogre::PixelUtil::getNumElemBytes(format);
         unsigned char *data = new unsigned char [iImgWidth * iImgHeight * outBytesPerPixel];
         Ogre::Box extents(0, 0, iImgWidth, iImgHeight);
         Ogre::PixelBox mRenderWindowPixelBox(extents, format, data);
         mWindow->copyContentsToMemory(mRenderWindowPixelBox);
         Ogre::Image img;
         img.loadDynamicImage(static_cast<Ogre::uchar*>(mRenderWindowPixelBox.data), iImgWidth, iImgHeight, format);
         for(size_t y = 0; y < iImgHeight; y++)
            for(size_t x = 0; x < iImgWidth; x++)
            {
               Ogre::ColourValue cv = img.getColourAt(x, y, 0);
               Ogre::Real red = std::min<Ogre::Real>(255.0f, cv.r * 255.0f);
               Ogre::Real green = std::min<Ogre::Real>(255.0f, cv.g * 255.0f);
               Ogre::Real blue = std::min<Ogre::Real>(255.0f, cv.b * 255.0f);
               SetPixel(hdcMemory, x, y, RGB(red, green, blue));
            }


Now we can start the printing job:

Start printing
// Begin a print job by calling the StartDoc function.
         StartDoc(pd.hDC, &di);

         // Inform the driver that the application is about to begin sending data.
         StartPage(pd.hDC);


We have to set the output pixel format and receive the device independent bitmap (DIB) section

Pixel format and DIB properties
// Choose a pixel format that best matches that described in pfd
         int iPixelFormat = ChoosePixelFormat(pd.hDC, &pfd);
         SetPixelFormat(pd.hDC, iPixelFormat, &pfd);

         // Retrieve the information describing the surface.
         DIBSECTION ds;
         GetObject(hBitmap, sizeof(DIBSECTION), &ds);


To stretch the image on the whole page it's necessary to calculate a required scaling factor.

Calculate scaling
// Determine the scaling factors required retain the bitmap's original proportions.
         float fScaleX = (float)std::max<int>(iImgWidth, iPageWidth) / (float)std::min<int>(iImgWidth, iPageWidth);
         float fScaleY = (float)std::max<int>(iImgHeight, iPageHeight) / (float)std::min<int>(iImgHeight, iPageHeight);
         float fScale = std::min<float>(fScaleX, fScaleY);

         // Compute the coordinates of the upper left corner of the centered bitmap.
         int xLeft = ((iPageWidth / 2) - ((int)(((float)iImgWidth) * fScale)) / 2);
         int yTop = ((iPageHeight / 2) - ((int)(((float)iImgHeight) * fScale)) / 2);


Now, it's easy to print the scaled image on selected device:

Print the image
// Use StretchDIBits to scale the bitmap and maintain its original proportions
         StretchDIBits(pd.hDC, xLeft, yTop, (int)((float)iImgWidth * fScale), (int)((float)iImgHeight * fScale), 0, 0, iImgWidth, iImgHeight, ds.dsBm.bmBits, (LPBITMAPINFO)&ds.dsBmih, DIB_RGB_COLORS, SRCCOPY);


The last step is closing the printing document and cleaning up the reserved memory.

Close & clean
// Inform the driver that the page is finished.
         EndPage(pd.hDC);
            
         // Inform the driver that document has ended.
         EndDoc(pd.hDC);

         // Cleanup
         DeleteDC(hdcMemory);
         ReleaseDC((HWND)mWindowHnd, hdcWindow);
         DeleteObject(hBitmap);
         DeleteDC(pd.hDC);


This is the complete function:

Complete print function
bool TutorialApplication::keyPressed( const OIS::KeyEvent &arg )
{
   if (mTrayMgr->isDialogVisible()) return true;   // don't process any more keys if dialog is up

   PIXELFORMATDESCRIPTOR pfd;
   ZeroMemory(&pfd, sizeof(pfd));
   pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
   pfd.nVersion = 1;
   pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_SUPPORT_GDI | PFD_DEPTH_DONTCARE;
   pfd.iPixelType = PFD_TYPE_RGBA;
   pfd.cColorBits = 24;

   if (arg.key == OIS::KC_P)
   {
      PRINTDLG pd;
      // Initialize PRINTDLG
      ZeroMemory(&pd, sizeof(pd));
      pd.lStructSize = sizeof(pd);
      pd.hwndOwner   = NULL;
      pd.hDevMode    = NULL;     // Don't forget to free or store hDevMode.
      pd.hDevNames   = NULL;     // Don't forget to free or store hDevNames.
      pd.Flags       = PD_RETURNDC | PD_ALLPAGES; 
      pd.nCopies     = 1;
      pd.nFromPage   = 0xFFFF; 
      pd.nToPage     = 0xFFFF; 
      pd.nMinPage    = 1; 
      pd.nMaxPage    = 0xFFFF; 

      if (PrintDlg(&pd) == TRUE) 
      {
         // Zero and then initialize the members of a DOCINFO structure.
         DOCINFO di = {0};
         di.cbSize = sizeof(DOCINFO);
         di.lpszDocName = ("Ogre");
         di.lpszOutput = (LPTSTR) NULL;
         di.lpszDatatype = (LPTSTR) NULL;
         di.fwType = 0;

         // Get the dimensions of the image and the page
         int iImgWidth = mWindow->getWidth();
         int iImgHeight = mWindow->getHeight();
         int iPageWidth = GetDeviceCaps(pd.hDC, HORZRES);
         int iPageHeight = GetDeviceCaps(pd.hDC, VERTRES);

         // Initialize the bitmap header.
         BITMAPINFO bmi;
         ZeroMemory(&bmi, sizeof(bmi));
         bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
         bmi.bmiHeader.biWidth = iImgWidth;
         bmi.bmiHeader.biHeight = iImgHeight;
         bmi.bmiHeader.biPlanes = 1;
         bmi.bmiHeader.biBitCount = 24;
         bmi.bmiHeader.biCompression = BI_RGB;

         // Create the surface.
         void* pBits;
         HDC hdcWindow = GetWindowDC((HWND)mWindowHnd);
         HBITMAP hBitmap = CreateDIBSection(hdcWindow, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);

         // Create memory dc for painting
         HDC hdcMemory = CreateCompatibleDC(hdcWindow);
         SelectObject(hdcMemory, hBitmap);

         // Copy render image to bitmap
         Ogre::PixelFormat format = Ogre::PF_BYTE_RGBA;
         size_t outBytesPerPixel = Ogre::PixelUtil::getNumElemBytes(format);
         unsigned char *data = new unsigned char [iImgWidth * iImgHeight * outBytesPerPixel];
         Ogre::Box extents(0, 0, iImgWidth, iImgHeight);
         Ogre::PixelBox mRenderWindowPixelBox(extents, format, data);
         mWindow->copyContentsToMemory(mRenderWindowPixelBox);
         Ogre::Image img;
         img.loadDynamicImage(static_cast<Ogre::uchar*>(mRenderWindowPixelBox.data), iImgWidth, iImgHeight, format);
         for(size_t y = 0; y < iImgHeight; y++)
            for(size_t x = 0; x < iImgWidth; x++)
            {
               Ogre::ColourValue cv = img.getColourAt(x, y, 0);
               Ogre::Real red = std::min<Ogre::Real>(255.0f, cv.r * 255.0f);
               Ogre::Real green = std::min<Ogre::Real>(255.0f, cv.g * 255.0f);
               Ogre::Real blue = std::min<Ogre::Real>(255.0f, cv.b * 255.0f);
               SetPixel(hdcMemory, x, y, RGB(red, green, blue));
            }

         // Begin a print job by calling the StartDoc function.
         StartDoc(pd.hDC, &di);

         // Inform the driver that the application is about to begin sending data.
         StartPage(pd.hDC);

         // Choose a pixel format that best matches that described in pfd
         int iPixelFormat = ChoosePixelFormat(pd.hDC, &pfd);
         SetPixelFormat(pd.hDC, iPixelFormat, &pfd);

         // Retrieve the information describing the surface.
         DIBSECTION ds;
         GetObject(hBitmap, sizeof(DIBSECTION), &ds);

         // Determine the scaling factors required retain the bitmap's original proportions.
         float fScaleX = (float)std::max<int>(iImgWidth, iPageWidth) / (float)std::min<int>(iImgWidth, iPageWidth);
         float fScaleY = (float)std::max<int>(iImgHeight, iPageHeight) / (float)std::min<int>(iImgHeight, iPageHeight);
         float fScale = std::min<float>(fScaleX, fScaleY);

         // Compute the coordinates of the upper left corner of the centered bitmap.
         int xLeft = ((iPageWidth / 2) - ((int)(((float)iImgWidth) * fScale)) / 2);
         int yTop = ((iPageHeight / 2) - ((int)(((float)iImgHeight) * fScale)) / 2);

         // Use StretchDIBits to scale the bitmap and maintain its original proportions
         StretchDIBits(pd.hDC, xLeft, yTop, (int)((float)iImgWidth * fScale), (int)((float)iImgHeight * fScale), 0, 0, iImgWidth, iImgHeight, ds.dsBm.bmBits, (LPBITMAPINFO)&ds.dsBmih, DIB_RGB_COLORS, SRCCOPY);

         // Inform the driver that the page is finished.
         EndPage(pd.hDC);
            
         // Inform the driver that document has ended.
         EndDoc(pd.hDC);

         // Cleanup
         DeleteDC(hdcMemory);
         ReleaseDC((HWND)mWindowHnd, hdcWindow);
         DeleteObject(hBitmap);
         DeleteDC(pd.hDC);
      }
   }

   return BaseApplication::keyPressed(arg);
}