diff options
author | Sven Gothel <[email protected]> | 2014-02-23 14:51:06 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-02-23 14:51:06 +0100 |
commit | 3352601e0860584509adf2b76f993d03893ded4b (patch) | |
tree | 974fccc8c0eb2f5ad9d4ffd741dfc35869ed67b5 /src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java | |
parent | f51933f0ebe9ae030c26c066e59a728ce08b8559 (diff) | |
parent | c67de337a8aaf52e36104c3f13e273aa19d21f1f (diff) |
Merge branch 'master' into stash_glyphcache
Conflicts:
make/scripts/tests.sh
src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java
src/jogl/classes/com/jogamp/graph/curve/Region.java
src/jogl/classes/com/jogamp/graph/curve/opengl/GLRegion.java
src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java
src/jogl/classes/com/jogamp/graph/curve/opengl/Renderer.java
src/jogl/classes/com/jogamp/graph/curve/opengl/TextRenderer.java
src/jogl/classes/com/jogamp/graph/font/Font.java
src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java
src/jogl/classes/jogamp/graph/curve/text/GlyphShape.java
src/jogl/classes/jogamp/graph/curve/text/GlyphString.java
src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java
src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java
src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java b/src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java new file mode 100644 index 000000000..1c1d2350a --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/awt/AWTTilePainter.java @@ -0,0 +1,400 @@ +/** + * Copyright 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 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 jogamp.opengl.awt; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Set; +import java.util.Map.Entry; + +import javax.imageio.ImageIO; +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.opengl.GL; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilitiesImmutable; +import javax.media.opengl.GLEventListener; + +import jogamp.opengl.Debug; + +import com.jogamp.opengl.util.TileRenderer; +import com.jogamp.opengl.util.TileRendererBase; +import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes; +import com.jogamp.opengl.util.awt.AWTGLPixelBuffer; +import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.AWTGLPixelBufferProvider; + +/** + * Implementing AWT {@link Graphics2D} based {@link TileRenderer} <i>painter</i>. + * <p> + * Maybe utilized for AWT printing. + * </p> + */ +public class AWTTilePainter { + private static final boolean DEBUG_TILES = Debug.debug("TileRenderer.PNG"); + + public final TileRenderer renderer; + public final int componentCount; + public final double scaleMatX, scaleMatY; + public final int customTileWidth, customTileHeight, customNumSamples; + public final boolean verbose; + + /** Default for OpenGL: True */ + public boolean flipVertical; + /** Default for OpenGL: True */ + public boolean originBottomLeft; + private AWTGLPixelBuffer tBuffer = null; + private BufferedImage vFlipImage = null; + private Graphics2D g2d = null; + private AffineTransform saveAT = null; + + public static void dumpHintsAndScale(Graphics2D g2d) { + final RenderingHints rHints = g2d.getRenderingHints(); + final Set<Entry<Object, Object>> rEntries = rHints.entrySet(); + int count = 0; + for(Iterator<Entry<Object, Object>> rEntryIter = rEntries.iterator(); rEntryIter.hasNext(); count++) { + final Entry<Object, Object> rEntry = rEntryIter.next(); + System.err.println("Hint["+count+"]: "+rEntry.getKey()+" -> "+rEntry.getValue()); + } + final AffineTransform aTrans = g2d.getTransform(); + if( null != aTrans ) { + System.err.println(" type "+aTrans.getType()); + System.err.println(" scale "+aTrans.getScaleX()+" x "+aTrans.getScaleY()); + System.err.println(" move "+aTrans.getTranslateX()+" x "+aTrans.getTranslateY()); + System.err.println(" mat "+aTrans); + } else { + System.err.println(" null transform"); + } + } + + /** + * @return resulting number of samples by comparing w/ {@link #customNumSamples} and the caps-config, 0 if disabled + */ + public int getNumSamples(GLCapabilitiesImmutable caps) { + if( 0 > customNumSamples ) { + return 0; + } else if( 0 < customNumSamples ) { + if ( !caps.getGLProfile().isGL2ES3() ) { + return 0; + } + return Math.max(caps.getNumSamples(), customNumSamples); + } else { + return caps.getNumSamples(); + } + } + + /** + * Assumes a configured {@link TileRenderer}, i.e. + * an {@link TileRenderer#attachAutoDrawable(GLAutoDrawable) attached} + * {@link GLAutoDrawable} with {@link TileRenderer#setTileSize(int, int, int) set tile size}. + * <p> + * Sets the renderer to {@link TileRenderer#TR_TOP_TO_BOTTOM} row order. + * </p> + * <p> + * <code>componentCount</code> reflects opaque, i.e. 4 if non opaque. + * </p> + * @param renderer + * @param componentCount + * @param scaleMatX {@link Graphics2D} {@link Graphics2D#scale(double, double) scaling factor}, i.e. rendering 1/scaleMatX * width pixels + * @param scaleMatY {@link Graphics2D} {@link Graphics2D#scale(double, double) scaling factor}, i.e. rendering 1/scaleMatY * height pixels + * @param numSamples custom multisampling value: < 0 turns off, == 0 leaves as-is, > 0 enables using given num samples + * @param tileWidth custom tile width for {@link TileRenderer#setTileSize(int, int, int) tile renderer}, pass -1 for default. + * @param tileHeight custom tile height for {@link TileRenderer#setTileSize(int, int, int) tile renderer}, pass -1 for default. + * @param verbose + */ + public AWTTilePainter(TileRenderer renderer, int componentCount, double scaleMatX, double scaleMatY, int numSamples, int tileWidth, int tileHeight, boolean verbose) { + this.renderer = renderer; + this.renderer.setGLEventListener(preTileGLEL, postTileGLEL); + this.componentCount = componentCount; + this.scaleMatX = scaleMatX; + this.scaleMatY = scaleMatY; + this.customNumSamples = numSamples; + this.customTileWidth= tileWidth; + this.customTileHeight = tileHeight; + this.verbose = verbose; + this.flipVertical = true; + } + + @Override + public String toString() { + return "AWTTilePainter[flipVertical "+flipVertical+", startFromBottom "+originBottomLeft+", "+ + renderer.toString()+"]"; + } + + /** + * @param flipVertical if <code>true</code>, the image will be flipped vertically (Default for OpenGL). + * @param originBottomLeft if <code>true</code>, the image's origin is on the bottom left (Default for OpenGL). + */ + public void setGLOrientation(boolean flipVertical, boolean originBottomLeft) { + this.flipVertical = flipVertical; + this.originBottomLeft = originBottomLeft; + } + + private static Rectangle2D getClipBounds2D(Graphics2D g) { + final Shape shape = g.getClip(); + return null != shape ? shape.getBounds2D() : null; + } + private static Rectangle2D clipNegative(Rectangle2D in) { + if( null == in ) { return null; } + double x=in.getX(), y=in.getY(), width=in.getWidth(), height=in.getHeight(); + if( 0 > x ) { + width += x; + x = 0; + } + if( 0 > y ) { + height += y; + y = 0; + } + return new Rectangle2D.Double(x, y, width, height); + } + + /** + * Caches the {@link Graphics2D} instance for rendering. + * <p> + * Copies the current {@link Graphics2D} {@link AffineTransform} + * and scales {@link Graphics2D} w/ <code>scaleMatX</code> x <code>scaleMatY</code>.<br> + * After rendering, the {@link AffineTransform} should be reset via {@link #resetGraphics2D()}. + * </p> + * <p> + * Sets the {@link TileRenderer}'s {@link TileRenderer#setImageSize(int, int) image size} + * and {@link TileRenderer#setTileOffset(int, int) tile offset} according the + * the {@link Graphics2D#getClipBounds() graphics clip bounds}. + * </p> + * @param g2d Graphics2D instance used for transform and clipping + * @param width width of the AWT component in case clipping is null + * @param height height of the AWT component in case clipping is null + * @throws NoninvertibleTransformException if the {@link Graphics2D}'s {@link AffineTransform} {@link AffineTransform#invert() inversion} fails. + * Since inversion is tested before scaling the given {@link Graphics2D}, caller shall ignore the whole <i>term</i>. + */ + public void setupGraphics2DAndClipBounds(Graphics2D g2d, int width, int height) throws NoninvertibleTransformException { + this.g2d = g2d; + saveAT = g2d.getTransform(); + if( null == saveAT ) { + saveAT = new AffineTransform(); // use identity + } + // We use double precision for scaling + // + // Setup original rectangles + final Rectangle2D dClipOrigR = getClipBounds2D(g2d); + final Rectangle2D dClipOrig = clipNegative(dClipOrigR); + final Rectangle2D dImageSizeOrig = new Rectangle2D.Double(0, 0, width, height); + + // Retrieve scaled image-size and clip-bounds + // Note: Clip bounds lie within image-size! + final Rectangle2D dImageSizeScaled, dClipScaled; + { + final AffineTransform scaledATI; + { + final AffineTransform scaledAT = new AffineTransform(saveAT); + scaledAT.scale(scaleMatX, scaleMatY); + scaledATI = scaledAT.createInverse(); // -> NoninvertibleTransformException + } + Shape s0 = saveAT.createTransformedShape(dImageSizeOrig); // user in + dImageSizeScaled = scaledATI.createTransformedShape(s0).getBounds2D(); // scaled out + if( null == dClipOrig ) { + dClipScaled = (Rectangle2D) dImageSizeScaled.clone(); + } else { + s0 = saveAT.createTransformedShape(dClipOrig); // user in + dClipScaled = scaledATI.createTransformedShape(s0).getBounds2D(); // scaled out + } + } + final Rectangle iClipScaled = dClipScaled.getBounds(); + final Rectangle iImageSizeScaled = dImageSizeScaled.getBounds(); + renderer.setImageSize(iImageSizeScaled.width, iImageSizeScaled.height); + renderer.clipImageSize(iClipScaled.width, iClipScaled.height); + final int clipH = Math.min(iImageSizeScaled.height, iClipScaled.height); + // Clip bounds lie within image-size! + // GL y-offset is lower-left origin, AWT y-offset upper-left. + scaledYOffset = iClipScaled.y; + renderer.setTileOffset(iClipScaled.x, iImageSizeScaled.height - ( iClipScaled.y + clipH )); + + // Scale actual Grahics2D matrix + g2d.scale(scaleMatX, scaleMatY); + + if( verbose ) { + System.err.println("AWT print.0: image "+dImageSizeOrig + " -> " + dImageSizeScaled + " -> " + iImageSizeScaled); + System.err.println("AWT print.0: clip "+dClipOrigR + " -> " + dClipOrig + " -> " + dClipScaled + " -> " + iClipScaled); + System.err.println("AWT print.0: "+renderer); + } + } + private int scaledYOffset; + + /** See {@ #setupGraphics2DAndClipBounds(Graphics2D)}. */ + public void resetGraphics2D() { + g2d.setTransform(saveAT); + } + + /** + * Disposes resources and {@link TileRenderer#detachAutoDrawable() detaches} + * the {@link TileRenderer}'s {@link GLAutoDrawable}. + */ + public void dispose() { + renderer.detachAutoDrawable(); // tile-renderer -> printGLAD + g2d = null; + if( null != tBuffer ) { + tBuffer.dispose(); + tBuffer = null; + } + if( null != vFlipImage ) { + vFlipImage.flush(); + vFlipImage = null; + } + } + + final GLEventListener preTileGLEL = new GLEventListener() { + @Override + public void init(GLAutoDrawable drawable) {} + @Override + public void dispose(GLAutoDrawable drawable) {} + @Override + public void display(GLAutoDrawable drawable) { + final GL gl = drawable.getGL(); + if( null == tBuffer ) { + final int tWidth = renderer.getParam(TileRenderer.TR_TILE_WIDTH); + final int tHeight = renderer.getParam(TileRenderer.TR_TILE_HEIGHT); + final AWTGLPixelBufferProvider printBufferProvider = new AWTGLPixelBufferProvider( true /* allowRowStride */ ); + final GLPixelAttributes pixelAttribs = printBufferProvider.getAttributes(gl, componentCount); + tBuffer = printBufferProvider.allocate(gl, pixelAttribs, tWidth, tHeight, 1, true, 0); + renderer.setTileBuffer(tBuffer); + if( flipVertical ) { + vFlipImage = new BufferedImage(tBuffer.width, tBuffer.height, tBuffer.image.getType()); + } else { + vFlipImage = null; + } + } + if( verbose ) { + System.err.println("XXX tile-pre "+renderer); + } + } + @Override + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} + }; + static int _counter = 0; + final GLEventListener postTileGLEL = new GLEventListener() { + @Override + public void init(GLAutoDrawable drawable) { + } + @Override + public void dispose(GLAutoDrawable drawable) {} + @Override + public void display(GLAutoDrawable drawable) { + final DimensionImmutable cis = renderer.getClippedImageSize(); + final int tWidth = renderer.getParam(TileRendererBase.TR_CURRENT_TILE_WIDTH); + final int tHeight = renderer.getParam(TileRendererBase.TR_CURRENT_TILE_HEIGHT); + final int tY = renderer.getParam(TileRendererBase.TR_CURRENT_TILE_Y_POS); + final int tYOff = renderer.getParam(TileRenderer.TR_TILE_Y_OFFSET); + final int imgYOff = originBottomLeft ? 0 : renderer.getParam(TileRenderer.TR_TILE_HEIGHT) - tHeight; // imgYOff will be cut-off via sub-image + final int pX = renderer.getParam(TileRendererBase.TR_CURRENT_TILE_X_POS); // tileX == pX + final int pY = cis.getHeight() - ( tY - tYOff + tHeight ) + scaledYOffset; + + // Copy temporary data into raster of BufferedImage for faster + // blitting Note that we could avoid this copy in the cases + // where !offscreenDrawable.isGLOriented(), + // but that's the software rendering path which is very slow anyway. + final BufferedImage dstImage; + if( DEBUG_TILES ) { + final String fname = String.format("file_%03d_0_tile_[%02d][%02d]_sz_%03dx%03d_pos0_%03d_%03d_yOff_%03d_pos1_%03d_%03d.png", + _counter, + renderer.getParam(TileRenderer.TR_CURRENT_COLUMN), renderer.getParam(TileRenderer.TR_CURRENT_ROW), + tWidth, tHeight, + pX, tY, tYOff, pX, pY).replace(' ', '_'); + System.err.println("XXX file "+fname); + final File fout = new File(fname); + try { + ImageIO.write(tBuffer.image, "png", fout); + } catch (IOException e) { + e.printStackTrace(); + } + } + if( flipVertical ) { + final BufferedImage srcImage = tBuffer.image; + dstImage = vFlipImage; + final int[] src = ((DataBufferInt) srcImage.getRaster().getDataBuffer()).getData(); + final int[] dst = ((DataBufferInt) dstImage.getRaster().getDataBuffer()).getData(); + if( DEBUG_TILES ) { + Arrays.fill(dst, 0x55); + } + final int incr = tBuffer.width; + int srcPos = 0; + int destPos = (tHeight - 1) * tBuffer.width; + for (; destPos >= 0; srcPos += incr, destPos -= incr) { + System.arraycopy(src, srcPos, dst, destPos, incr); + } + } else { + dstImage = tBuffer.image; + } + if( DEBUG_TILES ) { + final String fname = String.format("file_%03d_1_tile_[%02d][%02d]_sz_%03dx%03d_pos0_%03d_%03d_yOff_%03d_pos1_%03d_%03d.png", + _counter, + renderer.getParam(TileRenderer.TR_CURRENT_COLUMN), renderer.getParam(TileRenderer.TR_CURRENT_ROW), + tWidth, tHeight, + pX, tY, tYOff, pX, pY).replace(' ', '_'); + System.err.println("XXX file "+fname); + final File fout = new File(fname); + try { + ImageIO.write(dstImage, "png", fout); + } catch (IOException e) { + e.printStackTrace(); + } + _counter++; + } + // Draw resulting image in one shot + final BufferedImage outImage = dstImage.getSubimage(0, imgYOff, tWidth, tHeight); + final boolean drawDone = g2d.drawImage(outImage, pX, pY, null); // Null ImageObserver since image data is ready. + if( verbose ) { + final Shape oClip = g2d.getClip(); + System.err.println("XXX tile-post.X tile 0 / "+imgYOff+" "+tWidth+"x"+tHeight+", clippedImgSize "+cis); + System.err.println("XXX tile-post.X pYf "+cis.getHeight()+" - ( "+tY+" - "+tYOff+" + "+tHeight+" ) "+scaledYOffset+" = "+ pY); + System.err.println("XXX tile-post.X clip "+oClip+" + "+pX+" / [pY "+tY+", pYOff "+tYOff+", pYf "+pY+"] -> "+g2d.getClip()); + g2d.setColor(Color.BLACK); + g2d.drawRect(pX, pY, tWidth, tHeight); + if( null != oClip ) { + final Rectangle r = oClip.getBounds(); + g2d.setColor(Color.YELLOW); + g2d.drawRect(r.x, r.y, r.width, r.height); + } + System.err.println("XXX tile-post.X "+renderer); + System.err.println("XXX tile-post.X dst-img "+dstImage.getWidth()+"x"+dstImage.getHeight()); + System.err.println("XXX tile-post.X out-img "+outImage.getWidth()+"x"+outImage.getHeight()); + System.err.println("XXX tile-post.X y-flip "+flipVertical+", originBottomLeft "+originBottomLeft+" -> "+pX+"/"+pY+", drawDone "+drawDone); + } + } + @Override + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} + }; +} |