1 // OpenSG example: HiResImage.cpp
3 // This example shows how to create a high resolution screen shot from an OpenSG
5 // Two independent solutions are provided. The first one uses a GrabForeground
6 // object and the second one a FrameBufferObject (FBO) for image generation.
8 // This example installs a tiled rendering mechanism which allows the generation
9 // of really huge images. The resulting hires image is not stored in the main
10 // memory but is written to disk in blocks.
12 // Specifically demonstrated is the use of the following classes:
14 // - FrameBufferObject,
17 // - TileCameraDecorator
18 // - TileableBackground
21 // This examples a model file provided on the command line or a torus if no file
25 // a) mouse => standard navigator
27 // '1': take a high resolution snapshot with the GrabForeground class
28 // '2': take a high resolution snapshot using the FBO technique
37 #include <boost/tuple/tuple.hpp>
39 #ifdef OSG_BUILD_ACTIVE
42 #include <OSGConfig.h>
43 #include <OSGSimpleGeometry.h>
44 #include <OSGGLUTWindow.h>
45 #include <OSGGradientBackground.h>
46 #include <OSGGrabForeground.h>
47 #include <OSGSimpleSceneManager.h>
48 #include <OSGSceneFileHandler.h>
49 #include <OSGAction.h>
50 #include <OSGFrameBufferObject.h>
51 #include <OSGRenderBuffer.h>
52 #include <OSGSimpleStage.h>
53 #include <OSGPassiveViewport.h>
54 #include <OSGTileCameraDecorator.h>
55 #include <OSGTileableBackground.h>
56 #include <OSGVisitSubTree.h>
60 #include <OpenSG/OSGGLUT.h>
61 #include <OpenSG/OSGConfig.h>
62 #include <OpenSG/OSGSimpleGeometry.h>
63 #include <OpenSG/OSGGLUTWindow.h>
64 #include <OpenSG/OSGGradientBackground.h>
65 #include <OpenSG/OSGGrabForeground.h>
66 #include <OpenSG/OSGSimpleSceneManager.h>
67 #include <OpenSG/OSGSceneFileHandler.h>
68 #include <OpenSG/OSGAction.h>
69 #include <OpenSG/OSGFrameBufferObject.h>
70 #include <OpenSG/OSGRenderBuffer.h>
71 #include <OpenSG/OSGSimpleStage.h>
72 #include <OpenSG/OSGPassiveViewport.h>
73 #include <OpenSG/OSGTileCameraDecorator.h>
74 #include <OpenSG/OSGTileableBackground.h>
75 #include <OpenSG/OSGVisitSubTree.h>
76 #include <OpenSG/OSGImage.h>
79 OSG_USING_NAMESPACE
; // just for convenience but not recommended
82 // function forward declarations
84 static void cleanup(void);
85 static void display(void);
86 static void reshape(int w
, int h
);
87 static void mouse(int button
, int state
, int x
, int y
);
88 static void motion(int x
, int y
);
89 static void keyboard(unsigned char k
, int, int);
90 static void writeHiResScreenShot(const char* name
, UInt32 width
, UInt32 height
);
91 static void writeHiResScreenShotFBO(const char* name
, UInt32 width
, UInt32 height
);
92 static bool writePNMImagesHeader(const std::vector
<ImageUnrecPtr
>& vecImages
, UInt32 width
, UInt32 height
, std::ostream
& out
);
93 static bool writePNMImagesData(const std::vector
<ImageUnrecPtr
>& vecImages
, std::ostream
& out
);
94 static int setupGLUT(int *argc
, char *argv
[]);
95 static int doMain(int argc
, char *argv
[]);
98 // global state of example
100 SimpleSceneManagerRefPtr mgr
;
102 GLUTWindowRefPtr win
;
103 const char* output_file
= "grabforeground_image.ppm";
104 const char* output_file_fbo
= "fbo_image.ppm";
107 // Size factors of the output image with respect to the window dimensions.
108 // The actual image size does account for the aspect ratio of the window.
110 float fw
= 2.5f
; // multiplication factor for the window width
111 float fh
= 2.3f
; // multiplication factor for the window height
113 static void cleanup(void)
121 static void display(void)
127 static void reshape(int w
, int h
)
133 static void mouse(int button
, int state
, int x
, int y
)
136 mgr
->mouseButtonRelease(button
, x
, y
);
138 mgr
->mouseButtonPress(button
, x
, y
);
143 static void motion(int x
, int y
)
145 mgr
->mouseMove(x
, y
);
149 static void keyboard(unsigned char k
, int, int)
151 UInt32 winWidth
= win
->getWidth();
152 UInt32 winHeight
= win
->getHeight();
154 UInt32 width
= fw
* winWidth
;
155 UInt32 height
= fh
* winHeight
;
166 std::exit(EXIT_SUCCESS
);
172 std::cout
<< "Creating screenshot (Grab FG) "
173 << width
<< "x" << height
<< " in '" << output_file
175 writeHiResScreenShot(output_file
, width
, height
);
180 std::cout
<< "Creating screenshot (FBO) "
181 << width
<< "x" << height
<< " in '" << output_file_fbo
183 writeHiResScreenShotFBO(output_file_fbo
, width
, height
);
193 static int setupGLUT(int *argc
, char *argv
[])
195 glutInit(argc
, argv
);
197 glutInitDisplayMode(GLUT_RGB
| GLUT_DEPTH
| GLUT_STENCIL
| GLUT_DOUBLE
);
199 int winid
= glutCreateWindow("OpenSG");
201 glutReshapeFunc(reshape
);
202 glutDisplayFunc(display
);
203 glutIdleFunc(display
);
204 glutMouseFunc(mouse
);
205 glutMotionFunc(motion
);
206 glutKeyboardFunc(keyboard
);
214 static int doMain(int argc
, char *argv
[])
216 preloadSharedObject("OSGFileIO");
217 preloadSharedObject("OSGImageFileIO");
221 int winid
= setupGLUT(&argc
, argv
);
223 win
= GLUTWindow::create();
224 win
->setGlutId(winid
);
229 FWARNING(("No file given!\n"));
230 FWARNING(("Supported file formats:\n"));
232 std::list
<const char*> suffixes
;
233 SceneFileHandler::the()->getSuffixList(suffixes
);
235 for(std::list
<const char*>::iterator it
= suffixes
.begin();
236 it
!= suffixes
.end();
239 FWARNING(("%s\n", *it
));
242 scene
= makeTorus(.5, 2, 16, 16);
246 scene
= SceneFileHandler::the()->read(argv
[1]);
251 mgr
= SimpleSceneManager::create();
254 mgr
->setRoot (scene
);
256 GradientBackgroundUnrecPtr background
= GradientBackground::create();
257 background
->addLine(Color3f(0,0,0), 0);
258 background
->addLine(Color3f(1,1,1), 1);
260 Viewport
* viewport
= win
->getPort(0);
261 viewport
->setBackground(background
);
271 int main(int argc
, char *argv
[])
273 int ret
= doMain(argc
, argv
);
285 // scan for root node
287 static Node
* rootNode(Node
* node
)
292 node
= node
->getParent();
298 // GrabForeground based solution
300 static void writeHiResScreenShot(
305 size_t num_ports
= win
->getMFPort()->size();
309 // calc image dimensions
311 UInt32 winWidth
= win
->getWidth();
312 UInt32 winHeight
= win
->getHeight();
314 if (width
< winWidth
) width
= winWidth
;
315 if (height
< winHeight
) height
= winHeight
;
317 Real32 a
= Real32(winWidth
) / Real32(winHeight
);
318 width
= UInt32(a
*height
);
321 // output stream for writing the final image
323 std::ofstream
stream(name
, std::ios::binary
);
325 if (stream
.good() == false)
329 // Tile image used for foreground grabbing
331 ImageUnrecPtr grab_image
= Image::create();
333 GrabForegroundUnrecPtr grabber
= GrabForeground::create();
334 grabber
->setImage (grab_image
);
335 grabber
->setActive (true);
336 grabber
->setAutoResize(false);
339 // We tile the final image and render each tile with the screen resolution
340 // into the window. The more tiles we use the bigger the resolution of the
341 // final image gets with respect to a provided measure of length.
343 typedef boost::tuple
<TileCameraDecoratorUnrecPtr
, bool> TupleT
;
344 std::vector
<TupleT
> decorators
;
345 decorators
.resize(num_ports
);
348 // Setup the tile camera decorators for each viewport of the window and
349 // disable the tile property of tileable viewport backgrounds.
351 for (size_t i
= 0; i
< num_ports
; ++i
) {
352 Viewport
* vp
= win
->getPort(i
);
354 TileCameraDecoratorUnrecPtr decorator
= TileCameraDecorator::create();
356 decorator
->setFullSize (width
, height
);
357 decorator
->setDecoratee(vp
->getCamera());
359 vp
->setCamera(decorator
);
362 TileableBackground
* tbg
= dynamic_cast<TileableBackground
*>(vp
->getBackground());
364 bTiled
= tbg
->getTile();
369 // remember the decorator and the background tile prop setting
371 decorators
[i
] = boost::make_tuple(decorator
, bTiled
);
375 // Add the grabber to the forgrounds of the first viewport
377 Viewport
* vp0
= win
->getPort(0);
378 vp0
->addForeground(grabber
);
381 // We write the image in simple ppm format. This one starts with a description
382 // header which we output once on first write.
384 bool write_header
= true;
387 // Calc the max y start position (width). We process the tiles from bottom
388 // up and from left tp right as determined by the image format.
391 for (; yPosLast
< height
-winHeight
; yPosLast
+= winHeight
);
394 // Process from bottom to top
396 for (Int32 yPos
= yPosLast
; yPos
>= 0; yPos
-= winHeight
)
398 UInt32 ySize
= std::min(winHeight
, height
- yPos
);
401 // Collect the tile images for each row, i.e. we write the
402 // image in row manner to disk. This way the main memory is
403 // only moderately stressed.
405 std::vector
<ImageUnrecPtr
> vecColImages
;
408 // Process from left to right
410 for (UInt32 xPos
= 0; xPos
< width
; xPos
+= winWidth
)
412 UInt32 xSize
= std::min(winWidth
, width
- xPos
);
414 // The current tile image
416 ImageUnrecPtr col_image
= Image::create();
417 col_image
->set(Image::OSG_RGBA_PF
, xSize
, ySize
);
419 // Adapt the tile camera decorator boxes to the current tile
421 for (size_t i
= 0; i
< num_ports
; ++i
) {
422 Viewport
* vp
= win
->getPort(i
);
423 vp
->setSize(0, 0, xSize
, ySize
);
425 TileCameraDecorator
* decorator
= decorators
[i
].get
<0>();
427 decorator
->setSize( xPos
/ float(width
),
428 yPos
/ float(height
),
429 (xPos
+ xSize
) / float(width
),
430 (yPos
+ ySize
) / float(height
) );
433 // Adapt the grabber image size to the current tile dimension
435 grab_image
->set(Image::OSG_RGBA_PF
, xSize
, ySize
);
441 // Copy the image into the tile image stored for later processing
443 col_image
->setSubData(0, 0, 0, xSize
, ySize
, 1, grabber
->getImage()->getData());
445 vecColImages
.push_back(col_image
);
449 // Write the image format header once
452 write_header
= false;
453 if (!writePNMImagesHeader(vecColImages
, width
, height
, stream
)) break;
456 // Write the current column
458 if (!writePNMImagesData(vecColImages
, stream
)) break;
460 // Forget the current column images
462 vecColImages
.clear();
466 // restore window and cleanup
468 vp0
->removeObjFromForegrounds(grabber
);
470 for (size_t i
= 0; i
< num_ports
; ++i
) {
471 Viewport
* vp
= win
->getPort(i
);
473 vp
->setCamera(decorators
[i
].get
<0>()->getDecoratee());
474 vp
->setSize(0, 0, 1, 1);
476 TileableBackground
* tbg
= dynamic_cast<TileableBackground
*>(vp
->getBackground());
478 tbg
->setTile(decorators
[i
].get
<1>());
485 static void writeHiResScreenShotFBO(const char* name
, UInt32 width
, UInt32 height
)
487 size_t num_ports
= win
->getMFPort()->size();
491 // calc image dimensions
493 UInt32 winWidth
= win
->getWidth();
494 UInt32 winHeight
= win
->getHeight();
496 if (width
< winWidth
) width
= winWidth
;
497 if (height
< winHeight
) height
= winHeight
;
499 Real32 a
= Real32(winWidth
) / Real32(winHeight
);
500 width
= UInt32(a
*height
);
503 // output stream for writing the final image
505 std::ofstream
stream(name
, std::ios::binary
);
506 if (stream
.good() == false)
512 FrameBufferObjectUnrecPtr fbo
= FrameBufferObject::create();
514 // We use two render buffers. One for the color buffer and one for the depth and
515 // stencil buffer. This example does not take credit of the stencil buffer. There-
516 // fore a depth buffer would suffice. However, the use of the combined depth and
517 // stencil buffer is useful in other contextes and hence used.
519 RenderBufferUnrecPtr colBuf
= RenderBuffer::create();
520 RenderBufferUnrecPtr dsBuf
= RenderBuffer::create();
522 // As we would like to read back the FBO color buffer, we must provide a fitting
525 ImageUnrecPtr buffer_image
= Image::create();
526 buffer_image
->set(Image::OSG_RGBA_PF
, winWidth
, winHeight
);
527 colBuf
->setImage(buffer_image
);
529 // We must setup the internal image formats of the two render buffers accordingly.
531 colBuf
->setInternalFormat(GL_RGBA
);
532 dsBuf
->setInternalFormat(GL_DEPTH24_STENCIL8_EXT
);
534 // we must inform the FBO about the actual used color render buffers.
536 fbo
->editMFDrawBuffers()->push_back(GL_COLOR_ATTACHMENT0_EXT
);
538 // The FBO takes responsibility of the render buffers. Notice, that the shared
539 // depth/stencil buffer is provided twice. As the depth render buffer and as the
540 // stencil render buffer.
542 fbo
->setColorAttachment (colBuf
, 0);
543 fbo
->setDepthAttachment (dsBuf
);
544 fbo
->setStencilAttachment(dsBuf
);
546 // Also the FBO must be sized correctly.
548 fbo
->setWidth (winWidth
);
549 fbo
->setHeight(winHeight
);
551 // In order to read the color buffer back next two statements are necessary.
553 fbo
->setPostProcessOnDeactivate(true);
554 fbo
->getColorAttachments(0)->setReadBack(true);
557 // We tile the final image and render each tile with the screen resolution
558 // into the FBO. The more tiles we use the bigger the resolution of the
559 // final image gets with respect to a provided measure of length.
561 typedef boost::tuple
<TileCameraDecoratorUnrecPtr
, bool, SimpleStageUnrecPtr
, ViewportUnrecPtr
> TupleT
;
562 std::vector
<TupleT
> decorators
;
563 decorators
.resize(num_ports
);
566 // Remember the stage viewports for later cleanup
568 std::stack
<ViewportUnrecPtr
> stage_viewports
;
571 // Setup the tile camera decorators for each viewport of the window and
572 // disable the tile property of tileable viewport backgrounds.
574 for (size_t i
= 0; i
< num_ports
; ++i
) {
575 Viewport
* vp
= win
->getPort(i
);
577 TileCameraDecoratorUnrecPtr decorator
= TileCameraDecorator::create();
579 decorator
->setFullSize (width
, height
);
580 decorator
->setDecoratee(vp
->getCamera());
582 vp
->setCamera(decorator
);
585 TileableBackground
* tbg
= dynamic_cast<TileableBackground
*>(vp
->getBackground());
587 bTiled
= tbg
->getTile();
592 // The scene manager root node does not provide the illumination of the
593 // scene. This is governed internally by the manager. However, to take
594 // credit of the illumination we scan to the final parent of the scene
597 Node
* internalRoot
= rootNode(mgr
->getRoot());
599 // We would like to render the scene but won't detach it from its parent.
600 // The VisitSubTree allows just that.
602 VisitSubTreeUnrecPtr visitor
= VisitSubTree::create();
603 visitor
->setSubTreeRoot(internalRoot
);
604 NodeUnrecPtr visit_node
= makeNodeFor(visitor
);
606 // We clone the camera of the first viewport and do not swap the buffer on later
607 // rendering. This way the image generation process is not noticable in the
610 CameraUnrecPtr camera
= dynamic_pointer_cast
<Camera
>(vp
->getCamera()->shallowCopy());
612 // The stage object does provide a render target for the frame buffer attachment.
613 // SimpleStage has a camera, a background and the left, right, top, bottom
614 // fields to let you restrict rendering to a sub-rectangle of your FBO, i.e.
615 // they give you a viewport.
617 SimpleStageUnrecPtr stage
= SimpleStage::create();
618 stage
->setRenderTarget(fbo
);
619 stage
->setCamera (decorator
);
620 stage
->setBackground (vp
->getBackground());
622 // Give the stage core a place to live
624 NodeUnrecPtr stage_node
= makeNodeFor(stage
);
625 stage_node
->addChild(visit_node
);
631 // +- VisitSubTree -> ApplicationScene
633 NodeUnrecPtr root
= makeCoredNode
<Group
>();
634 root
->addChild(stage_node
);
636 // Give the root node a place to live, i.e. create a passive
637 // viewport and add it to the window.
639 ViewportUnrecPtr stage_viewport
= PassiveViewport::create();
640 stage_viewport
->setRoot (root
);
641 stage_viewport
->setBackground(vp
->getBackground());
642 stage_viewport
->setCamera (camera
);
644 win
->addPort(stage_viewport
);
647 // remember the decorator, the background tile prop setting and the stage setup
649 decorators
[i
] = boost::make_tuple(decorator
, bTiled
, stage
, stage_viewport
);
653 // We write the image in simple ppm format. This one starts with a description
654 // header which we output once on first write.
656 bool write_header
= true;
659 // Calc the max y start position (width). We process the tiles from bottom
660 // up and from left tp right as determined by the image format.
663 for (; yPosLast
< height
-winHeight
; yPosLast
+= winHeight
);
666 // Process from bottom to top
668 for (Int32 yPos
= yPosLast
; yPos
>= 0; yPos
-= winHeight
)
670 UInt32 ySize
= std::min(winHeight
, height
- yPos
);
673 // Collect the tile images for each row, i.e. we write the
674 // image in row manner to disk. This way the main memory is
675 // only moderately stressed.
677 std::vector
<ImageUnrecPtr
> vecColImages
;
680 // Process from left to right
682 for (UInt32 xPos
= 0; xPos
< width
; xPos
+= winWidth
)
684 UInt32 xSize
= std::min(winWidth
, width
- xPos
);
686 // The current tile image
688 ImageUnrecPtr col_image
= Image::create();
689 col_image
->set(Image::OSG_RGBA_PF
, xSize
, ySize
);
691 // Adapt the tile camera decorator boxes to the current tile
693 for (size_t i
= 0; i
< num_ports
; ++i
)
696 // this tile does not fill the whole FBO - adjust to only render
699 decorators
[i
].get
<2>()->setLeft (0.f
);
700 decorators
[i
].get
<2>()->setRight (xSize
/ float(winWidth
));
701 decorators
[i
].get
<2>()->setBottom(0.f
);
702 decorators
[i
].get
<2>()->setTop (ySize
/ float(winHeight
));
704 TileCameraDecorator
* decorator
= decorators
[i
].get
<0>();
706 decorator
->setSize( xPos
/ float(width
),
707 yPos
/ float(height
),
708 (xPos
+ xSize
) / float(width
),
709 (yPos
+ ySize
) / float(height
) );
716 win
->renderNoFinish(mgr
->getRenderAction());
721 // Copy the image into the tile image stored for later processing
725 RenderBuffer
* grabber
= dynamic_cast<RenderBuffer
*>(fbo
->getColorAttachments(0));
729 grabber
->getImage()->subImage(0, 0, 0, xSize
, ySize
, 1, col_image
);
733 vecColImages
.push_back(col_image
);
737 // Write the image format header once
740 write_header
= false;
741 if (!writePNMImagesHeader(vecColImages
, width
, height
, stream
)) break;
744 // Write the current column
746 if (!writePNMImagesData(vecColImages
, stream
)) break;
748 // Forget the current column images
750 vecColImages
.clear();
754 // restore window and cleanup
756 for (size_t i
= 0; i
< num_ports
; ++i
) {
757 win
->subPortByObj(decorators
[i
].get
<3>());
759 Viewport
* vp
= win
->getPort(i
);
760 vp
->setCamera(decorators
[i
].get
<0>()->getDecoratee());
761 vp
->setSize(0, 0, 1, 1);
763 TileableBackground
* tbg
= dynamic_cast<TileableBackground
*>(vp
->getBackground());
765 tbg
->setTile(decorators
[i
].get
<1>());
770 // We write the image in PNM format (Portable Any Map).
771 // see http://netpbm.sourceforge.net/doc/ppm.html
772 // or http://en.wikipedia.org/wiki/Netpbm_format
774 static bool writePNMImagesHeader(
775 const std::vector
<ImageUnrecPtr
>& vecImages
,
776 UInt32 width
, UInt32 height
,
779 if (vecImages
.empty()) return false;
781 ImageUnrecPtr col_image
= vecImages
[0];
783 UInt16 bpp
= col_image
->getBpp();
792 out
<< "P6" << std::endl
;
796 out
<< "# PNMImageFileType write" << std::endl
;
797 out
<< width
<< " " << height
<< std::endl
;
798 out
<< "255" << std::endl
;
804 // Write tile images column wise
806 static bool writePNMImagesData(
807 const std::vector
<ImageUnrecPtr
>& vecImages
,
810 if (vecImages
.empty()) return false;
812 ImageUnrecPtr first
= vecImages
[0];
814 UInt16 bpp
= first
->getBpp();
815 Int16 width
= first
->getWidth();
816 Int16 height
= first
->getHeight();
818 std::size_t num_images
= vecImages
.size();
820 for(Int16 y
= height
- 1; y
>= 0; y
--)
822 for (std::size_t img_idx
= 0; img_idx
< num_images
; img_idx
++)
824 ImageUnrecPtr image
= vecImages
[img_idx
];
826 width
= image
->getWidth();
827 height
= image
->getHeight();
829 Int16 lineSize
= bpp
* width
;
831 const UInt8
* data
= (image
->getData() + (lineSize
* y
));
833 for(Int16 x
= 0; x
< width
; x
++)
835 for(Int16 p
= bpp
- 1; p
--;)