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:
Collapse
class CGLObj
{
public:
CGLObj();
...
// Printing
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;
// Operations
protected:
virtual bool InitializeOpenGL(CView* pView);
virtual void SetFrustum(); // For both screen and printing RCs
virtual bool SetDCPixelFormat(HDC hDC, DWORD dwFlags); // For both screen and printing RCs
virtual void SetOpenGLState(); // For both screen and printing RCs
virtual void CreateDisplayList(UINT nList); // For both screen and printing RCs
virtual void RenderScene(); // For both screen and printing RCs
virtual void OnSize(int cx, int cy);
virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo, CView* pView); // Interface function
virtual void OnPrint1(CDC* pDC, CPrintInfo* pInfo, CView* pView);
virtual void OnPrint2(CDC* pDC);
// Implementation
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.