921f8f67d855a113c51fb448a6d2d4469f1465cc
[occt.git] / src / OpenGl / OpenGl_View_Print.cxx
1 // Created on: 2011-09-20
2 // Created by: Sergey ZERCHANINOV
3 // Copyright (c) 2011-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <OpenGl_GlCore11.hxx>
17
18 #include <OpenGl_FrameBuffer.hxx>
19 #include <TColStd_Array2OfReal.hxx>
20 #include <OpenGl_telem_util.hxx>
21 #include <Graphic3d_GraphicDriver.hxx>
22
23 #include <OpenGl_PrinterContext.hxx>
24 #include <OpenGl_Workspace.hxx>
25 #include <OpenGl_View.hxx>
26
27 #include <Standard_NotImplemented.hxx>
28
29 #if (defined(_WIN32) || defined(__WIN32__)) && defined(HAVE_FREEIMAGE)
30   #include <NCollection_Handle.hxx>
31   #include <FreeImagePlus.h>
32   #ifdef _MSC_VER
33     #pragma comment( lib, "FreeImage.lib" )
34     #pragma comment( lib, "FreeImagePlus.lib" )
35   #endif
36   typedef NCollection_Handle<fipImage> FipHandle;
37 #endif
38
39 #ifdef _WIN32
40
41 #ifndef HAVE_FREEIMAGE
42
43   // =======================================================================
44 // function : initBitmapBuffer
45 // purpose  : init device independent bitmap to hold printing data
46 // =======================================================================
47 static void initBitmapBuffer (const HDC theMemoryDC,
48                               HBITMAP &theMemoryBmp,
49                               const   Standard_Integer theBmpWidth,
50                               const   Standard_Integer theBmpHeight,
51                               VOID*   &theBufferPtr)
52 {
53   // define compatible bitmap
54   BITMAPINFO aBitmapData;
55   memset (&aBitmapData, 0, sizeof (BITMAPINFOHEADER));
56   aBitmapData.bmiHeader.biSize          = sizeof (BITMAPINFOHEADER);
57   aBitmapData.bmiHeader.biWidth         = theBmpWidth;
58   aBitmapData.bmiHeader.biHeight        = theBmpHeight;
59   aBitmapData.bmiHeader.biPlanes        = 1;
60   aBitmapData.bmiHeader.biBitCount      = 24;
61   aBitmapData.bmiHeader.biXPelsPerMeter = 0;
62   aBitmapData.bmiHeader.biYPelsPerMeter = 0;
63   aBitmapData.bmiHeader.biClrUsed       = 0;
64   aBitmapData.bmiHeader.biClrImportant  = 0;
65   aBitmapData.bmiHeader.biCompression   = BI_RGB;
66   aBitmapData.bmiHeader.biSizeImage     = 0;
67
68   // Create Device Independent Bitmap
69   theMemoryBmp = CreateDIBSection (theMemoryDC, &aBitmapData, DIB_RGB_COLORS,
70                                    &theBufferPtr, NULL, 0);
71 }
72
73 #else /* HAVE_FREEIMAGE */
74
75 // =======================================================================
76 // function : imagePasteDC
77 // purpose  : copy the data from image buffer to the device context
78 // =======================================================================
79 static bool imagePasteDC(HDC theDstDC,    FipHandle theImage, int theOffsetX,
80                          int theOffsetY,  int theWidth, int theHeight,
81                          int theLeft = 0, int theTop = 0)
82 {
83   // get image parameters
84   BITMAPINFO* aBitmapData = theImage->getInfo ();
85   SetStretchBltMode (theDstDC, STRETCH_HALFTONE);
86
87   // organize blocks data passing if memory isn't enough to pass all the data
88   // at once
89   int aLinesComplete = 0, aMaxBlockWidth = theHeight, aBlockWidth = 0,
90       aPassed        = 0, aInverseLine   = 0, aScan = 0;
91   BYTE *aDataPtr = 0;
92   while (aMaxBlockWidth >= 1 && aLinesComplete < theHeight)
93   {
94     // how much lines still to pass
95     aBlockWidth = theHeight - aLinesComplete;
96
97     // normalize count of lines to pass to maximum lines count at one pass.
98     if (aBlockWidth > aMaxBlockWidth)
99       aBlockWidth = aMaxBlockWidth;
100
101     // access image data at the start scan line, we need to calculate scan from
102     // the bottom of image (image is bottom-left, the src coord is top-left)
103     aInverseLine = theTop + aBlockWidth + aLinesComplete;
104     aScan = theImage->getHeight() - aInverseLine;
105     aDataPtr = theImage->getScanLine (aScan);
106     if (!aDataPtr)
107       return false;
108
109     // try to pass block to the device
110     if (aBlockWidth > 0)
111     {
112       // instead of banded output we provide blocked as it isn't always passed
113       // to printer as it is expected
114       aPassed = SetDIBitsToDevice (theDstDC, theOffsetX,
115                                    theOffsetY + aLinesComplete,
116                                    theWidth, aBlockWidth, theLeft, 0,
117                                    0, aBlockWidth,
118                                    aDataPtr, aBitmapData, DIB_RGB_COLORS);
119
120       // if result is bad, try to decrease band width
121       if (aPassed != aBlockWidth)
122       {
123         aMaxBlockWidth = aMaxBlockWidth >> 1;
124         aLinesComplete = 0;
125       }
126       else
127         aLinesComplete += aBlockWidth;
128     }
129   }
130
131   // check for total failure
132   if (aMaxBlockWidth < 1)
133     return false;
134
135   return true;
136 }
137
138 // =======================================================================
139 // function : imageStretchDC
140 // purpose  : copy pixels from image to dc by stretching them
141 // =======================================================================
142 static bool imageStretchDC(HDC theDstDC,   FipHandle theImage, int theOffsetX,
143                            int theOffsetY, int theWidth, int theHeight)
144 {
145   // access to raw image data
146   BYTE *aDataPtr = theImage->accessPixels ();
147   if (!aDataPtr)
148     return false;
149
150   // get image parameters
151   unsigned int widthPx    = theImage->getWidth ();
152   unsigned int heightPx   = theImage->getHeight ();
153   BITMAPINFO* aBitmapData = theImage->getInfo ();
154   SetStretchBltMode (theDstDC, STRETCH_HALFTONE);
155
156   // pass lines and check if operation is succesfull
157   int aPassed = 0;
158   aPassed = StretchDIBits (theDstDC, theOffsetX, theOffsetY, theWidth,
159                            theHeight, 0, 0, widthPx, heightPx, aDataPtr,
160                            aBitmapData, DIB_RGB_COLORS, SRCCOPY);
161
162   if ((unsigned)aPassed != heightPx)
163     return false;
164
165   return true;
166 }
167
168 #endif /* HAVE_FREEIMAGE */
169
170 // =======================================================================
171 // function : getNearestPowOfTwo
172 // purpose  : get the nearest power of two for theNumber
173 // =======================================================================
174 static GLsizei getNearestPowOfTwo (const GLsizei theNumber)
175 {
176   GLsizei aLast = 1;
177   for (GLsizei p2 = 1; p2 <= theNumber; aLast = p2, p2 <<= 1);
178   return aLast;
179 }
180
181 // =======================================================================
182 // function : getMaxFrameSize
183 // purpose  : get the maximum possible frame size
184 // =======================================================================
185 static void getMaxFrameSize(Standard_Integer& theWidth,
186                             Standard_Integer& theHeight)
187 {
188   GLsizei aMaxX, aMaxY;
189   GLint aVpDim[2];
190   GLint aTexDim = 2048;
191   glGetIntegerv (GL_MAX_VIEWPORT_DIMS, (GLint*) &aVpDim);
192   glGetIntegerv (GL_MAX_TEXTURE_SIZE, &aTexDim);
193   (aVpDim[0] >= aTexDim) ? aMaxX = (GLsizei) aTexDim :
194                            aMaxX = getNearestPowOfTwo((GLsizei)aVpDim[0]);
195   (aVpDim[1] >= aTexDim) ? aMaxY = (GLsizei) aTexDim :
196                            aMaxY = getNearestPowOfTwo((GLsizei)aVpDim[1]);
197
198   theWidth  = (Standard_Integer)aMaxX;
199   theHeight = (Standard_Integer)aMaxY;
200 }
201
202 // =======================================================================
203 // function : fitDimensionsRatio
204 // purpose  : calculate correct width/height ratio for theWidth and
205 //            theHeight parameters
206 // =======================================================================
207 static void fitDimensionsRatio (Standard_Integer& theWidth,
208                                 Standard_Integer& theHeight,
209                                 const Standard_Real theViewRatio)
210 {
211   // set dimensions in accordance with the viewratio
212   if (theHeight <  theWidth/theViewRatio)
213       theWidth  = (Standard_Integer)(theHeight*theViewRatio);
214
215   if (theWidth  <  theHeight*theViewRatio)
216       theHeight = (Standard_Integer)(theWidth/theViewRatio);
217 }
218
219 // =======================================================================
220 // function : initBufferStretch
221 // purpose  : calculate initialization sizes for frame buffer
222 //            when the stretch algorithm is selected
223 // =======================================================================
224 static void initBufferStretch (Standard_Integer& theFrameWidth,
225                                Standard_Integer& theFrameHeight,
226                                const int theViewWidth,
227                                const int theViewHeight)
228 {
229
230   // Calculate correct width/height for framebuffer
231   Standard_Real aViewRatio = (Standard_Real)theViewWidth/theViewHeight;
232   fitDimensionsRatio (theFrameWidth, theFrameHeight, aViewRatio);
233
234   // downscale the framebuffer if it is too large
235   Standard_Real aWidthRate  = (Standard_Real)theFrameWidth /theViewWidth;
236   Standard_Real aHeightRate = (Standard_Real)theFrameHeight/theViewHeight;
237
238   if ((aWidthRate > 1 && aHeightRate > 1 && aWidthRate >= aHeightRate) ||
239       (aWidthRate > 1 && aHeightRate <= 1))
240   {
241     theFrameWidth  = (Standard_Integer)(theFrameWidth /aWidthRate);
242     theFrameHeight = (Standard_Integer)(theFrameHeight/aWidthRate);
243   }
244   else if ((aWidthRate  > 1 && aHeightRate > 1 && aWidthRate < aHeightRate) ||
245            (aWidthRate <= 1 && aHeightRate > 1))
246   {
247     theFrameWidth  = (Standard_Integer)(theFrameWidth /aHeightRate);
248     theFrameHeight = (Standard_Integer)(theFrameHeight/aHeightRate);
249   }
250 }
251
252 // =======================================================================
253 // function : initBufferTiling
254 // purpose  : calculate initialization sizes for frame buffer
255 //            when the tile algorithm is selected
256 // =======================================================================
257 static void initBufferTiling (Standard_Integer& theFrameWidth,
258                               Standard_Integer &theFrameHeight,
259                               const int theViewWidth,
260                               const int theViewHeight)
261 {
262   // fit framebuffer into the printing area
263   if (theFrameWidth > theViewWidth)
264       theFrameWidth = theViewWidth;
265
266   if (theFrameHeight > theViewHeight)
267       theFrameHeight = theViewHeight;
268 }
269
270 #endif /* _WIN32 */
271
272 // =======================================================================
273 // function : Print
274 // purpose  :
275 // =======================================================================
276 Standard_Boolean OpenGl_View::Print (const Aspect_Handle    thePrinterDC,
277                                      const Standard_Boolean theToShowBackground,
278                                      const Standard_CString theFileName,
279                                      const Aspect_PrintAlgo thePrintAlgorithm,
280                                      const Standard_Real    theScaleFactor)
281 {
282 #ifdef _WIN32
283   if (!myWorkspace->Activate())
284   {
285     //MessageBox (NULL, "Print failed: can't setup the view for printing.",
286     //            "The operation couldn't be completed.", MB_OK);
287     return Standard_False;
288   }
289
290   Handle(OpenGl_Context) aCtx = myWorkspace->GetGlContext();
291
292   // printer page dimensions
293   HDC hPrnDC = (HDC) thePrinterDC;
294   int devWidth  = GetDeviceCaps (hPrnDC, HORZRES);
295   int devHeight = GetDeviceCaps (hPrnDC, VERTRES);
296
297   // if context is actually a memory dc, try to retrieve bitmap dimensions
298   // (memory dc could be used for testing purposes)
299   if (GetObjectType (hPrnDC) == OBJ_MEMDC)
300   {
301     // memory dc dimensions
302     BITMAP aBitmapInfo;
303     HBITMAP aMemoryBitmap = (HBITMAP) GetCurrentObject (hPrnDC, OBJ_BITMAP);
304     if (aMemoryBitmap)
305       if (GetObject (aMemoryBitmap, sizeof (BITMAP), &aBitmapInfo))
306       {
307         devWidth  = aBitmapInfo.bmWidth;
308         devHeight = aBitmapInfo.bmHeight;
309       }
310   }
311
312   Standard_Integer tempWidth  = (Standard_Integer) devWidth;
313   Standard_Integer tempHeight = (Standard_Integer) devHeight;
314
315   // view dimensions
316   int viewWidth  = myWindow->Width();
317   int viewHeight = myWindow->Height();
318   if (viewWidth == 0 || viewHeight == 0)
319   {
320     //MessageBox (NULL, "Print failed: can't setup the view for printing.",
321     //            "The operation couldn't be completed.", MB_OK);
322     return Standard_False;
323   }
324
325   // calculate correct width/height ratio
326   Standard_Real viewRatio = (Standard_Real)viewWidth/viewHeight;
327   fitDimensionsRatio(tempWidth, tempHeight, viewRatio);
328
329   // width and height for printing area
330   int width  = (int) (tempWidth  * theScaleFactor);
331   int height = (int) (tempHeight * theScaleFactor);
332
333   // device independent bitmap for the whole view
334 #ifdef HAVE_FREEIMAGE
335   FipHandle  aViewImage  = NULL;
336   BYTE*      aViewBuffer = NULL;
337 #else
338   HDC     hMemDC          = CreateCompatibleDC (hPrnDC);
339   HBITMAP hViewBitmap     = NULL;
340   HGDIOBJ hViewBitmapOld  = NULL;
341   VOID*   aViewBuffer    = NULL;
342 #endif
343
344   // Frame buffer initialization
345   OpenGl_FrameBuffer* aFrameBuffer = NULL;
346   OpenGl_FrameBuffer* aPrevBuffer  = myFBO.operator->();
347   Standard_Integer aFrameWidth (0),  aFrameHeight (0),
348                    aPrevBufferX (0), aPrevBufferY (0);
349
350   bool IsTiling = (thePrintAlgorithm == Aspect_PA_TILE);
351
352   // try to use existing frame buffer
353   if (aPrevBuffer)
354   {
355     GLsizei aPrevWidth  = aPrevBuffer->GetSizeX();
356     GLsizei aPrevHeight = aPrevBuffer->GetSizeY();
357     bool isUsable = false;
358
359     // check if its possible to use previous frame buffer
360     if (!IsTiling && aPrevWidth >= width && aPrevHeight >= height)
361     {
362       aFrameWidth  = (Standard_Integer) width;
363       aFrameHeight = (Standard_Integer) height;
364       isUsable = true;
365     }
366     else if (IsTiling)
367     {
368       // use previous frame buffer with its dimensions
369       aFrameWidth  = aPrevWidth;
370       aFrameHeight = aPrevHeight;
371       isUsable = true;
372     }
373
374     // if it is enough memory for image paste dc operation
375     if (isUsable)
376     {
377 #ifdef HAVE_FREEIMAGE
378       // try to allocate fipImage and necessary resources
379       fipImage* anImagePtr = new fipImage (FIT_BITMAP, aFrameWidth,
380                                            aFrameHeight, 24);
381
382       // if allocated successfully
383       if (anImagePtr->isValid())
384       {
385         aViewImage  = anImagePtr;
386         aViewBuffer = aViewImage->accessPixels ();
387       }
388       else
389         delete anImagePtr;
390
391       if (!aViewBuffer)
392       {
393         isUsable = false;
394         aViewBuffer = NULL;
395         aViewImage  = NULL;
396       }
397 #else
398       // try to allocate compatible bitmap and necessary resources
399       initBitmapBuffer (hMemDC, hViewBitmap,
400                         aFrameWidth, aFrameHeight, aViewBuffer);
401       if (!aViewBuffer)
402       {
403         isUsable = false;
404         if (hViewBitmap)
405           DeleteObject (hViewBitmap);
406         hViewBitmap = NULL;
407       }
408       else
409         hViewBitmapOld = SelectObject (hMemDC, hViewBitmap);
410 #endif
411     }
412
413     // use previous frame buffer
414     if (isUsable)
415     {
416       aPrevBufferX = aPrevWidth;
417       aPrevBufferY = aPrevHeight;
418       aFrameBuffer = aPrevBuffer;
419       aFrameBuffer->ChangeViewport (aFrameWidth, aFrameHeight);
420     }
421   }
422
423   // if previous buffer cannot be used, try to init a new one
424   if (!aFrameBuffer)
425   {
426     aFrameBuffer = new OpenGl_FrameBuffer();
427
428     // try to create the framebuffer with the best possible size
429     Standard_Integer aMaxWidth(0), aMaxHeight(0);
430     getMaxFrameSize (aMaxWidth, aMaxHeight);
431     while (aMaxWidth > 1 && aMaxHeight > 1)
432     {
433       aFrameWidth  = aMaxWidth;
434       aFrameHeight = aMaxHeight;
435
436       // calculate dimensions for different printing algorithms
437       if (!IsTiling)
438         initBufferStretch (aFrameWidth, aFrameHeight, width, height);
439       else
440         initBufferTiling (aFrameWidth, aFrameHeight, width, height);
441
442       // try to initialize framebuffer
443       if (aFrameBuffer->Init (aCtx, aFrameWidth, aFrameHeight, GL_RGBA8, GL_DEPTH24_STENCIL8))
444       {
445 #ifdef HAVE_FREEIMAGE
446         // try to allocate fipImage and necessary resources
447         fipImage* anImagePtr = new fipImage (FIT_BITMAP, aFrameWidth,
448                                             aFrameHeight, 24);
449
450         // if allocated successfully
451         if (anImagePtr->isValid())
452         {
453           aViewImage  = anImagePtr;
454           aViewBuffer = aViewImage->accessPixels ();
455         }
456         else
457           delete anImagePtr;
458
459         if (!aViewBuffer)
460         {
461           aFrameBuffer->Release (aCtx.operator->());
462           aViewBuffer = NULL;
463           aViewImage  = NULL;
464         }
465         else
466           break;
467 #else
468         // try to allocate compatible bitmap and necessary resources
469         initBitmapBuffer (hMemDC, hViewBitmap,
470                           aFrameWidth, aFrameHeight, aViewBuffer);
471         if (!aViewBuffer)
472         {
473           if (hViewBitmap)
474             DeleteObject (hViewBitmap);
475           aFrameBuffer->Release (aCtx.operator->());
476           hViewBitmap = NULL;
477         }
478         else
479         {
480           hViewBitmapOld = SelectObject (hMemDC, hViewBitmap);
481           break;
482         }
483 #endif
484       }
485
486       // not initialized, decrease dimensions
487       aMaxWidth  = aMaxWidth  >> 1;
488       aMaxHeight = aMaxHeight >> 1;
489     }
490
491     // check if there are proper dimensions
492     if (aMaxWidth <= 1 || aMaxHeight <= 1)
493     {
494       MessageBox (NULL, "Print failed: can't allocate buffer for printing.",
495                         "The operation couldn't be completed.", MB_OK);
496
497       if (aFrameBuffer)
498         delete aFrameBuffer;
499 #ifndef HAVE_FREEIMAGE
500       if (hMemDC)
501         DeleteDC (hMemDC);
502 #endif
503
504       return Standard_False;
505     }
506   }
507
508   // setup printing context and viewport
509   myWorkspace->PrinterContext() = new OpenGl_PrinterContext();
510   myWorkspace->PrinterContext()->SetLayerViewport ((GLsizei )aFrameWidth, (GLsizei )aFrameHeight);
511   GLint aViewPortBack[4];
512   GLint anAlignBack = 1;
513   glGetIntegerv (GL_VIEWPORT, aViewPortBack);
514   glGetIntegerv (GL_PACK_ALIGNMENT, &anAlignBack);
515   glPixelStorei (GL_PACK_ALIGNMENT, 4);
516
517   // start document if the printer context is not actually a memory dc
518   // (memory dc could be used for testing purposes)
519   DOCINFO di;
520   if (GetObjectType (hPrnDC) == OBJ_DC)
521   {
522     // Initialize printing procedure
523     di.cbSize = sizeof(DOCINFO);
524     di.lpszDocName = "Open Cascade Document - print v3d view";
525     di.lpszOutput = theFileName;
526
527     // if can't print the document
528     if (StartDoc (hPrnDC, &di) <= 0 || StartPage (hPrnDC) <= 0)
529     {
530       MessageBox (NULL, "Print failed: printer can't start operation.",
531                   "The operation couldn't be completed.", MB_OK);
532 #ifndef HAVE_FREEIMAGE
533       if (hViewBitmap)
534       {
535         SelectObject (hMemDC, hViewBitmapOld);
536         DeleteObject (hViewBitmap);
537       }
538       DeleteDC (hMemDC);
539 #endif
540
541       myWorkspace->PrinterContext().Nullify();
542       return Standard_False;
543     }
544   }
545
546   // calculate offset for centered printing
547   int aDevOffx = (int)(devWidth  - width) /2;
548   int aDevOffy = (int)(devHeight - height)/2;
549
550   // operation complete flag
551   bool isDone = true;
552
553   // Set up status for printing
554   if (!theToShowBackground)
555   {
556     myWorkspace->NamedStatus |= OPENGL_NS_WHITEBACK;
557   }
558
559   // switch to mono camera for image dump
560   const Graphic3d_Camera::Projection aProjectType = myCamera->ProjectionType() != Graphic3d_Camera::Projection_Stereo
561                                                   ? myCamera->ProjectionType()
562                                                   : Graphic3d_Camera::Projection_Perspective;
563   if (!IsTiling)
564   {
565     myWorkspace->PrinterContext()->SetScale ((GLfloat )aFrameWidth /viewWidth, (GLfloat )aFrameHeight/viewHeight);
566     redraw (aProjectType, aFrameBuffer);
567     if (!myTransientDrawToFront)
568     {
569       // render to FBO only if allowed to render to back buffer
570       myBackBufferRestored = Standard_True;
571       myIsImmediateDrawn   = Standard_False;
572       redrawImmediate (aProjectType, NULL, aFrameBuffer);
573       myBackBufferRestored = Standard_False;
574       myIsImmediateDrawn   = Standard_False;
575     }
576     glReadPixels (0, 0, aFrameWidth, aFrameHeight,
577                   GL_BGR_EXT, GL_UNSIGNED_BYTE, (GLvoid* )aViewBuffer);
578
579     // copy result to the printer device and check for errors
580 #ifdef HAVE_FREEIMAGE
581     if (!aViewImage->rescale(width, height, FILTER_BICUBIC) ||
582         !imagePasteDC (hPrnDC, aViewImage, aDevOffx, aDevOffy, width, height))
583       isDone = imageStretchDC (hPrnDC, aViewImage, aDevOffx, aDevOffy,
584                                width, height);
585 #else
586     if (width > aFrameWidth && height > aFrameHeight)
587     {
588       SetStretchBltMode (hPrnDC, STRETCH_HALFTONE);
589       isDone = (StretchBlt (hPrnDC, aDevOffx, aDevOffy, width, height,
590                             hMemDC, 0, 0, aFrameWidth, aFrameHeight, SRCCOPY) != 0); // to avoid warning C4800
591     }
592     else
593     {
594       isDone = (BitBlt (hPrnDC, aDevOffx, aDevOffy, width, height,
595                         hMemDC, 0, 0, SRCCOPY) != 0); // to avoid warning C4800
596     }
597 #endif
598   }
599   else
600   {
601     // calculate total count of frames and cropping size
602     Standard_Integer aPxCropx = 0;
603     Standard_Integer aPxCropy = 0;
604     Standard_Integer aTotalx =
605                      (Standard_Integer)floor ((float)width /aFrameWidth);
606     Standard_Integer aTotaly =
607                      (Standard_Integer)floor ((float)height/aFrameHeight);
608     if (width %aFrameWidth != 0)
609     {
610       aPxCropx = (aFrameWidth - width%aFrameWidth)/2;
611       aTotalx++;
612     }
613     if (height%aFrameHeight != 0)
614     {
615       aPxCropy = (aFrameHeight - height%aFrameHeight)/2;
616       aTotaly++;
617     }
618
619     int anOddPixelx = (width %aFrameWidth) %2;
620     int anOddPixely = (height%aFrameHeight)%2;
621
622     // calculate scale factor for full frames
623     Standard_Real aScalex = (Standard_Real)width /aFrameWidth;
624     Standard_Real aScaley = (Standard_Real)height/aFrameHeight;
625
626     // calculate and set the text scaling factor for printing context
627     GLfloat aScaleRatex = (GLfloat)aFrameWidth /viewWidth;
628     GLfloat aScaleRatey = (GLfloat)aFrameHeight/viewHeight;
629     myWorkspace->PrinterContext()->SetScale (aScaleRatex * (GLfloat )aScalex, aScaleRatey * (GLfloat )aScaley);
630
631     // initialize projection matrix for printer context
632     TColStd_Array2OfReal aProj (0, 3, 0, 3);
633     Standard_Real aDef = 0;
634     aProj.Init (aDef);
635     aProj(2,2) = 1.0;
636     aProj(3,3) = 1.0;
637
638     // projection matrix offsets for printer context
639     // offsets are even numbers
640     Standard_Real aOffsetx(0), aOffsety(0);
641     aOffsetx = -(aTotalx-1);
642     aOffsety = -(aTotaly-1);
643
644     // rect of frame image that will be copied
645     // and coordinates in view image where to put it
646     Standard_Integer aLeft = 0, aRight = 0, aBottom = 0, aTop = 0;
647     Standard_Integer aSubLeft = (Standard_Integer)aDevOffx;
648     Standard_Integer aSubTop  = (Standard_Integer)aDevOffy;
649
650     // draw sequence of full frames
651     for (int i = 0; i < aTotalx; i++)
652     {
653       // offsets are even numbers
654       aOffsety = -(aTotaly-1);
655       aSubTop  =  (Standard_Integer)aDevOffy;
656
657       // calculate cropped frame rect
658       aLeft  = (i == 0) ? aPxCropx : 0;
659       aRight = (i == aTotalx-1) ? aFrameWidth-(aPxCropx+anOddPixelx) :
660                                   aFrameWidth;
661
662       for (int j = 0; j < aTotaly; j++)
663       {
664         // no offset for single frames
665         aProj(3,0) = (aTotalx == 1) ? 0 : -aOffsetx;
666         aProj(3,1) = (aTotaly == 1) ? 0 :  aOffsety;
667
668         // set projection matrix
669         aProj(0,0) = aScalex;
670         aProj(1,1) = aScaley;
671         myWorkspace->PrinterContext()->SetProjTransformation (aProj);
672
673         // calculate cropped frame rect
674         aTop    = (j == 0)         ? aPxCropy : 0;
675         aBottom = (j == aTotaly-1) ? aFrameHeight-(aPxCropy+anOddPixely) :
676                                      aFrameHeight;
677
678         // draw to the offscreen buffer and capture the result
679         redraw (aProjectType, aFrameBuffer);
680         if (!myTransientDrawToFront)
681         {
682           // render to FBO only if forces to render to back buffer
683           myBackBufferRestored = Standard_True;
684           myIsImmediateDrawn   = Standard_False;
685           redrawImmediate (aProjectType, NULL, aFrameBuffer);
686           myBackBufferRestored = Standard_False;
687           myIsImmediateDrawn   = Standard_False;
688         }
689         glReadPixels (0, 0, aFrameWidth, aFrameHeight,
690                       GL_BGR_EXT, GL_UNSIGNED_BYTE, (GLvoid* )aViewBuffer);
691 #ifdef HAVE_FREEIMAGE
692         // cut out pixels that are out of printing area
693         isDone = imagePasteDC (hPrnDC, aViewImage, aSubLeft, aSubTop,
694                                aRight-aLeft, aBottom-aTop, aLeft, aTop);
695 #else
696         isDone = (BitBlt (hPrnDC, aSubLeft, aSubTop, aRight-aLeft, aBottom-aTop,
697                           hMemDC, aLeft, aTop, SRCCOPY) != 0); // to avoid warning C4800
698 #endif
699
700         // stop operation if errors
701         if (!isDone)
702           break;
703
704         // calculate new view offset for y-coordinate
705         aOffsety += 2.0;
706         aSubTop  += aBottom-aTop;
707       }
708
709       // stop operation if errors
710       if (!isDone)
711         break;
712
713       // calculate new view offset for x-coordinate
714       aOffsetx += 2.0;
715       aSubLeft += aRight-aLeft;
716     }
717   }
718
719   // complete printing or indicate an error
720   if (GetObjectType (hPrnDC) == OBJ_DC && isDone == true)
721   {
722     EndPage (hPrnDC);
723     EndDoc (hPrnDC);
724   }
725   else if (isDone == false)
726   {
727     MessageBox (NULL, "Print failed: insufficient memory or spool error.\nPlease use smaller printer resolution.",
728                 "The opeartion couldn't be completed.", MB_OK);
729     if (GetObjectType (hPrnDC) == OBJ_DC)
730       AbortDoc (hPrnDC);
731   }
732
733   // return OpenGl to the previous state
734   glPixelStorei (GL_PACK_ALIGNMENT, anAlignBack);
735   aFrameBuffer->UnbindBuffer (aCtx);
736   glViewport (aViewPortBack[0], aViewPortBack[1],
737               aViewPortBack[2], aViewPortBack[3]);
738   if (aPrevBuffer)
739   {
740     aPrevBuffer->ChangeViewport (aPrevBufferX, aPrevBufferY);
741   }
742   else
743   {
744     aFrameBuffer->Release (aCtx.operator->());
745     delete aFrameBuffer;
746   }
747
748   // delete resources
749 #ifndef HAVE_FREEIMAGE
750   if (hViewBitmap)
751   {
752     SelectObject (hMemDC, hViewBitmapOld);
753     DeleteObject (hViewBitmap);
754   }
755   DeleteDC (hMemDC);
756 #endif
757
758   // Reset status after printing
759   myWorkspace->NamedStatus &= ~OPENGL_NS_WHITEBACK;
760   myWorkspace->PrinterContext().Nullify();
761   return (Standard_Boolean) isDone;
762
763 #else // not _WIN32
764   (void )thePrinterDC;
765   (void )theToShowBackground;
766   (void )theFileName;
767   (void )thePrintAlgorithm;
768   (void )theScaleFactor;
769   Standard_NotImplemented::Raise ("OpenGl_View::Print is implemented only on Windows");
770   myWorkspace->PrinterContext().Nullify();
771   return Standard_False;
772 #endif
773 }