Printing and Print Preview OpenGL in MFC

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 do not work with the SetPixelFormat() API function, it is not feasible to render an OpenGL scene to a printer directly. A widely used technique is to render an OpenGL scene to a DIB section, then copy it to a DC for printing or print preview. This article demonstrates this approach.

I use Document/View architecture in the demo. The view class CGLView is multiple-derived from CView and CGLObj. The later class bundles all the OpenGL implementation - including printing. The CGLView::OnPrint function wraps three methods as follows:

void CGLView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
{	CGLObj::OnPrint1(pDC, pInfo, this);
	// TODO: Render header here.
	OnDraw(pDC);
	// TODO: Render footer here.
	CGLObj::OnPrint2(pDC);
}

The CGLObj::OnPrint1 is designed to prepare for off-screen rendering. The major purposes of the method are to create a DIB section, and a memory RC. The memory RC will be used for OpenGL off-screen rendering later on. Following is the sequential of the method. After rendering, CGLObj::OnPrint2 copies the image in DIB section to the printer or preview DC, then releases the memory.

Preparing for OpenGL Off-Screen Rendering - OnPrint1()

  1. Determine the DIB size for either printing or print preview.
  2. The size of the DIB section depends on the size of the display device. For print previewing, I use screen resolution. For printing, I use the reduced 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 to excess of 100MB. That is why I reduce the printing resolution.

  3. Create DIB Section

    I call Windows function CreateDIBSection() to create a DIB section with this size mentioned above.

  4. Create memory DC, and associate it with the DIB section

    Calling Win32 function CreateCompatibleDC() creates a memory DC and returns a handle of it. Then I select the DIB section into a memory DC.

  5. Setup memory DC's pixel format

    Setting memory DC's pixel format is similar to setup a screen DC. The only difference is a flag specifying properties of the pixel buffer. I set PFD_DRAW_TO_WINDOW and PFD_DOUBLEBUFFER for the screen DC, but I need PFD_DRAW_TO_BITMAP for the memory DC. Thus, I made a helper function SetDCPixelFormat() to reuse this part of code.

  6. Create memory RC

    I use the above memory DC to create a memory RC for OpenGL off-screen rendering. The life of memory DC and RC will end when the printing is finished.

  7. Store old DC and RC

    The DC and RC for screen rendering should be stored. They will be restored after printing is finished.

  8. Make the memory RC current

    The function wglMakeCurrent() sets the memory RC current. From now, OpenGL will render with the memory RC, not screen RC. However, before rendering with the memory RC, I have to initialize the RC just like that working with screen RC.

  9. Set OpenGL state for memory RC

    The OpenGL state and frustum of the memory RC must be the same as the screen RC's so that we can get the same image rendered to the memory RC. I made SetOpenGLState() and SetFrustum() functions work for both RCs

  10. Create display list with the newly created memory RC.

    If you use OpenGL display list, you must create it again with the newly created memory RC. Keep in mind the display list is not reusable across RCs.

Following is the source code of CGLObj::OnPrint1

