
/*------------------------------------------------------------------------*
 * Originally implemented by Scott Flinn, in association with the
 * Imager Graphics Laboratory at the University of British Columbia
 * (scottflinn@alumni.uwaterloo.ca).
 *
 * This source code may be freely distributed and modified for any purpose
 * as long as these introductory comments are not removed.  Please be
 * aware that this represents the author's initial experiments with the
 * Java platform and should not necessarily be considered good examples
 * of Java programming.
 *------------------------------------------------------------------------*/

/*------------------------------------------------------------------------*
 *  The YG class implements the chromaticity contrast (yellow-gray)
 *    illusion.  It is meant to be run in a separate window using
 *    AppletStarter.
 *------------------------------------------------------------------------*/

import java.awt.*;

/*------------------------------  Class YG  ------------------------------*/

public class YG extends WindowApplet
{
  YGPanel     canvas;
  YGControls  controls;
  Button      dismiss;

    /*-----------------------  constructor  ------------------------*/

    YG()
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;

        // Create the layout manager and a constraint object
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create the canvas panel and put it on top
        canvas = new YGPanel();
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        gridbag.setConstraints( canvas, c );
        add( canvas );

        // Create the control panel and put it second from the bottom
        controls = new YGControls( canvas );
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weighty = 0.0;
        gridbag.setConstraints( controls, c );
        add( controls );

        // Create the dismiss button and put it at the bottom
        dismiss = new Button( "Dismiss" );
        gridbag.setConstraints( dismiss, c );
        add( dismiss );
        validate();
    }

    /*--------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == dismiss )
        {
            hide();
            return true;
        }
        return false;
    }
}

/*---------------------------  Class YGPanel  ----------------------------*/

class YGPanel extends Panel
{
  // Yellow range and initial value
  public final int  minYellow   =   0;
  public final int  maxYellow   = 255;
  public final int  startYellow = 204;  // 80%

  // Gray range and initial value
  public final int  minGray   =   0;
  public final int  maxGray   = 255;
  public final int  startGray = 102;   // 40%

  // Drawing colours -- modified through setYellow and setGray methods
  private Color  yellow;
  private Color  gray;
  private Color  mix;

  // Other drawing parameters -- can be modified directly
  public  final int      minXWidth =   2;
  public  final int      maxXWidth = 100;
  public        int      xWidth    =  20;
  private final double   border    = 0.1;   // Fraction of width
  public        boolean  connected = false;
  public        boolean  whiteTop  = false;

  // Off-screen drawing buffer
  private Dimension  offDimension;
  private Image      offImage;
  private Graphics   offGraphics = null;

    /*-----------------------  constructor  -----------------------*/

    YGPanel()
    {
      int  rg;  // red-green component of colour mix

        // Create colour objects
        rg = ( startYellow + startGray ) / 2;
        yellow = new Color( startYellow, startYellow, 0 );
        gray   = new Color( startGray, startGray, startGray );
        mix    = new Color( rg, rg, startGray / 2 );
    }

    /*--------------------------  paint  --------------------------*/

    public void paint( Graphics g )
    {
        update( g );
    }

    /*-------------------------  update  --------------------------*/

    public void update( Graphics g )
    {
      Dimension  d          = size();
      int        halfWidth  = d.width / 2;
      int        halfHeight = d.height / 2;
      int        height     = d.height - 1;
      double     theta;
      int        dx, dy, i;
      int[]      x = new int[4];
      int[]      y = new int[4];

        // Create off-screen image for double buffering
        if ( ( offGraphics == null ) ||
             ( d.width  != offDimension.width  ) ||
             ( d.height != offDimension.height ) )
        {
            offDimension = d;
            offImage = createImage( d.width, d.height );
            offGraphics = offImage.getGraphics();
        }

        if ( whiteTop )
        {
            dy = halfHeight;
            offGraphics.setColor( Color.white );
            offGraphics.fillRect( 0, 0, d.width - 1, dy );
        }
        else
            dy = 0;

        // Draw yellow background in left (possibly bottom) half
        offGraphics.setColor( yellow );
        offGraphics.fillRect( 0, dy, halfWidth, ( whiteTop ? dy : height ) );

        // Draw gray background in right (possibly bottom) half
        offGraphics.setColor( gray );
        offGraphics.fillRect( halfWidth,
            dy, halfWidth, ( whiteTop ? dy : height ) );

        // Draw X's in mixed colour
        offGraphics.setColor( mix );
        y[0] = (int)( height * border );
        y[1] = y[0];
        y[2] = (int)( height * ( 1.0 - border ) );
        y[3] = y[2];
        for ( dx = 0; dx <= halfWidth; dx += halfWidth )
        {
            // Set x vertices of left major arm of X
            x[0] = dx + (int)( halfWidth * border );
            x[1] = x[0] + xWidth;
            x[2] = dx + (int)( halfWidth * ( 1.0 - border ) );
            x[3] = x[2] - xWidth;
            offGraphics.fillPolygon( x, y, 4 );

            // Reverse x vertices to get minor arm of X
            for ( i = 0; i < 4; i++ )
                x[i] = halfWidth - x[i] + ( dx << 1 );
            offGraphics.fillPolygon( x, y, 4 );
        }

        // Draw connecting bar if requested
        if ( connected )
        {
            // Calculate actual thickness of lines
            theta = Math.atan2(
                halfWidth * ( 1.0 - border * 2.0 ),
                (double)( y[2] - y[0] ) );
            dy = (int)( xWidth * Math.cos( theta ) );
            offGraphics.fillRect(
                (int)( halfWidth * ( 1.0 - border ) ) - xWidth,
                y[0], ( (int)( halfWidth * border ) + xWidth ) * 2, dy );
        }

        // Finally, copy the off-screen image to the screen
        g.drawImage( offImage, 0, 0, this );
    }

