/** * Copyright 2010 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.opengl.util; import com.jogamp.common.nio.Buffers; import java.io.File; import java.io.IOException; import java.nio.*; import javax.media.opengl.*; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.TextureIO; /** * Utility to read out the current FB to TextureData, optionally writing the data back to a texture object. *

May be used directly to write the TextureData to file (screenshot).

*/ public class GLReadBufferUtil { protected final int components, alignment; protected final Texture readTexture; protected final GLPixelStorageModes psm; protected int readPixelSizeLast = 0; protected ByteBuffer readPixelBuffer = null; protected TextureData readTextureData = null; /** * @param alpha true for RGBA readPixels, otherwise RGB readPixels. Disclaimer: Alpha maybe forced on ES platforms! * @param write2Texture true if readPixel's TextureData shall be written to a 2d Texture */ public GLReadBufferUtil(boolean alpha, boolean write2Texture) { components = alpha ? 4 : 3 ; alignment = alpha ? 4 : 1 ; readTexture = write2Texture ? new Texture(GL.GL_TEXTURE_2D) : null ; psm = new GLPixelStorageModes(); } public boolean isValid() { return null!=readTextureData && null!=readPixelBuffer ; } public boolean hasAlpha() { return 4 == components ? true : false ; } public GLPixelStorageModes getGLPixelStorageModes() { return psm; } /** * @return the raw pixel ByteBuffer, filled by {@link #readPixels(GLAutoDrawable, boolean)} */ public ByteBuffer getPixelBuffer() { return readPixelBuffer; } /** * rewind the raw pixel ByteBuffer */ public void rewindPixelBuffer() { if( null != readPixelBuffer ) { readPixelBuffer.rewind(); } } /** * @return the resulting TextureData, filled by {@link #readPixels(GLAutoDrawable, boolean)} */ public TextureData getTextureData() { return readTextureData; } /** * @return the Texture object filled by {@link #readPixels(GLAutoDrawable, boolean)}, * if this instance writes to a 2d Texture, otherwise null. * @see #GLReadBufferUtil(boolean, boolean) */ public Texture getTexture() { return readTexture; } /** * Write the TextureData filled by {@link #readPixels(GLAutoDrawable, boolean)} to file */ public void write(File dest) { try { TextureIO.write(readTextureData, dest); rewindPixelBuffer(); } catch (IOException ex) { throw new RuntimeException("can not write to file: " + dest.getAbsolutePath(), ex); } } /** * Read the drawable's pixels to TextureData and Texture, if requested at construction * * @param gl the current GL context object. It's read drawable is being used as the pixel source. * @param drawable the drawable to read from * @param flip weather to flip the data vertically or not * * @see #GLReadBufferUtil(boolean, boolean) */ public boolean readPixels(GL gl, boolean flip) { final int glerr0 = gl.glGetError(); if(GL.GL_NO_ERROR != glerr0) { System.err.println("Info: GLReadBufferUtil.readPixels: pre-exisiting GL error 0x"+Integer.toHexString(glerr0)); } final GLDrawable drawable = gl.getContext().getGLReadDrawable(); final int textureInternalFormat, textureDataFormat, textureDataType; final int[] glImplColorReadVals = new int[] { 0, 0 }; if(gl.isGL2GL3() && 3 == components) { textureInternalFormat=GL.GL_RGB; textureDataFormat=GL.GL_RGB; textureDataType = GL.GL_UNSIGNED_BYTE; } else if(gl.isGLES2Compatible() || gl.isExtensionAvailable(GLExtensions.OES_read_format)) { gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_FORMAT, glImplColorReadVals, 0); gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_TYPE, glImplColorReadVals, 1); textureInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; textureDataFormat = glImplColorReadVals[0]; textureDataType = glImplColorReadVals[1]; } else { // RGBA read is safe for all GL profiles textureInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; textureDataFormat=GL.GL_RGBA; textureDataType = GL.GL_UNSIGNED_BYTE; } final int tmp[] = new int[1]; final int readPixelSize = GLBuffers.sizeof(gl, tmp, textureDataFormat, textureDataType, drawable.getWidth(), drawable.getHeight(), 1, true); boolean newData = false; if(readPixelSize>readPixelSizeLast) { readPixelBuffer = Buffers.newDirectByteBuffer(readPixelSize); readPixelSizeLast = readPixelSize ; try { readTextureData = new TextureData( gl.getGLProfile(), textureInternalFormat, drawable.getWidth(), drawable.getHeight(), 0, textureDataFormat, textureDataType, false, false, flip, readPixelBuffer, null /* Flusher */); newData = true; } catch (Exception e) { readTextureData = null; readPixelBuffer = null; readPixelSizeLast = 0; throw new RuntimeException("can not fetch offscreen texture", e); } } else { readTextureData.setInternalFormat(textureInternalFormat); readTextureData.setWidth(drawable.getWidth()); readTextureData.setHeight(drawable.getHeight()); readTextureData.setPixelFormat(textureDataFormat); readTextureData.setPixelType(textureDataType); } boolean res = null!=readPixelBuffer; if(res) { psm.setAlignment(gl, alignment, alignment); readPixelBuffer.clear(); try { gl.glReadPixels(0, 0, drawable.getWidth(), drawable.getHeight(), textureDataFormat, textureDataType, readPixelBuffer); } catch(GLException gle) { res = false; gle.printStackTrace(); } readPixelBuffer.position(readPixelSize); readPixelBuffer.flip(); final int glerr1 = gl.glGetError(); if(GL.GL_NO_ERROR != glerr1) { System.err.println("GLReadBufferUtil.readPixels: readPixels error 0x"+Integer.toHexString(glerr1)+ " "+drawable.getWidth()+"x"+drawable.getHeight()+ ", fmt 0x"+Integer.toHexString(textureDataFormat)+", type 0x"+Integer.toHexString(textureDataType)+ ", impl-fmt 0x"+Integer.toHexString(glImplColorReadVals[0])+", impl-type 0x"+Integer.toHexString(glImplColorReadVals[1])+ ", "+readPixelBuffer+", sz "+readPixelSize); res = false; } if(res && null != readTexture) { if(newData) { readTexture.updateImage(gl, readTextureData); } else { readTexture.updateSubImage(gl, readTextureData, 0, 0, 0, // src offset 0, 0, // dst offset drawable.getWidth(), drawable.getHeight()); } readPixelBuffer.rewind(); } psm.restore(gl); } return res; } public void dispose(GL gl) { if(null != readTexture) { readTexture.destroy(gl); readTextureData = null; } if(null != readPixelBuffer) { readPixelBuffer.clear(); readPixelBuffer = null; } readPixelSizeLast = 0; } }