Download demo project - 32 Kb
Introduction
Wrapping OpenGL with MFC can take the advantages of both APIs: fast rendering and an elegant GUI.
However, due to the fact that many printer drivers can't work with the SetPixelFormat()
function, it is not feasible to render an OpenGL scene to a printer directly. This article presents
an approach to copy an OpenGL scene to a DIB section, and then to print it out.
There are at least 3 available approaches for OpenGL printing.
1. Capture the screen. This method is easy. You can use the Windows BitBlt()
function
or the OpenGL glReadPixel()
command to make a screen shot and print it out. Although these
two functions use different approaches, you get the same result. Since the resolution of a screen is
much lower than a printer's, you can't get a picture with decent quality. Furthermore, if the window's
client area is clipped by another window, say, a toolbar or a modeless dialog box, the clipping window
will show in the result.
2. Use an enhanced metafile device context. Currently it works only on Windows NT, therefore it is
not portable to Windows 9x.
3. Use off-screen rendering. This technique can print an image with high resolution.
However, if the printer's resolution is very high and the page size is very large,
this approach requires large memory to hold the image during printing. A workaround is to
reduce the image's resolution when large memory is required. I will use this method
in this article.
OpenGL Off-Screen Rendering
First, I call Windows function CreateDIBSection()
to create a DIB section,
select it into a memory DC, then create an OpenGL memory RC associated with the memory DC.
After rendering the scene, call StretchDIBits()
to copy the image to the
printer DC. In this way, I get reasonably good print quality.
To simplify the interface, the implementation is wrapped in CGLObj
class, and only one
interface function, OnPrint()
, is needed for printing and print preview. Internally, the
method wraps three virtual functions OnPrint1()
, OnPrint2()
, and
RenderScene()
to make the customization flexible.
The printing-related code segment in the header file is as follows:
class CGLObj
{
public:
CGLObj();
...
HDC m_hOldDC;
HDC m_hMemDC;
HGLRC m_hOldRC;
HGLRC m_hMemRC;
BITMAPINFO m_bmi;
LPVOID m_pBitmapBits;
HBITMAP m_hDib;
HGDIOBJ m_hOldDib;
CSize m_szPage;
protected:
virtual bool InitializeOpenGL(CView* pView);
virtual void SetFrustum();
virtual bool SetDCPixelFormat(HDC hDC, DWORD dwFlags);
virtual void SetOpenGLState();
virtual void CreateDisplayList(UINT nList);
virtual void RenderScene();
virtual void OnSize(int cx, int cy);
virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo, CView* pView);
virtual void OnPrint1(CDC* pDC, CPrintInfo* pInfo, CView* pView);
virtual void OnPrint2(CDC* pDC);
protected:
virtual ~CGLObj();
};
Create DIB Section
The size of the DIB section depends on the size of the display device. I call
GetDeviceCaps()
to retrieve the size of the display device for either printing or
print preview. When print previewing, I use screen resolution; when printing, I use the adjusted
printer's resolution. Ideally, I hope I can use the printer's full resolution if memory and
speed is not a problem. However, for a printer with 720 DPI and using letter-sized paper, the
memory of a DIB section is easily in excess of 100MB. That is why I reduce the printing resolution.
I then use this size to create a DIB section. After I create the memory DC, I select the handle of
the DIB section to the DC.
Setup Memory RC Associate to Memory DC
Setup a memory RC is similar to setup a screen RC. The only difference is the flag, which specifies
properties of the pixel buffer. I set PFD_DRAW_TO_WINDOW
and PFD_DOUBLEBUFFER
for the screen RC, but I need PFD_DRAW_TO_BITMAP
for the memory RC. Thus, I made a helper
function SetDCPixelFormat()
to reuse this part of code. The OpenGL state and frustum of
the memory RC are also the same as the screen RC. I therefore made SetOpenGLState()
and
SetFrustum()
functions work for both RCs. Due to the fact that the display list is not
reusable across RCs, I have to call the CreateDisplayList()
function to create it again
with the memory RC. If you use a texture object, you need to create it again with the new RC here.
The image drawn by bit pattern manipulation functions, such as glLineStipple()
, and
the bitmap font may be changed when you perform printing and print preview. You need to create them
again based on the ratio of the screen and adjusted printer resolutions.
Copy DIB Section to DC
After rendering the scene to the DIB section, I copy the image in the DIB section to the destination,
which is a DC for either print preview or printing. Before copying, I have to map the size of the DIB
section to the size of the destination. The page can be either portrait or landscape; and the image can
also be either portrait or landscape. So there are four mapping cases. The size of the printing image is
stored in szImageOnPage
. The StretchDIBits()
function is used to copy and
stretch the image in the DIB section to the DC for either print preview or printing.
Implementation
The CGLObj
class is very flexible to use with View. You can use aggregation, acquaintance,
multiple-inheritance, virtual-inheritance, or whatever you like. You can also uncouple the rendering
implementation from printing, and make two classes. This may be more flexible.
In the example, I use virtual-inheritance. I derive CGLView
from CView
and CGLObj
, in which CGLObj
is a virtual base class. The implementation is
only one line in CGLView::OnPrint()
:
void CGLView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
CGLObj::OnPrint(pDC, pInfo, this);
}
For simplicity, I do not handle multiple views. You need to handle the RC for View activation if you
need multiple views.