Save a screenshot from an OpenGLGadget.

Share your advanced PureBasic knowledge/code with the community.
Axeman
User
User
Posts: 89
Joined: Mon Nov 03, 2003 5:34 am

Save a screenshot from an OpenGLGadget.

Post by Axeman »

This function saves a screenshot from an OpenGLGadget. See the comments in the function for more information.

SaveScreenshot() function.

Code: Select all

UseJPEGImageEncoder()

Procedure SaveScreenshot( gadget, filepath.s, buffer = 0 )
; Saves a screenshot from an OpenGL Gadget as a JPEG image file.
; Note that the screenshot will be taken from the front-buffer by default. Set the 'buffer' parameter to 1 to grab from the back-buffer.
; -
; gadget - Should hold the PureBasic gadget number of the OpenGLGadget.
; filepath.s - Should hold the filepath to save the image file to. This will be a JPEG file, so 'UseJPEGImageEncoder()' should be used to enable that functionality.
; buffer - 0 = Grab screenshot from front-buffer (the visible buffer). 1 = Grab screenshot from back-buffer (the drawing buffer). The front buffer is the default to grab from.
; -
; https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadPixels.xhtml
; https://stackoverflow.com/questions/5844858/how-to-take-screenshot-in-opengl
; https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadBuffer.xhtml