void CGLObj::OnPrint1(CDC* pDC, CPrintInfo* pInfo, CView* pView) 
{
 // 1. Determine the DIB size for either printing or print preview.
 CRect rcClient;
 pView->GetClientRect(&rcClient); 
 float fClientRatio = float(rcClient.Height())/rcClient.Width();

 // Get page size
 m_szPage.cx  = pDC->GetDeviceCaps(HORZRES);
 m_szPage.cy = pDC->GetDeviceCaps(VERTRES);

 CSize szDIB;
 if (pInfo->m_bPreview)
 {
  // Use screen resolution for preview.
  szDIB.cx = rcClient.Width();
  szDIB.cy = rcClient.Height();
 }
 else  // Printing
 {
  // Use higher resolution for printing.
  // Adjust size according screen's ratio.
  if (m_szPage.cy > fClientRatio*m_szPage.cx)
  {
   // View area is wider than Printer area
   szDIB.cx = m_szPage.cx;
   szDIB.cy = long(fClientRatio*m_szPage.cx);
  }
  else
  {
   // View area is narrower than Printer area
   szDIB.cx = long(float(m_szPage.cy)/fClientRatio);
   szDIB.cy = m_szPage.cy;
  }
 
  // Reduce the Resolution if the Bitmap size is too big.
  // Ajdust the maximum value, which is depends on printer's memory.
  // I use 20 MB. 
  while (szDIB.cx*szDIB.cy > 20e6)   
  {
   szDIB.cx = szDIB.cx/2;
   szDIB.cy = szDIB.cy/2;
  }
 }

 TRACE("Buffer size: %d x %d = %6.2f MB\n",
 szDIB.cx, szDIB.cy, szDIB.cx*szDIB.cy*0.000001);

 // 2. Create DIB Section
 memset(&m_bmi, 0, sizeof(BITMAPINFO));
 m_bmi.bmiHeader.biSize		= sizeof(BITMAPINFOHEADER);
 m_bmi.bmiHeader.biWidth		= szDIB.cx;
 m_bmi.bmiHeader.biHeight		= szDIB.cy;
 m_bmi.bmiHeader.biPlanes		= 1;
 m_bmi.bmiHeader.biBitCount	= 24;
 m_bmi.bmiHeader.biCompression	= BI_RGB;
 m_bmi.bmiHeader.biSizeImage	= szDIB.cx * szDIB.cy * 3; 

 HDC	hDC = ::GetDC(pView->m_hWnd);
 m_hDib = ::CreateDIBSection(hDC, &m_bmi, DIB_RGB_COLORS,
 &m_pBitmapBits, NULL, (DWORD)0);
 ::ReleaseDC(pView->m_hWnd, hDC);

 // 3. Create memory DC, and associate it with the DIB.
 m_hMemDC = ::CreateCompatibleDC(NULL);
 if (!m_hMemDC)
 {
  DeleteObject(m_hDib);
  m_hDib = NULL;
  return;
 }
 SelectObject(m_hMemDC, m_hDib);

 // 4. Setup memory DC's pixel format.
 if (!SetDCPixelFormat(m_hMemDC, 
                       PFD_DRAW_TO_BITMAP 
                       | PFD_SUPPORT_OPENGL 
                       | PFD_STEREO_DONTCARE))
 {
  DeleteObject(m_hDib);
  m_hDib = NULL;
  DeleteDC(m_hMemDC);
  m_hMemDC = NULL;
  return;
 }

 // 5. Create memory RC
 m_hMemRC = ::wglCreateContext(m_hMemDC);
 if (!m_hMemRC)
 {
  DeleteObject(m_hDib);
  m_hDib = NULL;
  DeleteDC(m_hMemDC);
  m_hMemDC = NULL;

  return;
 }

 // 6. Store old DC and RC
 m_hOldDC = ::wglGetCurrentDC();
 m_hOldRC = ::wglGetCurrentContext(); 

 // 7. Make the memory RC current
 ::wglMakeCurrent(m_hMemDC, m_hMemRC);

 // 8. Set OpenGL state for memory RC. 
 // The state is the same as the screen RC's.
 SetOpenGLState();
 ::glViewport(0, 0, szDIB.cx, szDIB.cy);
 SetFrustum();

 // 9. Create display list with the newly created memory RC
 CreateDisplayList(1);
}

Off-Screen Rendering

Now we can render with the memory RC. It is a common practice and a good idea to reuse the rendering scenario, but do not use double buffering for the memory RC.
Printing - OnPrint2()
This method handles the real "printing", and then free the memory.
  1. Release memory RC, and restore the old DC and RC.
  2. After rendering, we can delete the memory RC right away, and restore the old DC and RC for screen rendering.

  3. Calculate the target size according to the image size, and orientation of the paper.
  4. Now the image is stored in the memory, particularly in the DIB section. In general, the page size and orientation are different from the image's. We need to work out a target area on the page, to which the image will be copied. The target area should have the same orientation and elongation as the image in the DIB section.

  5. Stretch image to fit in the target size.
  6. The Win32 API StretchDIBits() copies the image in the DIB section to the destination, a handle to DC for either printing or print preview. The image is stretched to fit in the target area with the same elongation.

  7. Release memory.
  8. The job is finished. Release the memory for DIB section and memory DC now.

