使用 OpenGL 和 MFC 进行打印和打印预览






4.87/5 (8投票s)
2000年6月3日

203686

3377
使用 DIB section 实现高分辨率的 OpenGL 打印。
引言
将 OpenGL 与 MFC 结合使用可以同时发挥两者的优势:快速的渲染能力和优雅的 GUI。然而,由于许多打印机驱动程序无法与 SetPixelFormat()
函数配合工作,因此无法直接将 OpenGL 场景渲染到打印机。本文提出了一种将 OpenGL 场景复制到 DIB section,然后进行打印的方法。
目前至少有三种 OpenGL 打印方法可用。
1. 截屏。这种方法很简单。您可以使用 Windows 的 BitBlt()
函数或 OpenGL 的 glReadPixel()
命令进行屏幕截图并打印。虽然这两个函数采用不同的方法,但结果是相同的。由于屏幕分辨率远低于打印机,因此无法获得高质量的图像。此外,如果窗口的客户区被另一个窗口(例如工具栏或无模式对话框)遮挡,则遮挡窗口会显示在结果中。
2. 使用增强型图元文件设备上下文。目前仅在 Windows NT 上有效,因此无法移植到 Windows 9x。
3. 使用离屏渲染。此技术可以打印高分辨率图像。但是,如果打印机的分辨率非常高且纸张尺寸非常大,则此方法在打印时需要大量内存来存储图像。一种解决方法是在需要大量内存时降低图像的分辨率。我将在本文中使用此方法。
OpenGL 离屏渲染
首先,我调用 Windows 函数 CreateDIBSection()
创建一个 DIB section,将其选入一个内存 DC,然后创建一个与内存 DC 关联的 OpenGL 内存 RC。渲染场景后,调用 StretchDIBits()
将图像复制到打印机 DC。通过这种方式,我获得了相当好的打印质量。
为了简化接口,实现被封装在 CGLObj
类中,只需要一个接口函数 OnPrint()
即可用于打印和打印预览。在内部,该方法封装了三个虚拟函数 OnPrint1()
、OnPrint2()
和 RenderScene()
,以使自定义更灵活。
头文件中的打印相关代码段如下:
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(); };
创建 DIB Section
DIB section 的大小取决于显示设备的大小。我调用 GetDeviceCaps()
来检索打印或打印预览显示设备的大小。在进行打印预览时,我使用屏幕分辨率;在进行打印时,我使用调整后的打印机分辨率。理想情况下,如果内存和速度不是问题,我希望能够使用打印机的完整分辨率。但是,对于 720 DPI 和信纸大小的打印机,DIB section 的内存很容易超过 100MB。这就是我降低打印分辨率的原因。然后,我使用此尺寸创建 DIB section。创建内存 DC 后,我将 DIB section 的句柄选择到 DC 中。
设置与内存 DC 关联的内存 RC
设置内存 RC 与设置屏幕 RC 类似。唯一的区别在于指定像素缓冲区属性的标志。我为屏幕 RC 设置了 PFD_DRAW_TO_WINDOW
和 PFD_DOUBLEBUFFER
,但需要为内存 RC 设置 PFD_DRAW_TO_BITMAP
。因此,我创建了一个辅助函数 SetDCPixelFormat()
来重用这部分代码。内存 RC 的 OpenGL 状态和视锥体也与屏幕 RC 相同。因此,我让 SetOpenGLState()
和 SetFrustum()
函数同时适用于两个 RC。由于显示列表不能跨 RC 重用,因此我必须再次调用 CreateDisplayList()
函数来使用内存 RC 创建它。如果使用纹理对象,则需要在此处使用新的 RC 重新创建它。
通过位图模式操作函数(如 glLineStipple()
)绘制的图像以及位图字体在进行打印和打印预览时可能会发生变化。您需要根据屏幕分辨率与调整后的打印机分辨率的比例重新创建它们。
将 DIB Section 复制到 DC
将场景渲染到 DIB section 后,我将 DIB section 中的图像复制到目标(即用于打印预览或打印的 DC)。在复制之前,我必须将 DIB section 的大小映射到目标的大小。页面可以是纵向或横向;图像也可以是纵向或横向。因此有四种映射情况。打印图像的大小存储在 szImageOnPage
中。StretchDIBits()
函数用于将 DIB section 中的图像复制并拉伸到用于打印预览或打印的 DC。
实现
CGLObj
类与 View 配合使用非常灵活。您可以使用聚合、关联、多重继承、虚拟继承或其他任何您喜欢的方式。您还可以将渲染实现与打印解耦,并创建两个类。这样可能更灵活。
在示例中,我使用了虚拟继承。我从 CView
和 CGLObj
派生出 CGLView
,其中 CGLObj
是一个虚拟基类。在 CGLView::OnPrint()
中,实现只有一行。
void CGLView::OnPrint(CDC* pDC, CPrintInfo* pInfo) { CGLObj::OnPrint(pDC, pInfo, this); }
为简单起见,我没有处理多个视图。如果需要多个视图,则需要处理 View 激活的 RC。