Hi everybody,
NOTE: This code was written to be applied in an LWJGL game and as a result, offers very “specific” functionality. Nonetheless I believe it may have other uses as well. I just hope it’s going to help some of you…
Some time ago I implemented a screenshot capture method. I used ImageIO to do the screenshot writting. Of course it wasn’t good enough for my needs, as it added both memory overhead (more classes loaded, obligatory use of BufferedImage :P, etc.) and a lot of CPU overhead (in my Athlon 700MHz it took 1.5-2 seconds per capture). It did it’s job, but I had to use another method to go at least under 1 sec / capture.
I wanted to have a fast solution (1-3 captures per second), and export a cross-platform loss-less image format. From what ImageIO offers (NOTE: I used the new ImageIO extension, that adds more formats), the only formats that satisfy the cross-platform / loss-less part are .PNG and .TIFF (maybe .BMP too? I have never touched a Mac…). I tried both, using ImageIO and although the new extension adds some native code for read/write acceleration, the results were unsatisfactory when it came to speed. I even tried some other formats, with no luck
Finally I decided to implement my own writer. I chose .TIFF, as it is the “most” cross-platform of all the others, it supports uncompressed data (no need for time consuming compression algos), and is easy to write ;).
So, here it is, for you to use and enjoy! Overall, I got a ten-fold(!) speed increase, grabbing ~5 screenshots / second. It’s not a complete .TIFF writer, you can just write 3 bytes/pixel RGB images. If you want other types, feel free to extend it’s functionality ::).
/** Screenshot count */
private static int lastCapture = 1;
/** A buffer that holds the inversed screenshot image */
private static byte[] captureData = new byte[Display.WIDTH * Display.HEIGHT * 3];
public static void capture() throws IOException {
// Get a temp ByteBuffer
final ByteDataBuffer captureDataBuffer = Memory.getByteDataBuffer(captureData.length);
// Read Frame Buffer
Scene.gl.readPixels(0, 0, Display.WIDTH, Display.HEIGHT, GL.RGB, GL.UNSIGNED_BYTE, captureDataBuffer.address);
int bytesPerRow = Display.WIDTH * 3;
int lastRow = Display.HEIGHT - 1;
// Inverse captured image
ByteBuffer captureBuffer = captureDataBuffer.data;
for ( int i = 0; i < Display.HEIGHT; i++ )
captureBuffer.get(captureData, ( lastRow - i ) * bytesPerRow, bytesPerRow);
// Write screenshot
TIFFWriter.writeImage(Display.WIDTH, Display.HEIGHT, captureData, new File("screenshot" + lastCapture++ + ".tiff"));
// Release temp ByteBuffer
Memory.releaseDataBuffer(captureDataBuffer);
}
Memory.getByteDataBuffer() is just a method that grabs a ByteBuffer from another huge ByteBuffer. Saves a lot of overhead ( Cas, thanks for your precious tips ;-). Always helpful. ). Memory.releaseDataBuffer() releases the allocated memory. I strongly suggest you go implement sth like this (for those who haven’t yet). Otherwise, all you have to do is provide a direct ByteBuffer to put the pixel data from the Frame Buffer.
After grabbing the pixels, the image rows get inverted (0,0 coordinate bottom-left :o? What were they thinking?? Just kidding, I’ve gotten used to it…) and then we give the data to the TIFFWriter.
NOTE: I’ve used Display.WIDTH and Display.HEIGHT. This is the screen resolution. Modify according to your design.
Look at the next post for TIFFWriter implementation and comments…