import java.awt.image.BufferedImage; import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; /** * GLART_10_screen_capture.java * * Save the framebuffer contents (aka "the screen") to a file. * * Hit F1 to save the screen to a file * * The functions: * screenShot() */ public class GLART_10_screen_capture { private boolean done = false; private final String windowTitle = "Save Frame to File"; private DisplayMode displayMode; private float rotation = 0f; private float rotation2 = 0f; private float rotationAmount = .08f; // For copying screen image to a texture int screenTextureSize = 1024; // how large should texture be to hold screen // texture int marbleTextureHandle; // flags set by F1 key boolean screenCapture = false; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_10_screen_capture app = new GLART_10_screen_capture(); app.run(); } /** * Initialize the app, then sit in a render loop until done==true. */ public void run() { try { init(); while (!done) { mainloop(); render(); Display.update(); } cleanup(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } /** * Initialize the environment * @throws Exception */ private void init() throws Exception { initDisplay(); initGL(); // Load the image as RGBA pixels GLImage textureImg = new GLImage("images/marble.jpg"); // Allocate and configure a texture based on the image marbleTextureHandle = makeTexture(textureImg); // Random value for rotation increment: .05 - 1.0 rotationAmount = (float) (.05 + (Math.random()*.05)); } /** * Create an OpenGL display, in this case a fullscreen window. * @throws Exception */ private void initDisplay() throws Exception { // get all possible display resolutions DisplayMode d[] = Display.getAvailableDisplayModes(); // find a resolution we like for (int i = 0; i < d.length; i++) { if (d[i].getWidth() == 1024 && d[i].getHeight() == 768 && d[i].getBitsPerPixel() == 32) { displayMode = d[i]; break; } } // set the display to the resolution we picked Display.setDisplayMode(displayMode); Display.setTitle(windowTitle); // if true, set to full screen, no chrome Display.setFullscreen(true); Display.setVSyncEnabled(true); // create the window Display.create(); } /** * Initialize OpenGL * */ private void initGL() { // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Reset The Projection Matrix // Define perspective GLU.gluPerspective( 45.0f, // Field Of View (float)displayMode.getWidth() / (float)displayMode.getHeight(), // aspect ratio 0.1f, // near Z clipping plane 100.0f); // far Z clipping plane // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); // Depth test ON GL11.glEnable(GL11.GL_DEPTH_TEST); // set the background color GL11.glClearColor(.3f, .1f, .62f, 1); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // To create transparencies (alpha blending) GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // textures on GL11.glEnable(GL11.GL_TEXTURE_2D); } /** * Handle keyboard input. Just check for escape key or user * clicking to close the window. */ private void mainloop() { if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { // Escape is pressed done = true; } if(Display.isCloseRequested()) { // Window is closed done = true; } // handle key down and up events handleKeyPressEvents(); } /** * Key event functions (keyDown() and keyUp() are are called by mainloop()). * * We set screen capture flags here, but do the screen capturing * in render(), not here. This is because the input thread does * not have access to the OpenGL display. Only the render() thread * does, so any code involving opengl has to be invoked by render(). * * @param keycode */ public void keyDown(int keycode) { // set flag to save screen (see render()) if (keycode == Keyboard.KEY_F1) { screenCapture = true; } } public void keyUp(int keycode) { } /** * Detect changes in key state, ie. a key is pressed or released, and * call keydown() and keyup() functions. These are non-repeating events * (keydown will be called only when the key is first pressed). */ public void handleKeyPressEvents() { while ( Keyboard.next() ) { if (Keyboard.getEventKeyState()) { keyDown(Keyboard.getEventKey()); } else { keyUp(Keyboard.getEventKey()); } } } /** * Render the scene. */ private void render() { rotation += .05f; rotation2 += rotationAmount; // clear the color and depth buffers GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // Reset the Modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); GL11.glBindTexture(GL11.GL_TEXTURE_2D,marbleTextureHandle); // be sure we're in modelview mode (frameDraw switches to Projection matrix) GL11.glMatrixMode(GL11.GL_MODELVIEW); // Place the 'eye' GLU.gluLookAt( 0f, 0f, 5f, // eye position 0f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // rotate and shift scene GL11.glRotatef(rotation*3.3f, 0,0,1); GL11.glTranslatef(.5f,0,0); // draw a red quad GL11.glColor4f(1,0f,0f,.9f); GL11.glPushMatrix(); { GL11.glRotatef(rotation*.7f, 0,1,1); GL11.glTranslatef(0,-1.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // rotate more GL11.glRotatef(rotation2*4.7f, 0,0,1); GL11.glTranslatef(1,0,0); // draw a green quad GL11.glColor4f(0f,1,0f,.5f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2*1.5f, 0,0,1); GL11.glTranslatef(-.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // draw a blue quad GL11.glColor4f(0f,0f,(rotation2%360f)/360f,.5f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2*4.2f, 1,0,1); GL11.glTranslatef(.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // save framebuffer pixels to PNG image if (screenCapture) { screenCapture = false; screenShot(this.displayMode.getWidth(), displayMode.getHeight(), "screen_capture.png"); } } /** * draw a 1x1 square */ public void drawQuad() { GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left GL11.glTexCoord2f(1, 0); GL11.glVertex3f( 1.0f,-1.0f, 0.0f); // Bottom Right GL11.glTexCoord2f(1, 1); GL11.glVertex3f( 1.0f, 1.0f, 0.0f); // Top Right GL11.glTexCoord2f(0, 1); GL11.glVertex3f(-1.0f, 1.0f, 0.0f); // Top left } GL11.glEnd(); } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); } /** * Allocate a texture (glGenTextures) and return the handle to it. */ public static int allocateTexture() { IntBuffer textureHandle = allocInts(1); GL11.glGenTextures(textureHandle); return textureHandle.get(0); } public static final int SIZE_INT = 4; public static final int SIZE_BYTE = 4; public static IntBuffer allocInts(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); } public static ByteBuffer allocBytes(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_BYTE).order(ByteOrder.nativeOrder()); } /** * Create a texture from the given image. */ public static int makeTexture(GLImage textureImg) { if ( textureImg == null ) { return 0; } else { return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h); } } /** * Create a texture from the given pixels in RGBA format. Set the texture * to repeat in both directions and use LINEAR for magnification. * @return the texture handle */ public static int makeTexture(ByteBuffer pixels, int w, int h) { // get a new empty texture int textureHandle = allocateTexture(); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters: // how to wrap texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // how to scale up texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); // Create the texture from pixels GL11.glTexImage2D(GL11.GL_TEXTURE_2D, // type of texture we're creating 0, // level-of-detail: use 0 GL11.GL_RGBA, // texture pixel format w, h, // width and height of texture image (powers of 2) 0, // widtt of the border (either 0 or 1, use 0) GL11.GL_RGBA, // image pixel format GL11.GL_UNSIGNED_BYTE, // image pixel data type pixels // image pixel data ); return textureHandle; } /** * Save the contents of the current render buffer to a PNG image. If the current * buffer is the framebuffer then this will work as a screen capture. Can * also be used with the PBuffer class to copy large images or textures that * have been rendered into the offscreen pbuffer. *

* WARNING: this function hogs memory! Not optimized. *

*/ public static void screenShot(int width, int height, String saveFilename) { // allocate space for RBG pixels ByteBuffer framebytes = allocBytes(width * height * 3); int[] pixels = new int[width * height]; int bindex; // grab a copy of the current frame contents as RGB (has to be UNSIGNED_BYTE or colors come out too dark) GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, framebytes); // copy RGB data from ByteBuffer to integer array for (int i = 0; i < pixels.length; i++) { bindex = i * 3; pixels[i] = 0xFF000000 // A | ((framebytes.get(bindex) & 0x000000FF) << 16) // R | ((framebytes.get(bindex+1) & 0x000000FF) << 8) // G | ((framebytes.get(bindex+2) & 0x000000FF) << 0); // B } // free up this memory framebytes = null; // flip the pixels vertically (opengl has 0,0 at lower left, java is upper left) pixels = GLImage.flipPixels(pixels, width, height); try { // Create a BufferedImage with the RGB pixels then save as PNG BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); image.setRGB(0, 0, width, height, pixels, 0, width); javax.imageio.ImageIO.write(image, "png", new File(saveFilename)); } catch (Exception e) { System.out.println("screenShot(): exception " + e); } } }