From fd418a69eca7b8c1bb74244982305fc6004d0a52 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Thu, 18 Apr 2013 03:16:33 +0200 Subject: Fix Bug 720: Unify all platform specific GLContextImpl specializations; Fix Bug 719 - Windows BITMAP Offscreen Orientation is not propagated through API Fix Bug 719 - Windows BITMAP Offscreen Orientation is not propagated through API Depends on Bug 720, since cleaning up GLContextImpl* is required to move property 'GLContext.isGLOrientationFlippedVertical()' to 'GLDrawable.isGLOriented()' where it belongs! Windows BITMAP GLDrawable impl. isGLOriented() shall return false, while we keep the BITMAPINFOHEADER's height field negative to remove the need for vertical flip when used w/ AWT or Windows, .. Then property 'GLDrawable.isGLOriented()' has to be recognized throughout the utility functions, i.e. TextureData's mustFlipVertically and hence TextureIO writer. Fix Bug 720: Unify all platform specific GLContextImpl specializations GLContextImpl shall have only _one_ unique platform derivative to allow proper swapping of GLDrawables of any type via: - 'GLAutoDrawable.setContext(GLContext newCtx, boolean destroyPrevCtx)', which calls - 'GLContext.setGLDrawable(GLDrawable readWrite, boolean setWriteOnly)' Exception: External context may be specialized. All drawable specific property handling shall be provided and implemented (if possible) via GLDrawable specializations. - GLContext.isGLOrientationFlippedVertical() -> GLDrawable.isGLOriented() - PNGImage.createFromData() takes 'isGLOriented' to properly handle vertical flipping simply by line ordering - TextureIO's PNG writer passes TextureData's getMustFlipVertically() as isGLOriented to PNGImage.createFromData() - GLReadBufferUtil respects GLDrawable's isGLOriented() when creating TextureData instance. - Screenshot respects GLDrawable's isGLOriented() - Screenshot is deprecated, use GLReadBufferUtil. - Removed all PBuffer attributes, i.e. floatingPoint, RenderToTexture and RenderToTextureRectangle. - Allows removal of special pbuffer handling in GLContext* implementations. - Removed also from GLCapabilities* - Removed from deprecated GLPbuffer Impact: - Low, users who desire to render into a texture shall use our FBO GLOffscreenDrawable. - Only use case was the deprecated GLPbuffer - floating point framebuffer technology is still patented anyways :) - Removed Java2DGLContext, which was only used for OSX's GLJPanel Java2D bridge, which is no more supported anyways. --- .../classes/com/jogamp/opengl/swt/GLCanvas.java | 6 + .../com/jogamp/opengl/util/GLReadBufferUtil.java | 17 ++- .../com/jogamp/opengl/util/awt/Screenshot.java | 36 ++++-- .../com/jogamp/opengl/util/texture/TextureIO.java | 2 +- .../jogamp/opengl/util/texture/spi/PNGImage.java | 121 +++++++++++++++------ 5 files changed, 132 insertions(+), 50 deletions(-) (limited to 'src/jogl/classes/com/jogamp/opengl') diff --git a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java index 169266152..ff764d849 100644 --- a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java +++ b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java @@ -588,6 +588,12 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { return clientArea.height; } + @Override + public boolean isGLOriented() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.isGLOriented() : true; + } + @Override public void addGLEventListener(final GLEventListener listener) { helper.addGLEventListener(listener); diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java b/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java index b8709f31c..1345d29bd 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java @@ -111,11 +111,15 @@ public class GLReadBufferUtil { * * @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 + * @param mustFlipVertically indicates weather to flip the data vertically or not. + * The context's drawable {@link GLDrawable#isGLOriented()} state + * is taken into account. + * Vertical flipping is propagated to TextureData + * and handled in a efficient manner there (TextureCoordinates and TextureIO writer). * * @see #GLReadBufferUtil(boolean, boolean) */ - public boolean readPixels(GL gl, boolean flip) { + public boolean readPixels(GL gl, boolean mustFlipVertically) { 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)); @@ -124,6 +128,13 @@ public class GLReadBufferUtil { final int textureInternalFormat, textureDataFormat, textureDataType; final int[] glImplColorReadVals = new int[] { 0, 0 }; + final boolean flipVertically; + if( drawable.isGLOriented() ) { + flipVertically = mustFlipVertically; + } else { + flipVertically = !mustFlipVertically; + } + if(gl.isGL2GL3() && 3 == components) { textureInternalFormat=GL.GL_RGB; textureDataFormat=GL.GL_RGB; @@ -158,7 +169,7 @@ public class GLReadBufferUtil { textureDataFormat, textureDataType, false, false, - flip, + flipVertically, readPixelBuffer, null /* Flusher */); newData = true; diff --git a/src/jogl/classes/com/jogamp/opengl/util/awt/Screenshot.java b/src/jogl/classes/com/jogamp/opengl/util/awt/Screenshot.java index 0022d5c2d..0eab65380 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/awt/Screenshot.java +++ b/src/jogl/classes/com/jogamp/opengl/util/awt/Screenshot.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (c) 2013 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 @@ -43,17 +44,24 @@ import java.io.IOException; import java.nio.ByteBuffer; import javax.imageio.ImageIO; +import javax.media.opengl.GL; import javax.media.opengl.GL2; +import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawable; import javax.media.opengl.GLException; -import javax.media.opengl.glu.gl2.GLUgl2; import com.jogamp.common.util.IOUtil; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.util.GLPixelStorageModes; import com.jogamp.opengl.util.TGAWriter; -/** Utilities for taking screenshots of OpenGL applications. */ - +/** + * Utilities for taking screenshots of OpenGL applications. + * @deprecated Please consider using {@link com.jogamp.opengl.util.GLReadBufferUtil}, + * which is AWT independent and does not require a CPU based vertical image flip + * in case drawable {@link GLDrawable#isGLOriented() is in OpenGL orientation}. + */ public class Screenshot { private Screenshot() {} @@ -149,17 +157,17 @@ public class Screenshot { writer.open(file, width, height, alpha); ByteBuffer bgr = writer.getImageData(); - GL2 gl = GLUgl2.getCurrentGL2(); + GL gl = GLContext.getCurrentGL(); // Set up pixel storage modes GLPixelStorageModes psm = new GLPixelStorageModes(); psm.setPackAlignment(gl, 1); - int readbackType = (alpha ? GL2.GL_ABGR_EXT : GL2.GL_BGR); + int readbackType = (alpha ? GL2.GL_ABGR_EXT : GL2GL3.GL_BGR); // read the BGR values into the image buffer gl.glReadPixels(x, y, width, height, readbackType, - GL2.GL_UNSIGNED_BYTE, bgr); + GL.GL_UNSIGNED_BYTE, bgr); // Restore pixel storage modes psm.restore(gl); @@ -247,7 +255,7 @@ public class Screenshot { int height, boolean alpha) throws GLException { int bufImgType = (alpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); - int readbackType = (alpha ? GL2.GL_ABGR_EXT : GL2.GL_BGR); + int readbackType = (alpha ? GL2.GL_ABGR_EXT : GL2GL3.GL_BGR); if (alpha) { checkExtABGR(); @@ -256,7 +264,8 @@ public class Screenshot { // Allocate necessary storage BufferedImage image = new BufferedImage(width, height, bufImgType); - GL2 gl = GLUgl2.getCurrentGL2(); + GLContext glc = GLContext.getCurrent(); + GL gl = glc.getGL(); // Set up pixel storage modes GLPixelStorageModes psm = new GLPixelStorageModes(); @@ -264,14 +273,16 @@ public class Screenshot { // read the BGR values into the image gl.glReadPixels(x, y, width, height, readbackType, - GL2.GL_UNSIGNED_BYTE, + GL.GL_UNSIGNED_BYTE, ByteBuffer.wrap(((DataBufferByte) image.getRaster().getDataBuffer()).getData())); // Restore pixel storage modes psm.restore(gl); - // Must flip BufferedImage vertically for correct results - ImageUtil.flipImageVertically(image); + if( glc.getGLDrawable().isGLOriented() ) { + // Must flip BufferedImage vertically for correct results + ImageUtil.flipImageVertically(image); + } return image; } @@ -392,7 +403,8 @@ public class Screenshot { } private static void checkExtABGR() { - GL2 gl = GLUgl2.getCurrentGL2(); + GL gl = GLContext.getCurrentGL(); + if (!gl.isExtensionAvailable(GLExtensions.EXT_abgr)) { throw new IllegalArgumentException("Saving alpha channel requires GL_EXT_abgr"); } diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java index 0b0af5625..14ceb6421 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java +++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java @@ -1408,7 +1408,7 @@ public class TextureIO { buf.rewind(); PNGImage image = PNGImage.createFromData(data.getWidth(), data.getHeight(), -1f, -1f, - bytesPerPixel, reversedChannels, buf); + bytesPerPixel, reversedChannels, !data.getMustFlipVertically(), buf); image.write(file, true); return true; } diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java index 847451b41..b4b00e744 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java +++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java @@ -53,20 +53,39 @@ import com.jogamp.common.util.IOUtil; public class PNGImage { private static final boolean DEBUG = Debug.debug("PNGImage"); - /** Creates a PNGImage from data supplied by the end user. Shares - data with the passed ByteBuffer. Assumes the data is already in - the correct byte order for writing to disk, i.e., LUMINANCE, RGB or RGBA bottom-to-top (OpenGL coord). */ + /** + * Creates a PNGImage from data supplied by the end user. Shares + * data with the passed ByteBuffer. Assumes the data is already in + * the correct byte order for writing to disk, i.e., LUMINANCE, RGB or RGBA. + * Orientation is bottom-to-top (OpenGL coord. default) + * or top-to-bottom depending on isGLOriented. + * + * @param width + * @param height + * @param dpiX + * @param dpiY + * @param bytesPerPixel + * @param reversedChannels + * @param isGLOriented see {@link #isGLOriented()}. + * @param data + * @return + */ public static PNGImage createFromData(int width, int height, double dpiX, double dpiY, - int bytesPerPixel, boolean reversedChannels, ByteBuffer data) { - return new PNGImage(width, height, dpiX, dpiY, bytesPerPixel, reversedChannels, data); + int bytesPerPixel, boolean reversedChannels, boolean isGLOriented, ByteBuffer data) { + return new PNGImage(width, height, dpiX, dpiY, bytesPerPixel, reversedChannels, isGLOriented, data); } - /** Reads a PNG image from the specified InputStream. */ + /** + * Reads a PNG image from the specified InputStream. + *

+ * Implicitly flip image to GL orientation, see {@link #isGLOriented()}. + *

+ */ public static PNGImage read(InputStream in) throws IOException { return new PNGImage(in); } - /** Reverse read and store, implicitly flip image to GL coords. */ + /** Reverse read and store, implicitly flip image to GL orientation, see {@link #isGLOriented()}. */ private static final int getPixelRGBA8(ByteBuffer d, int dOff, int[] scanline, int lineOff, boolean hasAlpha) { if(hasAlpha) { d.put(dOff--, (byte)scanline[lineOff + 3]); // A @@ -76,27 +95,28 @@ public class PNGImage { d.put(dOff--, (byte)scanline[lineOff ]); // R return dOff; } - /** Reverse read and store, implicitly flip image from GL coords. Handle reversed channels (BGR[A])*/ - private static int setPixelRGBA8(ImageLine line, int lineOff, ByteBuffer d, int dOff, boolean hasAlpha, boolean reversedChannels) { - if(reversedChannels) { - line.scanline[lineOff ] = d.get(dOff--); // R, A - line.scanline[lineOff + 1] = d.get(dOff--); // G, B - line.scanline[lineOff + 2] = d.get(dOff--); // B, G + + /** Reverse write and store, implicitly flip image from current orientation, see {@link #isGLOriented()}. Handle reversed channels (BGR[A]). */ + private int setPixelRGBA8(ImageLine line, int lineOff, ByteBuffer d, int dOff, boolean hasAlpha) { + if( reversedChannels ) { if(hasAlpha) { - line.scanline[lineOff + 3] = d.get(dOff--);// R + line.scanline[lineOff + 3] = d.get(dOff++); // A } + line.scanline[lineOff + 2] = d.get(dOff++); // R + line.scanline[lineOff + 1] = d.get(dOff++); // G + line.scanline[lineOff ] = d.get(dOff++); // B } else { + line.scanline[lineOff ] = d.get(dOff++); // R + line.scanline[lineOff + 1] = d.get(dOff++); // G + line.scanline[lineOff + 2] = d.get(dOff++); // B if(hasAlpha) { - line.scanline[lineOff + 3] = d.get(dOff--); // A + line.scanline[lineOff + 3] = d.get(dOff++); // A } - line.scanline[lineOff + 2] = d.get(dOff--); // B - line.scanline[lineOff + 1] = d.get(dOff--); // G - line.scanline[lineOff ] = d.get(dOff--); // R } - return dOff; + return isGLOriented ? dOff - bytesPerPixel - bytesPerPixel : dOff; } - private PNGImage(int width, int height, double dpiX, double dpiY, int bytesPerPixel, boolean reversedChannels, ByteBuffer data) { + private PNGImage(int width, int height, double dpiX, double dpiY, int bytesPerPixel, boolean reversedChannels, boolean isGLOriented, ByteBuffer data) { pixelWidth=width; pixelHeight=height; dpi = new double[] { dpiX, dpiY }; @@ -109,6 +129,7 @@ public class PNGImage { } this.bytesPerPixel = bytesPerPixel; this.reversedChannels = reversedChannels; + this.isGLOriented = isGLOriented; this.data = data; } @@ -162,13 +183,14 @@ public class PNGImage { data = Buffers.newDirectByteBuffer(bytesPerPixel * pixelWidth * pixelHeight); reversedChannels = false; // RGB[A] + isGLOriented = true; int dataOff = bytesPerPixel * pixelWidth * pixelHeight - 1; // start at end-of-buffer, reverse store int[] rgbaScanline = indexed ? new int[imgInfo.cols * channels] : null; for (int row = 0; row < pixelHeight; row++) { final ImageLine l1 = pngr.readRow(row); - int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ; // start w/ last pixel in line, reverse read + int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ; // start w/ last pixel in line, reverse read (PNG top-left -> OpenGL bottom-left origin) if( indexed ) { for (int j = pixelWidth - 1; j >= 0; j--) { rgbaScanline = ImageLineHelper.palette2rgb(l1, plte, trns, rgbaScanline); // reuse rgbaScanline and update if resized @@ -189,7 +211,8 @@ public class PNGImage { pngr.end(); } private final int pixelWidth, pixelHeight, glFormat, bytesPerPixel; - private boolean reversedChannels; + private final boolean reversedChannels; + private final boolean isGLOriented; private final double[] dpi; private final ByteBuffer data; @@ -202,6 +225,16 @@ public class PNGImage { /** Returns true if data has the channels reversed to BGR or BGRA, otherwise RGB or RGBA is expected. */ public boolean getHasReversedChannels() { return reversedChannels; } + /** + * Returns true if the drawable is rendered in + * OpenGL's coordinate system, origin at bottom left. + * Otherwise returns false, i.e. origin at top left. + *

+ * Default impl. is true, i.e. OpenGL coordinate system. + *

+ */ + public boolean isGLOriented() { return isGLOriented; } + /** Returns the dpi of the image. */ public double[] getDpi() { return dpi; } @@ -231,20 +264,40 @@ public class PNGImage { // png.getMetadata().setText("my key", "my text"); final boolean hasAlpha = 4 == bytesPerPixel; final ImageLine l1 = new ImageLine(imi); - int dataOff = bytesPerPixel * pixelWidth * pixelHeight - 1; // start at end-of-buffer, reverse read - for (int row = 0; row < pixelHeight; row++) { - int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ; // start w/ last pixel in line, reverse store - if(1 == bytesPerPixel) { - for (int j = pixelWidth - 1; j >= 0; j--) { - l1.scanline[lineOff--] = data.get(dataOff--); // // Luminance, 1 bytesPerPixel - } - } else { - for (int j = pixelWidth - 1; j >= 0; j--) { - dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha, reversedChannels); - lineOff -= bytesPerPixel; + if( isGLOriented ) { + // start at last pixel at end-of-buffer, reverse read (OpenGL bottom-left -> PNG top-left origin) + int dataOff = ( pixelWidth * bytesPerPixel * ( pixelHeight - 1 ) ) + // full lines - 1 line + ( ( pixelWidth - 1 ) * bytesPerPixel ); // one line - 1 pixel + for (int row = 0; row < pixelHeight; row++) { + int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ; // start w/ last pixel in line, reverse store (OpenGL bottom-left -> PNG top-left origin) + if(1 == bytesPerPixel) { + for (int j = pixelWidth - 1; j >= 0; j--) { + l1.scanline[lineOff--] = data.get(dataOff--); // // Luminance, 1 bytesPerPixel + } + } else { + for (int j = pixelWidth - 1; j >= 0; j--) { + dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha); + lineOff -= bytesPerPixel; + } } + png.writeRow(l1, row); } - png.writeRow(l1, row); + } else { + int dataOff = 0; // start at first pixel at start-of-buffer, normal read (same origin: top-left) + for (int row = 0; row < pixelHeight; row++) { + int lineOff = 0; // start w/ first pixel in line, normal store (same origin: top-left) + if(1 == bytesPerPixel) { + for (int j = pixelWidth - 1; j >= 0; j--) { + l1.scanline[lineOff++] = data.get(dataOff++); // // Luminance, 1 bytesPerPixel + } + } else { + for (int j = pixelWidth - 1; j >= 0; j--) { + dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha); + lineOff += bytesPerPixel; + } + } + png.writeRow(l1, row); + } } png.end(); } finally { -- cgit v1.2.3