/* * [Pacman.java] * * Summary: Draws Pacman on a transparent background eight ways. Teaching tool. * * Copyright: (c) 2011-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2011-01-09 initial version */ package com.mindprod.example; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.LineMetrics; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; /** * Draws Pacman on a transparent background eight ways. Teaching tool. * Various ways of overpainting with transparency. Though DROPCLOTH is best here, * others may be more suitable in other contexts. */ enum Technique { DROPCLOTH { void technique( final Graphics2D g2dOfCaller, final Shape body, final Shape mouth, final Shape eye ) { // B E S T // this dropcloth method works reliably for the screen and pngs. // We never overpaint the transparent mouth. // We protect it with a clip region "dropcloth" // avoid making permanent changes to clip region of caller final Graphics2D g2d = ( Graphics2D ) g2dOfCaller.create(); final Shape currentClip = g2d.getClip(); final Area dropCloth = currentClip == null ? new Area( new Rectangle( 0, 0, imageWidth, imageHeight ) ) : new Area( currentClip ); dropCloth.subtract( new Area( mouth ) ); g2d.clip( dropCloth ); // intersect drop cloth with any current clip region. g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }, BUFFEREDCOMPOSITE { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // works, though it is a bit clumsy, We draw to a BufferedImage, then paint the // BufferedImage. final Rectangle r = g2d.getClipBounds(); final BufferedImage bufferedImage = new BufferedImage( r.width, r.height, BufferedImage.TYPE_4BYTE_ABGR_PRE ); final Graphics2D g2d2 = bufferedImage.createGraphics(); g2d2.setColor( PACMAN_BODY_COLOR ); g2d2.fill( body ); g2d2.setColor( TRANSPARENT ); // replace the usual AlphaComposite.SrcOver painting rule Composite was = g2d2.getComposite(); g2d2.setComposite( AlphaComposite.Clear ); g2d2.fill( mouth ); g2d2.setComposite( was ); g2d2.setColor( EYE_COLOR ); g2d2.fill( eye ); // paint image in RAM buffer onto the screen buffer g2d.drawImage( bufferedImage, null, 0, 0 ); } }, COMPOSITE { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // only works with creating PNG images, not on screen. // You could make it work on screen by painting to a BufferedImage then painting that // to the screen. g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setColor( TRANSPARENT ); // replace the usual AlphaComposite.SrcOver painting rule Composite was = g2d.getComposite(); g2d.setComposite( AlphaComposite.Clear ); g2d.fill( mouth ); g2d.setComposite( was ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }, BUFFEREDCLEARRECT { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // only works with rectangles. // We draw to a BufferedImage, then paint the BufferedImage. final Rectangle r = g2d.getClipBounds(); final BufferedImage bufferedImage = new BufferedImage( r.width, r.height, BufferedImage.TYPE_4BYTE_ABGR_PRE ); final Graphics2D g2d2 = bufferedImage.createGraphics(); g2d2.setColor( PACMAN_BODY_COLOR ); g2d2.fill( body ); g2d2.setBackground( TRANSPARENT ); g2d2.setColor( TRANSPARENT ); final Rectangle rm = mouth.getBounds(); g2d2.clearRect( rm.x, rm.y, rm.width, rm.height ); g2d2.setColor( EYE_COLOR ); g2d2.fill( eye ); // paint image in RAM buffer onto the screen buffer g2d.drawImage( bufferedImage, null, 0, 0 ); } }, CLEARRECT { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // only works with rectangles and creating PNG images, not onscreen. // You could make it work on screen by painting to a BufferedImage then painting that // to the screen. g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setBackground( TRANSPARENT ); g2d.setColor( TRANSPARENT ); final Rectangle r = mouth.getBounds(); g2d.clearRect( r.x, r.y, r.width, r.height ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }, COPYAREA { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // only works with rectangles and creating PNG images. // It requires a source of undisturbed transparent pixels. // It will not work if there is a Transform. // You could make it work on screen by painting to a BufferedImage then painting that // to the screen. g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setBackground( TRANSPARENT ); final Rectangle r = mouth.getBounds(); // copy some transparent pixels from the upper left corner to the mouth area g2d.copyArea( 0, 0, r.width, r.height, r.x, r.y ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }, FILL { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // U S E L E S S // has no effect. You can't remove an opaque colour by overpainting // with a transparent one. g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setColor( TRANSPARENT ); g2d.fill( mouth ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }, OPAQUE { void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ) { // No transparency. g2d.setColor( Color.BLACK ); g2d.fill( g2d.getClipBounds() ); g2d.setColor( PACMAN_BODY_COLOR ); g2d.fill( body ); g2d.setColor( Color.BLACK ); g2d.fill( mouth ); g2d.setColor( EYE_COLOR ); g2d.fill( eye ); } }; /** * colour of Pacman's eye */ private static final Color EYE_COLOR = new Color( 0x335edd ); /** * colour of Pacman's body, orangy yellow */ private static final Color PACMAN_BODY_COLOR = new Color( 0xffd700 ); /** * default transparent background colour for images. */ private static final Color TRANSPARENT = new Color( 0x00ffffff, true ); private static int imageHeight; private static int imageWidth; /** * set the size of the images drawn by Technique */ static void setSize( Dimension d ) { Technique.imageWidth = d.width; Technique.imageHeight = d.height; } /** * Draw a yellow Pacman with a transparent mouth and background and blue eye * * @param g2d graphics context * @param body outline of Paman's body * @param mouth outline of Pacman's mouth * @param eye outline of Pacman's eye */ abstract void technique( final Graphics2D g2d, final Shape body, final Shape mouth, final Shape eye ); } /** * Draws Pacman on a transparent background eight ways. Teaching tool. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2011-01-09 initial version * @since 2011-01-09 */ public class Pacman { /** * color to display the connection against */ private static final Color MATTE_COLOUR = new Color( 0xe0e0e0 ); /** * Prepares a set of png files for various connectors. * * @param args not used */ public static void main( String[] args ) { // display on screen JFrame f = new JFrame(); f.setSize( 760, 470 ); Container contentPane = f.getContentPane(); contentPane.setLayout( new FlowLayout() ); contentPane.setBackground( MATTE_COLOUR ); for ( Technique t : Technique.values() ) { contentPane.add( new PacmanPanel( t ) ); } f.validate(); f.setVisible( true ); } } /** * Draw a Pacman on a transparent background */ class PacmanPanel extends JPanel { /** * radius of the Pacman */ private static final int radius = 75; /** * transparent background colour . */ private static final Color TRANSPARENT = new Color( 0x00ffffff, true ); /** * font used to label the Pacmen */ private static final Font USUAL_FONT = new Font( "Dialog", Font.PLAIN, 18 ); /** * which technique to use for transparent overpainting. */ private final Technique technique; /** * Constructor * * @param technique which technique to use for transparent painting */ PacmanPanel( Technique technique ) { this.technique = technique; setBackground( TRANSPARENT ); setOpaque( false ); final Dimension d = new Dimension( radius * 2 + 20, radius * 2 + 40 ); Technique.setSize( d ); this.setMaximumSize( d ); this.setMinimumSize( d ); this.setPreferredSize( d ); } /** * Draw string centered on x,y in both x and y directions * * @param g2d the graphics context * @param x location of center of string. * @param y location of center of string. * @param text string to display */ private static void drawCenteredString( final Graphics2D g2d, final String text, final int x, final int y ) { final Font font = g2d.getFont(); int width = g2d.getFontMetrics().stringWidth( text ); final FontRenderContext fr = g2d.getFontRenderContext(); final LineMetrics lm = font.getLineMetrics( text, fr ); final int height = ( int ) ( lm.getAscent() * .76 + .5 )/* compensate for overstating */; // x,y is bottom left corner of text g2d.drawString( text, x - width / 2, y + height / 2 ); } /** * draw a Pacman * * @param g where to paint */ public void paintComponent( Graphics g ) { super.paintComponent( g ); Graphics2D g2d = ( Graphics2D ) g; g2d.setFont( USUAL_FONT ); g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); g2d.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ); // smooth geometric shapes too g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); g2d.setBackground( TRANSPARENT ); final Shape body = new Ellipse2D.Double( 0, 0, radius * 2, radius * 2 ); final Shape mouth = new Polygon( new int[] { radius, radius * 2, radius * 2 }, new int[] { radius, radius - 20, radius + 20 }, 3 ); final Shape eye = new Ellipse2D.Double( radius + 5, radius / 2 - 5, 25, 15 ); // select one of eight possible ways to render a Pacman. technique.technique( g2d, body, mouth, eye ); g2d.setColor( Color.RED ); drawCenteredString( g2d, technique.toString().toLowerCase(), radius, radius * 2 + 20 ); } }