SetGadgetAttribute( gadget, #PB_OpenGL_SetContext, #True )

width = GadgetWidth( gadget )
height = GadgetHeight( gadget )
max_x = width - 1
max_y = height - 1

buffer_size = width * height * 3
*pixel_buffer = AllocateMemory( buffer_size, #PB_Memory_NoClear )
If *pixel_buffer
	If buffer : glReadBuffer_( #GL_BACK ) : Else : glReadBuffer_( #GL_FRONT ) : EndIf
	glReadPixels_( 0, 0, width, height, #GL_RGB, #GL_UNSIGNED_BYTE, *pixel_buffer ) ; Note that the x,y coordinate is for the bottom-left corner of the output.
	screenshot = CreateImage( #PB_Any, width, height )
	If screenshot
		pos = 0
		StartDrawing( ImageOutput( screenshot ) )
		For y = max_y To 0 Step -1 ; Reading on the Y axis needs to be reversed as the starting coordinate in 'glReadPixels' is for the bottom-left corner of the output and images start at the top-left corner.
			For x = 0 To max_x
				If ( pos + 2 ) < buffer_size ; Ensure that the current buffer position is inside the buffer.
					Plot( x, y, RGB( PeekA( *pixel_buffer + pos ), PeekA( *pixel_buffer + pos + 1 ), PeekA( *pixel_buffer + pos + 2 ) ) ) : pos + 3
				EndIf
			Next
		Next
		StopDrawing()
		
		If SaveImage( screenshot, filepath.s, #PB_ImagePlugin_JPEG, 10, 24 ) = 0
			Debug "Screenshot could not be saved to file. File storage may be full." ; This is a placeholder for your own error message function.
		EndIf
		FreeImage( screenshot )
	Else
		Debug "Unable to create screenshot image. Possibly out of memory." ; This is a placeholder for your own error message function.
	EndIf
	FreeMemory( *pixel_buffer )
EndIf
EndProcedure

Example usage for this function.

Code: Select all

;
; OpenGL Gadget demonstration
;
; (c) Fantaisie Software
;
; Axis explainations:
;
;             +
;             y
;
;             |
;             |
;             |            +
;             /---------- x
;            /
;           /
;          /
;         / z+
;
; So a rotate on the y axis will take the y axis as center. With OpenGL, we can specify
; positive And negative value. Positive values are always in the same sens as the axis
; (like described on the schmatic, with '+' signs)
;


UseJPEGImageEncoder()


Global RollAxisX.f
Global RollAxisY.f
Global RollAxisZ.f

Global RotateSpeedX.f = 1.0
Global RotateSpeedY.f
Global RotateSpeedZ.f = 1.0

Global ZoomFactor.f = 1.0 ; Distance of the camera. Negative value = zoom back

Procedure DrawCube(Gadget)
  SetGadgetAttribute(Gadget, #PB_OpenGL_SetContext, #True)
  
  glPushMatrix_()                  ; Save the original Matrix coordinates
  glMatrixMode_(#GL_MODELVIEW)

  glTranslatef_(0, 0, ZoomFactor)  ;  move it forward a bit

  glRotatef_ (RollAxisX, 1.0, 0, 0) ; rotate around X axis
  glRotatef_ (RollAxisY, 0, 1.0, 0) ; rotate around Y axis
  glRotatef_ (RollAxisZ, 0, 0, 1.0) ; rotate around Z axis
 
  RollAxisX + RotateSpeedX 
  RollAxisY + RotateSpeedY 
  RollAxisZ + RotateSpeedZ 

  ; clear framebuffer And depth-buffer

  glClear_ (#GL_COLOR_BUFFER_BIT | #GL_DEPTH_BUFFER_BIT)

  ; draw the faces of a cube
  
  ; draw colored faces

  glDisable_(#GL_LIGHTING)
  glBegin_  (#GL_QUADS)
  
  ; Build a face, composed of 4 vertex ! 
  ; glBegin() specify how the vertexes are considered. Here a group of
  ; 4 vertexes (GL_QUADS) form a rectangular surface.

  ; Now, the color stuff: It's r,v,b but with float values which
  ; can go from 0.0 To 1.0 (0 is .. zero And 1.0 is full intensity) 
  
  glNormal3f_ (0,0,1.0)
  glColor3f_  (0,0,1.0)
  glVertex3f_ (0.5,0.5,0.5)   
  glColor3f_  (0,1.0,1.0)         
  glVertex3f_ (-0.5,0.5,0.5)
  glColor3f_  (1.0,1.0,1.0)
  glVertex3f_ (-0.5,-0.5,0.5)
  glColor3f_  (0,0,0)
  glVertex3f_ (0.5,-0.5,0.5) 

  ; The other face is the same than the previous one 
  ; except the colour which is nice blue To white gradiant

  glNormal3f_ (0,0,-1.0)
  glColor3f_  (0,0,1.0)
  glVertex3f_ (-0.5,-0.5,-0.5)
  glColor3f_  (0,0,1.0)
  glVertex3f_ (-0.5,0.5,-0.5)
  glColor3f_  (1.0,1.0,1.0)
  glVertex3f_ (0.5,0.5,-0.5)
  glColor3f_  (1.0,1.0,1.0)
  glVertex3f_ (0.5,-0.5,-0.5)
  
  glEnd_()
  
  ; draw shaded faces

  glEnable_(#GL_LIGHTING)
  glEnable_(#GL_LIGHT0)
  glBegin_ (#GL_QUADS)

  glNormal3f_ (   0, 1.0,   0)
  glVertex3f_ ( 0.5, 0.5, 0.5)
  glVertex3f_ ( 0.5, 0.5,-0.5)
  glVertex3f_ (-0.5, 0.5,-0.5)
  glVertex3f_ (-0.5, 0.5, 0.5)

  glNormal3f_ (0,-1.0,0)
  glVertex3f_ (-0.5,-0.5,-0.5)
  glVertex3f_ (0.5,-0.5,-0.5)
  glVertex3f_ (0.5,-0.5,0.5)
  glVertex3f_ (-0.5,-0.5,0.5)

  glNormal3f_ (1.0,0,0)
  glVertex3f_ (0.5,0.5,0.5)
  glVertex3f_ (0.5,-0.5,0.5)
  glVertex3f_ (0.5,-0.5,-0.5)
  glVertex3f_ (0.5,0.5,-0.5)

  glNormal3f_ (-1.0,   0,   0)
  glVertex3f_ (-0.5,-0.5,-0.5)
  glVertex3f_ (-0.5,-0.5, 0.5)
  glVertex3f_ (-0.5, 0.5, 0.5)
  glVertex3f_ (-0.5, 0.5,-0.5)

  glEnd_()

  glPopMatrix_()
  glFinish_()

  SetGadgetAttribute(Gadget, #PB_OpenGL_FlipBuffers, #True)
EndProcedure


Procedure SetupGL()
    
  glMatrixMode_(#GL_PROJECTION)
  gluPerspective_(30.0, 200/200, 1.0, 10.0) 
  
  ; position viewer
  glMatrixMode_(#GL_MODELVIEW)
  
  glTranslatef_(0, 0, -5.0)
  
  glEnable_(#GL_DEPTH_TEST)   ; Enabled, it slowdown a lot the rendering. It's to be sure than the
                              ; rendered objects are inside the z-buffer.
  
  glEnable_(#GL_CULL_FACE)    ; This will enhance the rendering speed as all the back face will be
                              ; ignored. This works only with CLOSED objects like a cube... Singles
                              ; planes surfaces will be visibles only on one side.
    
  glShadeModel_(#GL_SMOOTH)
EndProcedure


Procedure HandleError (Result, Text$)
  If Result = 0
    MessageRequester("Error", Text$, 0)
    End
  EndIf
EndProcedure



Procedure SaveScreenshot( gadget, filepath.s, buffer = 0 )
; Saves a screenshot from an OpenGL Gadget as a JPEG image file.
; Note that the screenshot will be taken from the front-buffer by default. Set the 'buffer' parameter to 1 to grab from the back-buffer.
; -
; gadget - Should hold the PureBasic gadget number of the OpenGLGadget.
; filepath.s - Should hold the filepath to save the image file to. This will be a JPEG file, so 'UseJPEGImageEncoder()' should be used to enable that functionality.
; buffer - 0 = Grab screenshot from front-buffer (the visible buffer). 1 = Grab screenshot from back-buffer (the drawing buffer). The front buffer is the default to grab from.
; -
; https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadPixels.xhtml
; https://stackoverflow.com/questions/5844858/how-to-take-screenshot-in-opengl
; https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadBuffer.xhtml

SetGadgetAttribute( gadget, #PB_OpenGL_SetContext, #True )

width = GadgetWidth( gadget )
height = GadgetHeight( gadget )
max_x = width - 1
max_y = height - 1

buffer_size = width * height * 3
*pixel_buffer = AllocateMemory( buffer_size, #PB_Memory_NoClear )
If *pixel_buffer
	If buffer : glReadBuffer_( #GL_BACK ) : Else : glReadBuffer_( #GL_FRONT ) : EndIf
	glReadPixels_( 0, 0, width, height, #GL_RGB, #GL_UNSIGNED_BYTE, *pixel_buffer ) ; Note that the x,y coordinate is for the bottom-left corner of the output.
	screenshot = CreateImage( #PB_Any, width, height )
	If screenshot
		pos = 0
		StartDrawing( ImageOutput( screenshot ) )
		For y = max_y To 0 Step -1 ; Reading on the Y axis needs to be reversed as the starting coordinate in 'glReadPixels' is for the bottom-left corner of the output and images start at the top-left corner.
			For x = 0 To max_x
				If ( pos + 2 ) < buffer_size ; Ensure that the current buffer position is inside the buffer.
					Plot( x, y, RGB( PeekA( *pixel_buffer + pos ), PeekA( *pixel_buffer + pos + 1 ), PeekA( *pixel_buffer + pos + 2 ) ) ) : pos + 3
				EndIf
			Next
		Next
		StopDrawing()
		
		If SaveImage( screenshot, filepath.s, #PB_ImagePlugin_JPEG, 10, 24 ) = 0
			Debug "Screenshot could not be saved to file. File storage may be full." ; This is a placeholder for your own error message function.
		EndIf
		FreeImage( screenshot )
	Else
		Debug "Unable to create screenshot image. Possibly out of memory." ; This is a placeholder for your own error message function.
	EndIf
	FreeMemory( *pixel_buffer )
EndIf
EndProcedure



; ---



#MAIN_WINDOW = 0

Enumeration
#OPENGL_CANVAS
#SCREENSHOT_BUTTON
#START_BUTTON
#STOP_BUTTON
EndEnumeration

#GRAPHICS_TIMER = 0

Global G_graphics_timer_running



OpenWindow( #MAIN_WINDOW, 0, 0, 530, 320, "OpenGL Gadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered )

ButtonGadget( #START_BUTTON, 10, 10, 50, 25, "Start" )
ButtonGadget( #STOP_BUTTON, 70, 10, 50, 25, "Stop" )
ButtonGadget( #SCREENSHOT_BUTTON, 10, 45, 100, 25, "Screenshot" )

OpenGLGadget( #OPENGL_CANVAS, 220, 10, 300, 300 )
SetupGL()

AddWindowTimer( #MAIN_WINDOW, #GRAPHICS_TIMER, 16 ) ; about 60 fps
G_graphics_timer_running = 1

Repeat
  
  Select WaitWindowEvent()
  
  	Case #PB_Event_CloseWindow
  		End
	
		Case #PB_Event_Gadget
		
			Select EventGadget()
			
				Case #SCREENSHOT_BUTTON
					SaveScreenshot( #OPENGL_CANVAS, "test.jpg" )
					
				Case #START_BUTTON
					If G_graphics_timer_running = 0
						AddWindowTimer( #MAIN_WINDOW, #GRAPHICS_TIMER, 16 )
						G_graphics_timer_running = 1
					EndIf
					
				Case #STOP_BUTTON
					If G_graphics_timer_running
						RemoveWindowTimer( #MAIN_WINDOW, #GRAPHICS_TIMER )
						G_graphics_timer_running = 0
					EndIf
					
			EndSelect
			
    Case #PB_Event_Timer
      If EventTimer() = #GRAPHICS_TIMER
        DrawCube( #OPENGL_CANVAS )
      EndIf
  EndSelect
  
ForEver

mpz
Enthusiast
Enthusiast
Posts: 494
Joined: Sat Oct 11, 2008 9:07 pm
Location: Germany, Berlin > member German forum

Re: Save a screenshot from an OpenGLGadget.

Post by mpz »

Thanks,

very usefull code (for my 3d engine, hihi).

Greeting Michael
Working on - MP3D Library - PB 5.73 version ready for download
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5353
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Save a screenshot from an OpenGLGadget.

Post by Kwai chang caine »

Works nice here :D
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
Post Reply