/*
 * [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 );
        }
    }