    /*------------------------  setYellow  ------------------------*/

    public void setYellow( int y )
    {
      int  g, m;

        g = gray.getRed();
        m = ( y + g ) / 2;
        yellow = new Color( y, y, 0 );
        mix    = new Color( m, m, g / 2 );
        update( getGraphics() );
    }

    /*-------------------------  setGray  -------------------------*/

    public void setGray( int g )
    {
      int  y, m;

        y = yellow.getRed();
        m = ( y + g ) / 2;
        gray = new Color( g, g, g );
        mix  = new Color( m, m, g / 2 );
        update( getGraphics() );
    }

    /*----------------------  setConnected  -----------------------*/

    public void setConnected( boolean connected )
    {
        this.connected = connected;
        update( getGraphics() );
    }

    /*-----------------------  setWhiteTop  -----------------------*/

    public void setWhiteTop( boolean whiteTop )
    {
        this.whiteTop = whiteTop;
        update( getGraphics() );
    }
}

/*--------------------------  Class YGControls  --------------------------*/

class YGControls extends Panel
{
  Scrollbar  ySlider;        // Controls yellow intensity
  Scrollbar  gSlider;        // Controls gray intensity
  Scrollbar  tSlider;        // Controls thickness of X's
  Checkbox   whiteTop;       // Toggles white background in top half
  Checkbox   connected;      // Toggles connecting bar between X's
  YGPanel    canvas;         // The main drawing canvas

    /*-----------------------  constructor  -----------------------*/

    YGControls( YGPanel canvas )
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;
      Panel               boxPanel;

        // Record id of drawing canvas
        this.canvas = canvas;

        // Use a GridBag to lay things out
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create sliders and check boxes
        ySlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.startYellow,
                ( canvas.maxYellow - canvas.minYellow ) / 10,
                canvas.minYellow, canvas.maxYellow );
        gSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.startGray,
                ( canvas.maxGray - canvas.minGray ) / 10,
                canvas.minGray, canvas.maxGray );
        tSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.xWidth,
                ( canvas.maxXWidth - canvas.minXWidth ) / 10,
                canvas.minXWidth, canvas.maxXWidth );

        whiteTop  = new Checkbox( "White top" );
        connected = new Checkbox( "Connected" );

        // Add check boxes in double column using a container panel
        boxPanel = new Panel();
        boxPanel.add( whiteTop );
        boxPanel.add( connected );
        addControl( (Component)boxPanel, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        // Add labels and sliders
        addControl( (Component)(new Label( "Yellow", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)ySlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
        addControl( (Component)(new Label( "Gray", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)gSlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
        addControl( (Component)(new Label( "Thickness", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)tSlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
    }

    /*-----------------------  addControl  ------------------------*/

    private void
    addControl( Component component, double wx, int fill, int gw,
        GridBagLayout gb, GridBagConstraints c )
    {
        c.weightx   = (float)wx;
        c.weighty   = 1.0;
        c.fill      = fill;
        c.gridwidth = gw;
        gb.setConstraints( component, c );
        add( component );
    }

    /*-------------------------  insets  --------------------------*/

    public Insets insets()
    {
        return( new Insets( 5, 5, 5, 5 ) );
    }

    /*-------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == connected )
        {
            // Toggle connected flag of canvas
            canvas.setConnected( connected.getState() );
            return true;
        }
        if ( event.target == whiteTop )
        {
            // Toggle whiteTop flag of canvas
            canvas.setWhiteTop( whiteTop.getState() );
            return true;
        }
        return false;
    }

    /*-----------------------  handleEvent  -----------------------*/

    public boolean handleEvent( Event event )
    {
        if ( event.target == ySlider )
        {
            // adjust yellow and redraw
            canvas.setYellow( ySlider.getValue() );
            return true;
        }
        else if ( event.target == gSlider )
        {
            // adjust gray and redraw
            canvas.setGray( gSlider.getValue() );
            return true;
        }
        else if ( event.target == tSlider )
        {
            // adjust X thickness and redraw
            canvas.xWidth = tSlider.getValue();
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else
            return super.handleEvent( event );
    }    
}

/*------------------------------------------------------------------------*/

