/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.util; import java.awt.Component; import java.awt.Container; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.CellRendererPane; import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.Timer; import org.mt4j.MTApplication; import org.mt4j.util.opengl.GLTextureSettings; import org.mt4j.util.opengl.GLTexture; import org.mt4j.util.opengl.GLTexture.EXPANSION_FILTER; import org.mt4j.util.opengl.GLTexture.SHRINKAGE_FILTER; import org.mt4j.util.opengl.GLTexture.WRAP_MODE; import processing.core.PImage; /** * This class can be used to render java swing JComponents to an * OpenGL texture. So this can at the moment only be used in OpenGL. * <br>After instantiating this class the component will be rendered * by a call to <code>scheduleRefresh()</code>. * The OpenGL texture can then be retrieved by the * <code>getTextureToRenderTo()</code> method. * <p>Note: * Order of adding components is the order in which they appear in z-order * -> add ontop comps first * <p>Note: * The JComponents can only be displayed if setBounds or setSize is called with positive values * (probably only neccessary when using no layoutManager). * * @author Christopher Ruff */ public class SwingTextureRenderer { /** The comp to draw. */ private Component compToDraw; /** The mt app. */ private MTApplication mtApp; /** The texture to render to. */ private GLTexture textureToRenderTo; /** The temp image. */ private PImage tempImage; /** The tmp bounds rect. */ private Rectangle tmpBoundsRect; /** The t. */ private Timer t; private List<ActionListener> paintedListeners; //TODO enable non OpenGL version /** * The Constructor. * * @param mtApp the mt app * @param compToRender the comp to render into texture */ public SwingTextureRenderer(MTApplication mtApp, Component compToRender) { super(); this.compToDraw = compToRender; this.mtApp = mtApp; paintedListeners = new ArrayList<ActionListener>(); this.tmpBoundsRect = new Rectangle(); //Add component to PApplet //make comptoDraw invisible to not actually show it on screen compToDraw.setVisible(false); mtApp.add(compToDraw); // MouseEvent me = new MouseEvent( // compToDraw, //Source == dispatch comp? // MouseEvent.MOUSE_PRESSED, // System.currentTimeMillis(), // MouseEvent.BUTTON1_MASK, // 10, 10, // 1, // false); // compToDraw.dispatchEvent(me); //Create the texture //// notVisibleImage = new PImage(rectC.width, rectC.height); // GLTextureParameters tp = new GLTextureParameters(); // tp.minFilter = GLConstants.LINEAR; //To avoid mipmapgeneration at each update //// tp.minFilter = GLConstants.LINEAR_MIPMAP_LINEAR; //For mip maps // tp.magFilter = GLConstants.LINEAR; // final Rectangle rectC = SwingUtilities.getLocalBounds(compToDraw); // textureToRenderTo = new GLTexture(mtApp, rectC.width, rectC.height, tp, false); // // //TODO check if we are in the rendering thread or not // if (!mtApp.isRenderThreadCurrent()){ // mtApp.invokeLater(new Runnable() { // public void run() { // textureToRenderTo.initTexture(rectC.width, rectC.height); // } // }); // } GLTextureSettings ts = new GLTextureSettings(); ts.shrinkFilter = SHRINKAGE_FILTER.BilinearNoMipMaps; // ts.shrinkFilter = SHRINKAGE_FILTER.Trilinear; //For better quality ts.expansionFilter = EXPANSION_FILTER.Bilinear; ts.wrappingHorizontal = WRAP_MODE.CLAMP_TO_EDGE; ts.wrappingVertical = WRAP_MODE.CLAMP_TO_EDGE; textureToRenderTo = new GLTexture(mtApp, ts); // textureToRenderTo = new MTTexture(mtApp, rectC.width, rectC.height, ts); //This would also init the gl texture textureToRenderTo.width = rectC.width; //So that tex coords of shape get scaled correctly textureToRenderTo.height = rectC.height; if (!mtApp.isRenderThreadCurrent()){ mtApp.invokeLater(new Runnable() { public void run() { // textureToRenderTo.initTexture(rectC.width, rectC.height); textureToRenderTo.setupGLTexture(rectC.width, rectC.height); } }); }else{ // textureToRenderTo.initTexture(rectC.width, rectC.height); textureToRenderTo.setupGLTexture(rectC.width, rectC.height); } // this.scheduleRefresh(); //ENABLE? } /** * Gets the texture to render to. * * @return the texture to render to */ public GLTexture getTextureToRenderTo(){ return this.textureToRenderTo; } /** * Start timed refresh. * * @param time the time */ public void startTimedRefresh(int time){ t = new Timer(time, new ActionListener() { //@Override public void actionPerformed(ActionEvent arg0) { scheduleRefresh(); } }); t.start(); } /** * Stop timer. */ public void stopTimedRefresh(){ if (t!=null){ t.stop(); } } /** * This schedules a new pass of rendering the JComponent into a texture. * Use this to render the component into the texture or to update the texture if the * JComponent changed. */ public void scheduleRefresh(){ // try { // SwingUtilities.invokeAndWait(new Runnable(){ SwingUtilities.invokeLater(new Runnable(){ //@Override public void run() { Rectangle boundingRect = SwingUtilities.getLocalBounds(compToDraw); //System.out.println("BoundingRect w:" + boundingRect.width + " h:" + boundingRect.height); //FIXME as the 2nd parameter container, really use the parent? paintComponentOffscreen(compToDraw, compToDraw.getParent(), boundingRect); firePaintOccurred(); } }); // } catch (InterruptedException e) { // e.printStackTrace(); // } catch (InvocationTargetException e) { // e.printStackTrace(); // } } private BufferedImage img; private Graphics2D g; //TODO dispose by hand! CellRendererPane crp; /** * Paint not visible component. * * @param c the c * @param con the con * @param rect the rect * * @return the buffered image */ private BufferedImage paintComponentOffscreen(Component c, Container con, Rectangle rect) { //FIXME n�tig? paintComponent added anscheinend c nochmal zu con!? con.remove(c); /* BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = img.createGraphics(); */ //Lazily initialize if (img == null){ img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_ARGB); } if (g == null){ g = img.createGraphics(); } if (c instanceof JComponent) { JComponent jComp = (JComponent) c; jComp.setOpaque(false); } /* if (!c.isLightweight()){ System.err.println("Component to paint (" + c.getName() + ") is not lightweight -> undeterministic behaviour!"); } */ // SwingUtilities.paintComponent(g, c, con, rect); //The following does what SwingUtilities.paintComponent does if (crp == null){ crp = new CellRendererPane(); } crp.add(c); con.add(crp); crp.paintComponent(g, c, con, rect.x, rect.y, rect.width, rect.height, false); //FIXME TEST, BENEFITS? con.remove(crp); crp.remove(c); con.add(c); //Dispose graphics context // g.dispose(); //reuse context! //Bug? c's bounds get changed by the paintComponent method!? //This is a hack that seems to fix it c.getBounds(tmpBoundsRect); c.setBounds(tmpBoundsRect.width, tmpBoundsRect.height, Math.abs(tmpBoundsRect.x), Math.abs(tmpBoundsRect.y)); //TODO evtl direkt in Intbuffer schreiben ohne PImage // this.tempImage = new PImage(img); // /* //Put buffered image into PImage pixels BufferedImage bi = img; int width = bi.getWidth(); int height = bi.getHeight(); if (tempImage == null){ tempImage = new PImage(width, height); tempImage.loadPixels(); } WritableRaster raster = bi.getRaster(); raster.getDataElements(0, 0, width, height, tempImage.pixels); // */ //Refresh GL texture next draw mtApp.invokeLater(new Runnable() { //@Override public void run() { // textureToRenderTo.putPixelsIntoTexture(tempImage); //OpenGL // textureToRenderTo.putImage(tempImage); //SLOWER but also fills the PImage pixels[] with the image //firePaintOccurred(); textureToRenderTo.loadGLTexture(tempImage); //OpenGL } }); return img; } private void firePaintOccurred(){ for (Iterator<ActionListener> iterator = paintedListeners.iterator(); iterator.hasNext();) { ActionListener a = iterator.next(); a.actionPerformed(new ActionEvent(this, 0,"")); } } public void addPaintOcurredListener(ActionListener a){ this.paintedListeners.add(a); } public void removePaintOcurredListener(ActionListener a){ if (this.paintedListeners.contains(a)){ this.paintedListeners.remove(a); } } /* //Swing render usage example: // Render swing component to texture \\ JPanel panel1 = new JPanel(true); panel1.setVisible(false); panel1.setBounds(0,0,200,200); //Button b = new B(); b.setBounds(0,0,100,100); b.setText("Hello"); JTextArea t = new JTextArea("text!"); t.setBounds(100,100,50,50); JPanel innerP = new JPanel(); innerP.setBackground(new Color(120)); innerP.setBounds(0,0,150,150); JButton otherButton = new JButton("lorem ipsum"); otherButton.setBounds(0,0, 120,120); innerP.add(otherButton); panel1.add(b); panel1.add(innerP); panel1.add(t); //Simulate mouse down on the button b.dispatchEvent(new MouseEvent(b, MouseEvent.MOUSE_PRESSED, 0, MouseEvent.BUTTON1_MASK, 10,10, 40,40, 1, false, MouseEvent.BUTTON1)); swingTex = new SwingTextureRenderer(pa, panel1); MTRectangle rectangle = new MTRectangle(300,300,0, 200,200, pa); rectangle.setTexture(swingTex.getTextureToRenderTo()); this.getCanvas().addChild(rectangle); */ }