From 6ac800501f9a3212b2d321cf9aa8ab0c218a39c7 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 3 Apr 2023 15:35:38 +0200 Subject: [PATCH] Fix issue with exporting of transparent image when off-screen rendering is disabled --- .../gui/figure3d/photo/PhotoPanel.java | 91 +++++++++++++++++-- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java index 628d34bd9..0df2e0c98 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java @@ -7,6 +7,7 @@ import java.awt.SplashScreen; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; import java.util.Collection; import java.util.EventObject; import java.util.HashSet; @@ -259,7 +260,7 @@ public class PhotoPanel extends JPanel implements GLEventListener { rr.updateFigure(drawable); needUpdate = false; - draw(drawable, 0); + draw(drawable, 0, true); if (p.isMotionBlurred()) { Bounds b = calculateBounds(); @@ -271,7 +272,7 @@ public class PhotoPanel extends JPanel implements GLEventListener { gl.glAccum(GL2.GL_LOAD, m); for (int i = 1; i <= c; i++) { - draw(drawable, d / c * i); + draw(drawable, d / c * i, true); gl.glAccum(GL2.GL_ACCUM, (1.0f - m) / c); } @@ -279,10 +280,19 @@ public class PhotoPanel extends JPanel implements GLEventListener { } if (!imageCallbacks.isEmpty()) { - BufferedImage i = (new AWTGLReadBufferUtil( - GLProfile.get(GLProfile.GL2), true)) // Set the second parameter to true - .readPixelsToBufferedImage(drawable.getGL(), 0, 0, - drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), true); + final BufferedImage i; + // If off-screen rendering is disabled, and the sky color is transparent, we need to redraw the scene + // in an off-screen framebuffer object (FBO), otherwise the fake transparency rendering will cause the + // exported image to have a fully white background. + if (!Application.getPreferences().getBoolean( + Preferences.OPENGL_USE_FBO, false) && p.getSkyColorOpacity() < 100) { + i = drawToBufferedImage(drawable); + } else { + i = (new AWTGLReadBufferUtil( + GLProfile.get(GLProfile.GL2), true)) // Set the second parameter to true + .readPixelsToBufferedImage(drawable.getGL(), 0, 0, + drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), true); + } final Vector cbs = new Vector( imageCallbacks); imageCallbacks.clear(); @@ -296,6 +306,66 @@ public class PhotoPanel extends JPanel implements GLEventListener { } } + /** + * Draws the scene with fake transparency rendering disabled to an off-screen framebuffer object (FBO) and + * returns the result as a BufferedImage. + * @param drawable The GLAutoDrawable to draw to + * @return The rendered image + */ + private BufferedImage drawToBufferedImage(final GLAutoDrawable drawable) { + GL2 gl = drawable.getGL().getGL2(); + int width = drawable.getSurfaceWidth(); + int height = drawable.getSurfaceHeight(); + + // Create a new framebuffer object (FBO) + int[] fboId = new int[1]; + gl.glGenFramebuffers(1, fboId, 0); + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, fboId[0]); + + // Create a texture to store the rendered image + int[] textureId = new int[1]; + gl.glGenTextures(1, textureId, 0); + gl.glBindTexture(GL.GL_TEXTURE_2D, textureId[0]); + gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, null); + + // Attach the texture to the FBO + gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, textureId[0], 0); + + // Check if the FBO is complete + int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER); + if (status != GL2.GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("Framebuffer not complete"); + } + + // Draw the scene with useFakeTransparencyRendering set to false + draw(drawable, 0, false); + + // Read the pixels from the FBO + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4); + gl.glReadPixels(0, 0, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buffer); + + // Unbind the FBO and delete resources + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0); + gl.glDeleteFramebuffers(1, fboId, 0); + gl.glDeleteTextures(1, textureId, 0); + + // Convert the ByteBuffer to a BufferedImage + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int i = (y * width + x) * 4; + int r = buffer.get(i) & 0xFF; + int g = buffer.get(i + 1) & 0xFF; + int b = buffer.get(i + 2) & 0xFF; + int a = buffer.get(i + 3) & 0xFF; + image.setRGB(x, height - y - 1, (a << 24) | (r << 16) | (g << 8) | b); + } + } + return image; + } + private static void convertColor(Color color, float[] out) { if (color == null) { out[0] = 1; @@ -327,11 +397,12 @@ public class PhotoPanel extends JPanel implements GLEventListener { int r = (int) ((color1.getRed() * inverseRatio) + (color2.getRed() * ratio)); int g = (int) ((color1.getGreen() * inverseRatio) + (color2.getGreen() * ratio)); int b = (int) ((color1.getBlue() * inverseRatio) + (color2.getBlue() * ratio)); + int a = (int) ((color1.getAlpha() * inverseRatio) + (color2.getAlpha() * ratio)); - return new Color(r, g, b); + return new Color(r, g, b, a); } - private void draw(final GLAutoDrawable drawable, float dx) { + private void draw(final GLAutoDrawable drawable, float dx, boolean useFakeTransparencyRendering) { GL2 gl = drawable.getGL().getGL2(); GLU glu = new GLU(); @@ -361,9 +432,9 @@ public class PhotoPanel extends JPanel implements GLEventListener { // Machines that don't use off-screen rendering can't render transparent background, so we create it // artificially by blending the sky color with white (= color that is rendered as transparent background) - if (!Application.getPreferences().getBoolean( + if (useFakeTransparencyRendering && !Application.getPreferences().getBoolean( Preferences.OPENGL_USE_FBO, false)) { - convertColor(blendColors(p.getSkyColor(), new Color(255, 255, 255), 1-p.getSkyColorOpacity()), + convertColor(blendColors(p.getSkyColor(), new Color(255, 255, 255, 0), 1-p.getSkyColorOpacity()), color); } else { convertColor(p.getSkyColor(), color);