Making screenshots in JOGL

Hi. I want my engine to be capable of making screenshots. And I have experienced serious problems doing that (even HotSpot unexpected errors). I have a method that makes screenshots. Here it is:


public void makeScreenShot(File output, int width, int height) {
        ByteBuffer data = ByteBuffer.allocateDirect(width*height*3);
        gl.glReadBuffer(gl.GL_FRONT);
        gl.glReadPixels(0, 0, width, height, gl.GL_RGB, gl.GL_BYTE, data);
        BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
        screenshot.setData(Raster.createRaster(new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 3), 
        new DataBufferByte(data.array(), data.capacity()), new Point(0, 0)));
        try {
        ImageIO.write(screenshot, "png", output);
        } catch(IOException e) {
            System.out.println("Unable to write screenshot. Details: ");
            e.printStackTrace();
            System.exit(1);
        }
    } 

Well, in theory, it must read image data from the front gl buffer and convert it to a ByteBuffer, than make a BufferedImage and write it to the disk.

What I get is

`
Exception in thread “AWT-EventQueue-0” java.lang.UnsupportedOperationException
at java.nio.ByteBuffer.array(Unknown Source)
at overlord.renderer.GLView.makeScreenShot(GLView.java:264)
at overlord.renderer.Demo.display(Demo.java:48)
at net.java.games.jogl.impl.GLDrawableHelper.display(GLDrawableHelper.ja
va:74)
at net.java.games.jogl.GLCanvas$DisplayAction.run(GLCanvas.java:206)
at net.java.games.jogl.impl.GLContext.invokeGL(GLContext.java:239)
at net.java.games.jogl.GLCanvas.displayImpl(GLCanvas.java:194)
at net.java.games.jogl.GLCanvas.display(GLCanvas.java:82)
at net.java.games.jogl.GLCanvas.paint(GLCanvas.java:89)
at sun.awt.RepaintArea.paintComponent(Unknown Source)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(Unknown Source)

    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source) `

Very likely, my buffer is empty after glReadPixels. How is that possible? Please help.

Hi
I use java.awt.Robot to make screenshot, like:
Robot screenRobot = new Robot();
Rectangle rectangle = new Rectangle(0,0, width, height)
BufferedImage screenshot= screenRobot.createScreenCapture(rectangle);

This will give you a bufferedImage of the parts of the screen that is in the rectangle. I dont know if it is a good way of doing it, but it works for me :slight_smile:

// Gregof

Hi

This works for me:


private static void writeBufferToFile(GLDrawable drawable, File outputFile) {

      int width = drawable.getSize().width;
      int height = drawable.getSize().height;

      ByteBuffer pixelsRGB = BufferUtils.newByteBuffer(width * height * 3);

      GL gl = drawable.getGL();

      gl.glReadBuffer(GL.GL_BACK);
      gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);

      gl.glReadPixels(0,                    // GLint x
                  0,                    // GLint y
                  width,                     // GLsizei width
                  height,              // GLsizei height
                  GL.GL_RGB,              // GLenum format
                  GL.GL_UNSIGNED_BYTE,        // GLenum type
                  pixelsRGB);               // GLvoid *pixels

      int[] pixelInts = new int[width * height];

      // Convert RGB bytes to ARGB ints with no transparency. Flip image vertically by reading the
      // rows of pixels in the byte buffer in reverse - (0,0) is at bottom left in OpenGL.

      int p = width * height * 3; // Points to first byte (red) in each row.
      int q;                  // Index into ByteBuffer
      int i = 0;                  // Index into target int[]
      int w3 = width*3;         // Number of bytes in each row

      for (int row = 0; row < height; row++) {
            p -= w3;
            q = p;
            for (int col = 0; col < width; col++) {
                  int iR = pixelsRGB.get(q++);
                  int iG = pixelsRGB.get(q++);
                  int iB = pixelsRGB.get(q++);

                  pixelInts[i++] = 0xFF000000
                              | ((iR & 0x000000FF) << 16)
                              | ((iG & 0x000000FF) << 8)
                              | (iB & 0x000000FF);
            }

      }

      BufferedImage bufferedImage =
            new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

      bufferedImage.setRGB(0, 0, width, height, pixelInts, 0, width);

      try {
            ImageIO.write(bufferedImage, "PNG", outputFile);
      } catch (IOException e) {
            e.printStackTrace();
      }

}

I call this at the very end of the display() method.

One thing to watch out for is:

gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);

Without this, and if your display width is not a multiple of 4, glReadPixels will pad each row of pixels to the next multiple of 4 bytes, with potentially disasterous results - it will probably end up trying to pack more than widthheight3 bytes into your buffer.

Also, the order of the rows of pixels in the ByteBuffer will be reversed - Row 0 in openGL is at the bottom of the image, everywhere else it’s at the top. I don’t know if BandedSampleModel etc. will be able to resolve this, so I’m just doing it manually (efficiency isn’t that important to me in this case).

Kevin

Thanks for the replies and code! I’ll take time to examine it.