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
size_t mWindowHnd;
to BaseApplication class declaration.
To set this variable you just have to modify 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:
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:
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
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.
// 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:
// 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 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:
// 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
// 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.
// 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:
// 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.
// 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:
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); }