Search code examples
javaopenglscreenshotjoglglcanvas

Taking a screenshot with JOGL


I am looking for a way to screenshot my GLCanvas programmatically without awt Robot.

Here is my current setup:

Constructor:

glcaps = new GLCapabilities(GLProfile.get(GLProfile.GL2));
glcaps.setDoubleBuffered(true);
glcaps.setHardwareAccelerated(true);

glcanvas = new GLCanvas(glcaps);
glcanvas.setSize(720, 720);
glcanvas.addGLEventListener(this);

glcanvas is declared as an instance variable: GLCanvas glcanvas

OpenGL init:

@Override
public void init(GLAutoDrawable glad) {

    GL2 gl = glad.getGL().getGL2();
    glu = new GLU();

    gl.glEnable(GL2.GL_DEPTH_TEST);
    gl.glDepthFunc(GL2.GL_LEQUAL);
    gl.glShadeModel(GL2.GL_SMOOTH);
    gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);
    gl.glClearColor(0f, 0f, 0f, 1f);

    // Some camera related code not shown
}

OpenGL display:

public void display(GLAutoDrawable glad) {
    GL2 gl = glad.getGL().getGL2();

    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

    ...
    // Orient camera and draw a simple cube
    ...

    gl.glFlush();
}

Screenshot method:

BufferedImage b = new BufferedImage(glcanvas.getWidth(), glcanvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = b.createGraphics();
glcanvas.setupPrint(glcanvas.getWidth(), glcanvas.getWidth(), 50, 50, 50);
glcanvas.print(g);

try {
    ImageIO.write(b, "png", new File("test.png"));
} catch (IOException ex) {
    // Error handling
}

glcanvas.releasePrint();
g.dispose();

This method works, as in executes without crashing, but the png file I get is just black with no cube. I also tried using glReadPixels but that does not work either as it just gives me a buffer full of 0's (black).

I think that the problem is that I am not reading glcanvas from the draw thread. Is this the error, and if so, how can I solve it?

All answers appreciated!


Solution

  • First, you have to be sure that you read the framebuffer after what you want to catch has been rendered.

    Second, you can do something like this:

    protected void saveImage(GL3 gl3, int width, int height) {
    
        try {
            BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics graphics = screenshot.getGraphics();
    
            ByteBuffer buffer = GLBuffers.newDirectByteBuffer(width * height * 4);
            // be sure you are reading from the right fbo (here is supposed to be the default one)
            // bind the right buffer to read from
            gl3.glReadBuffer(GL_BACK);
            // if the width is not multiple of 4, set unpackPixel = 1
            gl3.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    
            for (int h = 0; h < height; h++) {
                for (int w = 0; w < width; w++) {
                    // The color are the three consecutive bytes, it's like referencing
                    // to the next consecutive array elements, so we got red, green, blue..
                    // red, green, blue, and so on..+ ", "
                    graphics.setColor(new Color((buffer.get() & 0xff), (buffer.get() & 0xff),
                            (buffer.get() & 0xff)));
                    buffer.get();   // consume alpha
                    graphics.drawRect(w, height - h, 1, 1); // height - h is for flipping the image
                }
            }
            // This is one util of mine, it make sure you clean the direct buffer
            BufferUtils.destroyDirectBuffer(buffer);
    
            File outputfile = new File("D:\\Downloads\\texture.png");
            ImageIO.write(screenshot, "png", outputfile);
        } catch (IOException ex) {
        }
    }
    

    I filled some comment inside, if something is still unclear, don't hesitate to ask further