Following is the source code of CGLObj::OnPrint2

void CGLObj::OnPrint2(CDC* pDC) 
{
 // Now the image is in the DIB already. We don't need the 
 // memory RC anymore. We'll copy the image to the DC for 
 // printing or print preview.

 // 1. Release memory RC, and restore the old DC and RC.
 ::wglMakeCurrent(NULL, NULL);	
 ::wglDeleteContext(m_hMemRC);

 // Restore last DC and RC
 ::wglMakeCurrent(m_hOldDC, m_hOldRC);	

 // 2. Calculate the target size according to the image size, 
 //    and orientation of the paper.
 float fBmiRatio = 
 float(m_bmi.bmiHeader.biHeight) / m_bmi.bmiHeader.biWidth;

 CSize szTarget;  
 if (m_szPage.cx > m_szPage.cy)	 // Landscape page
 {
  if(fBmiRatio<1)	  // Landscape image
  {
   szTarget.cx = m_szPage.cx;
   szTarget.cy = long(fBmiRatio * m_szPage.cx);
  }
  else			  // Portrait image
  {
   szTarget.cx = long(m_szPage.cy/fBmiRatio);
   szTarget.cy = m_szPage.cy;
  }
 }
 else		    // Portrait page
 {
  if(fBmiRatio<1)	  // Landscape image
  {
   szTarget.cx = m_szPage.cx;
   szTarget.cy = long(fBmiRatio * m_szPage.cx);
  }
  else			  // Portrait image
  {
   szTarget.cx = long(m_szPage.cy/fBmiRatio);
   szTarget.cy = m_szPage.cy;
  }
 }

 CSize szOffset((m_szPage.cx - szTarget.cx) / 2,
                (m_szPage.cy - szTarget.cy) / 2);

 // 3. Stretch image to fit in the target size.
 int nRet = ::StretchDIBits(pDC->GetSafeHdc(),
                            szOffset.cx, szOffset.cy,
                            szTarget.cx, szTarget.cy,
                            0, 0,
                            m_bmi.bmiHeader.biWidth,
                            m_bmi.bmiHeader.biHeight,
                            GLubyte*) m_pBitmapBits,
                            m_bmi, 
                            DIB_RGB_COLORS, 
                            SRCCOPY);

 if(nRet == GDI_ERROR)
  TRACE0("Failed in StretchDIBits()");

 // 4. Release memory.
 DeleteObject(m_hDib);
 m_hDib = NULL;
 DeleteDC(m_hMemDC);	
 m_hMemDC = NULL;
 m_hOldDC = NULL;
}

Notice

This demo requires at least 16 bit colors. If your computer displays blank in print preview, check out the color setting in the Control Panel.

Not all video cards work well with OpenGL. When you involve any problem, try with machines with different video chards.

Downloads

Download source - 37 Kb


