danini-the-panini / mittsu

3D Graphics Library for Ruby.
https://github.com/danini-the-panini/mittsu
MIT License
510 stars 33 forks source link

How to take a screenshot? #96

Open pascalr opened 3 years ago

pascalr commented 3 years ago

Hello I was wondering how I can get the pixel values that are rendered to the screen. I got this so far:

module RendererPatch
  def take_screenshot(x,y,width,height)
    pixels = []
    glReadPixels(x,y,width,height,GL_RGB,GL_FLOAT,pixels)
  end
end
Mittsu::OpenGLRenderer.include RendererPatch

But I don't understand what kind of variable pixels is supposed to be. It's complaining:

/var/lib/gems/2.7.0/gems/opengl-bindings-1.6.10/lib/opengl_command.rb:246:in []: can't convert Array into Integer (TypeError)

This is the method source code from opengl-bindings:

GL_FUNCTIONS_ARGS_MAP[:glReadPixels] = [Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT,
                                        -Fiddle::TYPE_INT, -Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP]
GL_FUNCTIONS_RETVAL_MAP[:glReadPixels] = Fiddle::TYPE_VOID
def glReadPixels(_x_, _y_, _width_, _height_, _format_, _type_, _pixels_)
  f = OpenGL::get_command(:glReadPixels)
  f.call(_x_, _y_, _width_, _height_, _format_, _type_, _pixels_)
end

PS: Thanks for the great library!

danini-the-panini commented 3 years ago

I think pixels should be a string of length width*height*4 (I think float size is 4 bytes), and then to turn it into an array of floats you would call pixels.unpack('F*')

I should really just add a function to do this and output a ChunkyPNG object or something.

Possibly related to #75

pascalr commented 3 years ago

That would be great if you manage to add a function that outputs a png!

I tried with the string, but I didn't get very far, somehow it made my application crash without any error messages.

I believe the length of the string should be width*height*4*3 in my case since I wanted RGB, but probably mutliplied by 4 for GL_RGBA for a png.

danini-the-panini commented 3 years ago

Oh right, I forgot about the number of channels 🤦‍♀️

pascalr commented 3 years ago

I was finally able to do it with this:

module RendererPatch
  def take_screenshot(x,y,width,height)
    type_nb_bytes = 1 # for GL_UNSIGNED_BYTE (0 to 255)
    nb_channels = 3 # for GL_RGB
    pixels = ' '*width*height*type_nb_bytes*nb_channels
    glReadPixels(x,y,width,height,GL_RGB,GL_UNSIGNED_BYTE,pixels)
    png = ChunkyPNG::Image.from_rgb_stream(width, height, pixels)
    png.flip_horizontally!
    png.save('screenshot.png', :interlace => true)
  end
end
Mittsu::OpenGLRenderer.include RendererPatch