import java.awt.image.BufferedImage; import java.io.File; import java.nio.*; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; /** * GLART_10_giantimage.java * * Save a "giant" image of a scene by rendering the scene as a series * of tiles and saving the tiled images. Piece the tiles back together * to form a high resolution image of the scene. * * Hit F1 to save the current scene as 16 tiled images. * * The functions: * * giantScreenShot() - render scene as tiles and saves to files * setPerspective() - inititialize perspective settings for frustum * setFrustum() - adjust the frustum to show one portion of scene * screenShot() - save current screen to PNG file */ public class GLART_10_giantimage { private boolean done = false; private final String windowTitle = "Pbuffer Demo"; private DisplayMode displayMode; private float rotation = 0f; private float rotation2 = 0f; private float rotationAmount = .08f; float aspectRatio; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_10_giantimage app = new GLART_10_giantimage(); 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(displayMode.getWidth(), displayMode.getHeight()); // Random value for rotation increment: .05 - 1.0 rotationAmount = (float) (.05 + (Math.random()*.05)); // Initialize the OpenGL context FOR THE PBUFFER (which we "selected" above) initGL(displayMode.getWidth(), displayMode.getHeight()); } /** * 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() == 800 //1024 && d[i].getHeight() == 600 //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(false); Display.setVSyncEnabled(true); // create the window Display.create(); // keep track of the aspect ratio of screen aspectRatio = (float)displayMode.getHeight()/ (float)displayMode.getWidth(); } /** * Initialize OpenGL * */ private void initGL(int displayWidth, int displayHeight) { // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Reset The Projection Matrix // setup perspective AND initialize values for giant screenshot setPerspective(); // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); // No need for depth, composition is flat GL11.glDisable(GL11.GL_DEPTH_TEST); // set the background color GL11.glClearColor(.1f, .1f, .12f, 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); } /** * 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) { doGiantScreenShot = 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 += .08f; rotation2 += rotationAmount; renderFrame(); // make a jumbo sized image of screen if (doGiantScreenShot) { // save screen regular size (for reference) screenShot(displayMode.getWidth(), displayMode.getHeight(), "screen_capture.png"); // save scene tiles giantScreenShot(4); // (how many rows and columns: ie. 4 rows and 4 columns) // turn off the flag doGiantScreenShot = false; } } /** * Render the scene. */ private void renderFrame() { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // Reset the Modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // 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 scene GL11.glRotatef(rotation*3.3f, 0,0,1); GL11.glTranslatef(.5f,0,0); // draw a red quad GL11.glColor4f(1,0f,0f,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation*.7f, 0,1,1); GL11.glTranslatef(0,.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,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2*2f, 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,1,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2, 1,0,1); GL11.glTranslatef(.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuadLine(); } GL11.glPopMatrix(); // draw lines GL11.glPushMatrix(); { GL11.glRotatef(rotation2*2.1f, 1,0,1); GL11.glColor4f(0f,.5f,1,.7f); drawQuadLine(); GL11.glRotatef(2f, 1,0,1); GL11.glColor4f(0f,.6f,1,.7f); drawQuadLine(); GL11.glRotatef(3f, 1,0,1); GL11.glColor4f(0f,.8f,1,.7f); drawQuadLine(); } GL11.glPopMatrix(); } /** * 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(); } /** * draw a 1x1 square */ public void drawQuadLine() { GL11.glBegin(GL11.GL_LINE_STRIP); { 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.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left } GL11.glEnd(); } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); } //------------------------------------------------------- // for scene tiling //------------------------------------------------------- float Left, Right, Bottom, Top, Near, Far; int tileCol=0, tileRow=0; boolean doGiantScreenShot = false; /** * Set the field of view, view depth, based on camera position. * Also store some values used later by setFrustum(). */ public void setPerspective() { // select projection matrix (controls view on screen) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Field Of Vision, aspect of screen, zNear, zFar GLU.gluPerspective(55f, // zoom in or out of view aspectRatio, // shape of viewport rectangle 1f, // min Z: how far from eye position does view start 50000f); // max Z: how far from eye position does view extend // Prepare values for use by setFrustum() // Should be exact same values as in GLU.gluPerspective() initFrustumValues(55f, aspectRatio, 1f, 50000f); } /** * Call once to initialize perspective settings for giant screenshot. * Use same params as for GLU.gluPerspective(). * * @see setFrustum() */ public void initFrustumValues(float fovy, float aspect, float zNear, float zFar ) { Top = zNear * (float)Math.tan(fovy * 3.14159265 / 360.0); Bottom = -Top; Left = Bottom * aspect; Right = Top * aspect; Near = zNear; Far = zFar; } /** * sets frustum to render one portion of much larger scene. See giantScreenShot(). */ public void setFrustum(int CurrentColumn, int CurrentRow, int TileWidth, int TileHeight, int ImageWidth, int ImageHeight) { float left = Left + (((Right - Left) * (CurrentColumn * TileWidth)) / ImageWidth); float right = left + (((Right - Left) * TileWidth) / ImageWidth); float bottom = Bottom + (Top - Bottom) * (CurrentRow * TileHeight) / ImageHeight; float top = bottom + (Top - Bottom) * TileHeight / ImageHeight; GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glFrustum(left, right, bottom, top, Near, Far); GL11.glMatrixMode(GL11.GL_MODELVIEW); } /** * Render the current scene as a series of tiles, and save each * tile to a file. * * The scale paramater defines the number of rows/columns to use * when tiling, ie. scale=4 means there will be 4 rows and 4 columns, * for 16 tiles total. Scale should be a power of 2. * * tileWidth is proportional to imageWidth, so if tileWidth is 256 and * imageWidth is 1024, then there will be four tiles per row. (Same * thing for 512 tile width and 2048 imagewidth). * * @param scale */ public void giantScreenShot(int scale) { int imageWidth = displayMode.getWidth(); int imageHeight = displayMode.getHeight(); int tileWidth = displayMode.getWidth() / scale; int tileHeight = displayMode.getHeight() / scale; screenShotCounter = 0; // render tiled scene for (int r=0; r < scale; r++) { for (int c=0; c < scale; c++) { // adjust frustum to show one portion of scene setFrustum(c, r, tileWidth, tileHeight, imageWidth, imageHeight); // render the scene renderFrame(); // save the screen image to file screenShot(displayMode.getWidth(), displayMode.getHeight(), "tile"+"_"+screenShotCounter+".png"); } } // return to normal perspective setPerspective(); GL11.glMatrixMode(GL11.GL_MODELVIEW); } //------------------------------------------------------- // for screenShot //------------------------------------------------------- static ByteBuffer framebytes = null; static int[] framepixels = null; static BufferedImage frameimage = null; static int screenShotCounter = 0; public static void screenShot(int width, int height, String saveFilename) { int bindex; // allocate space for RBG pixels (only once) if (framebytes == null) { framebytes = allocBytes(width * height * 3); } if (framepixels == null) { framepixels = new int[width * height]; } // 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 and // flip the pixels vertically (opengl has 0,0 at lower left, java is upper left) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { bindex = ((y * width) + x) * 3; framepixels[ ( (height - y - 1) * width) + x] = //framepixels[ (y * width) + x]; 0xFF000000 // A | ((framebytes.get(bindex) & 0x000000FF) << 16) // R | ((framebytes.get(bindex+1) & 0x000000FF) << 8) // G | ((framebytes.get(bindex+2) & 0x000000FF) << 0); // B } } // save the pixels to PNG try { // Create a BufferedImage with the RGB pixels then save as PNG if (frameimage == null) { frameimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } frameimage.setRGB(0, 0, width, height, framepixels, 0, width); javax.imageio.ImageIO.write(frameimage, "png", new File(saveFilename)); screenShotCounter++; } catch (Exception e) { System.out.println("GLApp.screenShot(): exception " + e); } } public static final int SIZE_BYTE = 1; public static ByteBuffer allocBytes(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_BYTE).order(ByteOrder.nativeOrder()); } }