Comments

  • Hvilken ghd rettetang vil ikke skade hÃ¥ret om sommeren

    Posted by pletchernrr on 06/14/2013 03:49am

    [url=http://www.rettetangnewsnorge.com/falske-ghd]rettetang ghd[/url] Disse nyeste GHD strykejern kommer med en beskyttende vakt slik at du kan pop det når du er ferdig å beskytte dine rettetang. Glam svart vattert roll bag er det perfekte tilbehør til Red Metallic GHD. Samt å være helt varmebestandig på innsiden roll bag gir deg også en varme bevis matte for å tillate en trygg hvilested for køllene mens i bruk.Prøver å plukke den beste flatt jern eller glattejernet kan være skremmende. Hvordan vite hvilke som ikke vil bryte, ikke vil skade håret og vil arbeide raskest? Jeg har gjort jobben for deg, å forske på de beste flatt jern og hår straighteners på markedet. Du vil finne kostbar flatt jern argumenter kjærlighet og de mer rimelige flate strykejern som ikke vil bryte på deg. [url=http://www.rettetangnewsnorge.com/falske-ghd]rettetang ghd[/url] GHD rettetang beholder livfullheten og helsen til farget hår. Ved å sørge for at varmen er jevnt fordelt og aldri blir for høy, reduserer den unike fargebevaringsteknologi med IONTEC falming av fargen ved stylingen med 70 %*, og gjør at fargen blir betydelig mer glansfull.GHD stylingapparater gjør ikke håret ditt livløst og tørt. Tvert imot: de hindrer fuktighetstap fra stylingen ved å påføre unike satengioner til håret ditt! Disse negativt ladde ionene fester seg til det positivt ladde håret ditt og reduserer tørt, krusete hår og tiltrekker fuktighetspartikler fra lufta og sender dem dit hvor håret trenger det mest. [url=http://www.rettetangnewsnorge.com/]ghd rettetang pris[/url] Er dette sant, vil mange mennesker bare kjøpe en rettetang og ikke ekstra sjampo og produkter. Så, jeg er ikke helt sikker på hvordan dette fungerer som en langsiktig forretningsstrategi, en ting er sikkert skjønt er at forbrukeren er absolutt nyter godt av disse reduserte prices.Some kjente billigere merker er, Revlon og Remington, som tilbyr et ekstremt godt valg i billigere alternativer. Selv om de fortsatt ikke kan bli klassifisert som billig som du kan håpe, men de er fortsatt halv pris som GHD merkevare. De er også laget av keramiske og Tourmaline plater. Dette låser i fuktighet i håret og tørker ikke ut. Disse produktene er store som de er dobbelt bruk, for retting og curling, vil de ikke skade håret ditt og keramiske varer mye lenger enn aluminium gjør.

    Reply
  • GWtnP SNj ApqE

    Posted by jeZAYtodGA on 11/15/2012 03:49pm

    buy soma online buy cheap soma online no prescription - somanabolic muscle maximizer does it really work

    Reply
  • How to print a view containing both openGL and GDI drawings?

    Posted by RaviStrs on 11/09/2006 01:41am

    Along with OpenGL drawing, my application view have some other drawings on screen (pDC->LineTo,Rectangle etc..)to show some legends on screen. On printing I need to add the GDI drawings also to the existing DIB. Could you please suggest me some guide line to do that? I have used GDI commands in OnDraw() function of view class. Thank you.

    Reply
  • API only version

    Posted by Legacy on 09/02/2003 12:00am

    Originally posted by: Richard Jones

    Hello.

    I have been trying to create a strictly WIN32 api print preview/print routines.

    I like your example. I will try to use it to make what I want

    BUT!

    I need to better understand how to handle ratio's.

    My application displays images on the screen in a dialog's static box (picture screen).

    The user can press a button and select photo-sized images.

    1x2 wallet size (assuming)
    4x4 etc....

    I use the user entry *PixelsPerInch (GetDeviceCaps(LOGICALX...))

    So far the image displays the size they say and the printer routine displays it this way because it has a scaling ratio it uses like your code.

    But you use width/height for some ratio...?

    Anyway,

    The real problem starts when I try to do a print preview.

    First off I get the printer paper size in a rectangle from GetDeviceCaps(HORZRES and VERTRES) that gives me the full width and height of the printer paper.

    To display that rectangle in my picture screen I shrink the rectangle.

    I do it by multiplying the width and the hieght by .99 repeatedly in a loop untill both are <= the picture screens dementions. I keep track of the number of iterations. ITers.

    Understand that I want my users to be able to print the same picture more than one time on thier expensive photo quality sheet of paper. So I tile the image within the print preview rectangle.

    But before I tile the image I have to re-adjust the image size so it will be displayed proportionatley right in the print preview.

    Oddly enough the only way I could get the images to correlate with the shrinked print-preview rectangle was
    to divide ITers by 4; And then in a loop multiply the images
    Width and height by .99
    while(1)
    {
    W*=.99;
    H*=.99;
    if(x==(ITers/4))break;
    }

    So my printpreview looks proper. Now the user has the option to print what he sees.

    Here is where I get snagged.

    My printer routine works by capturing the image on screen in a designated rectangle. In this case the PrintPreview rectangle.

    It obtains the printer dc and then does the scaling by ratio for displaying what is on screen in the actual size on the printer. The scaling tries to keep the actual demensions.

    I guess I could simply increase the size that the printer wants to scale to, to a larger value by scaling the printerpreview rectangle ITers times by 1.1 and the images by (ITers/4) * 1.1.

    1.1 is the opposite of .99 correct?

    But I'm thinking maybe this seems kinda make shift junky.

    Maybe you know of a regular printpreview api style or could help me make one.

    Hope to hear from you.

    RJSoft

    Reply
  • Why it won't print texts

    Posted by Legacy on 06/14/2003 12:00am

    Originally posted by: sliu

    It works great with geometries, and the
    quality of printout is excellent. But
    the texts with the geometries were not
    printed.

    Reply
  • No right margin on landscape paper

    Posted by Legacy on 09/30/2002 12:00am

    Originally posted by: haibiao

    I reuse the code for GL view in a CStatic constrol. The print works pretty well on portrait paper. When page was set up at landscape, there was no right margin. Neither is the demo program.

    Reply
  • Cutting the OpenGL image

    Posted by Legacy on 12/09/2001 12:00am

    Originally posted by: dimik

    Is it possible to cut the image into several parts according to the ratio of screen and printer resolutions and then to pass each part into DIB section and work with it? If we scale a small part of the whole image we shall recieve better quality of printing, shalln`t we? Would you be so kind to prompt how to correct your code for it, please?

    Reply
  • Get some problem with glShareLists

    Posted by Legacy on 09/11/2001 12:00am

    Originally posted by: TOUSSET Olivier

    Hi,

    Thank you for you helpfull code.

    I did a first version of my program and everything was working fine (printing also).

    I did a second version where views are sharing a library-view, its works fine on the screen but not when printing.

    I made some modifications to your code to printing. I call
    glShareLists(m_library_viewRC,m_hMemRC) after creating m_hMemRC.

    But when rendering (for printing), the m_hMemRC does NOT
    know opengl lists created before with other views. If I draw a line directly (without using list) in m_hMemRC, I can see the result printed.

    Is there anything I didn't do ?

    I hope I'm clear.

    Thanks for your help.
    Best regards.

    TOUSSET Olivier Ing.


    Reply
  • Extraneous colour lines.

    Posted by Legacy on 07/10/2001 12:00am

    Originally posted by: Robert Webb

    It's great to see this example of how to print with OpenGL, and the code is actually readable!

    Anyway, one minor problem: in the print preview (and probably the print as well, I haven't tried) you will notice extra lines of colour here and there. This is because of the way StretchDIBits() works. The simple fix is to put the following line before the call to StretchDIBits():

    SetStretchBltMode(pDC->GetSafeHdc(), COLORONCOLOR);

    And that's it.
    Rob.

    Reply
  • Part of the windows screen got cut off

    Posted by Legacy on 06/25/2001 12:00am

    Originally posted by: Jimmy Wong

    I maximized the windows and saw part of the screen got cut off, such as on the right side and the bottom side. The cut-off part shows black. I had this problem on my program too. It seems like only happens to the double buffering mode. It appears on all type off Windows OS and any type of graphic card. I am using small font and 1024x768 resolution. I am still looking for a way to solve it.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • On-demand Event Event Date: September 17, 2014 Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this webcast and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the future state of your servers, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds