
/*------------------------------------------------------------------------*
 * 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 CB class implements the colour bar equiluminance effect.
 *    It is meant to be run in a separate window using AppletStarter.
 *------------------------------------------------------------------------*/

import java.awt.*;

/*------------------------------  Class CB  ------------------------------*/

public class CB extends WindowApplet
{
  CBPanel     canvas;
  CBControls  controls;
  Button      dismiss;

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

    CB()
    {
      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 CBPanel();
        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 CBControls( 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 )
        {
            canvas.stopAnimation();
            controls.enableStart();
            hide();
            return true;
        }
        return false;
    }
}

/*---------------------------  Class CBPanel  ----------------------------*/

class CBPanel extends Panel implements Runnable
{
  // Public constants
  public final int  border       =   10;     // Minimum left/right border
  public final int  minPeriod    =  100;     // Milliseconds
  public final int  maxPeriod    = 1000;     // Milliseconds
  public final int  minBlockSize =    5;
  public final int  maxBlockSize =  100;
  public final int  defRed       =  255;
  public final int  defGreen     =  255;

  // Public parameters
  public       int     blockSize = 20;
  public       int     period    = 250;    // milliseconds
  public       double  L1        = 0.8;    // Luminance of first colour
  public       double  L2        = 0.2;    // Luminance of second colour

  // Off-screen animation buffer and frame number
  private int        frame = 0;
  private Image      offImage;
  private Dimension  offDimension;
  private Graphics   offGraphics = null;

  // Other private things
  private Color   gray     = new Color( 192, 192, 192 );
  private Color   red      = new Color( defRed, 0, 0 );
  private Color   green    = new Color( 0, defGreen, 0 );
  private Thread  animator = null;

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

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

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

    public synchronized void update( Graphics g )
    {
      Dimension  d = size();
      Color      c1, c2;
      int        i, x, dx, xBar, yBar, blocks;
      int        doubleBlockSize, halfBlockSize;    // Widths

        // Create image buffer for current frame if necessary
        if ( ( offGraphics == null ) ||
             ( d.width  != offDimension.width ) ||
             ( d.height != offDimension.height ) )
        {
            // Allocate the new image buffer
            offDimension = d;
            offImage = createImage( d.width, d.height );
            offGraphics = offImage.getGraphics();
        }

        // Draw a gray background
        offGraphics.setColor( gray );
        offGraphics.fillRect( 0, 0, d.width, d.height );

        // Get bar size and current image location
        blocks = ( d.width - ( 2 * border ) ) / blockSize;
        if ( ( blocks % 2 ) == 1 )
            blocks -= 1;
        xBar = ( d.width - ( blocks * blockSize ) ) / 2;
        yBar = ( d.height - blockSize ) / 2;

        // Determine colors and offset
        doubleBlockSize = blockSize << 1;
        halfBlockSize   = blockSize >> 1;
        switch ( frame )
        {
          case 0:
            c1 = Color.white;
            c2 = Color.black;
            dx = blockSize + 1;
            break;
          case 1:
            c1 = red;
            c2 = green;
            dx = halfBlockSize + 1;
            break;
          case 2:
            c1 = Color.black;
            c2 = Color.white;
            dx = blockSize + 1;
            break;
          case 3:
          default:
            c1 = green;
            c2 = red;
            dx = halfBlockSize + 1;
            break;
        }

        // Draw pattern
        offGraphics.setColor( c1 );
        offGraphics.fillRect( xBar, yBar, blocks * blockSize, blockSize );
        offGraphics.setColor( c2 );
        for ( i = 1, x = xBar + dx; i < blocks; i += 2, x += doubleBlockSize )
            offGraphics.fillRect( x, yBar, blockSize, blockSize );

        // Copy the off-screen frame to the screen
        g.drawImage( offImage, 0, 0, this );
    }

    /*---------------------------  run  ----------------------------*/

    public void run()
    {
      long  lastTime = System.currentTimeMillis();
      long  t;

        while ( true )
        {
            t = ( lastTime + (long)period ) - System.currentTimeMillis();
            if ( t > (long)1 )
                try {
                    animator.sleep( (int)t );
                } catch ( InterruptedException e ) {
                    System.out.println(
                        "Interrupted exception in CBPanel.run" );
                }
            lastTime = System.currentTimeMillis();
            step();
        }
    }

    /*---------------------------  step  ---------------------------*/

    public void step()
    {
        // Update frame number and draw next frame
        frame = ( frame + 1 ) % 4;
        repaint();
    }

    /*----------------------  startAnimation  ----------------------*/

    public void startAnimation()
    {
        try {
            animator = new Thread( this );
            animator.start();
        } catch ( IllegalThreadStateException e ) {
            System.out.println( "Thread state exception in start" );
        }
    }

    /*----------------------  stopAnimation  -----------------------*/

    public void stopAnimation()
    {
        if ( animator != null )
        {
            animator.stop();
            animator = null;
        }
    }

    /*--------------------------  setRed  --------------------------*/

    public void setRed( int r )
    {
        red = new Color( r, 0, 0 );
        update( getGraphics() );
    }

    /*-------------------------  setGreen  -------------------------*/

    public void setGreen( int g )
    {
        green = new Color( 0, g, 0 );
        update( getGraphics() );
    }
}

/*--------------------------  Class CBControls  --------------------------*/

class CBControls extends Panel
{
  Button     start, stop, step;
  Panel      buttonPanel;
  Scrollbar  speedSlider, sizeSlider;
  Scrollbar  redSlider, greenSlider;
  Thread     animator = null;
  CBPanel    canvas;  // The main drawing canvas

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

    CBControls( CBPanel canvas )
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;

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

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

        // Create sliders
        speedSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL,
                canvas.maxPeriod + canvas.minPeriod - canvas.period,
                ( canvas.maxPeriod - canvas.minPeriod ) / 10,
                canvas.minPeriod, canvas.maxPeriod );
        sizeSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.blockSize,
                ( canvas.maxBlockSize - canvas.minBlockSize ) / 10,
                canvas.minBlockSize, canvas.maxBlockSize );
        redSlider =
            new Scrollbar( Scrollbar.HORIZONTAL,
                canvas.defRed, 25, 0, 255 );
        greenSlider =
            new Scrollbar( Scrollbar.HORIZONTAL,
                canvas.defGreen, 25, 0, 255 );

        // Create button panel with buttons
        buttonPanel = new Panel();
        start = new Button( "Start" );
        stop  = new Button( "Stop" );
        step  = new Button( "Step" );
        buttonPanel.add( start );
        buttonPanel.add( stop );
        buttonPanel.add( step );

        // Stop button is initially disabled
        stop.disable();

        // Add components to control panel
        addControl( (Component)(new Label( "Block size", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)sizeSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );
        addControl( (Component)(new Label( "Speed", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)speedSlider, 1.0,
            GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        addControl( (Component)(new Label( "Red luminance", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)redSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );
        addControl( (Component)(new Label( "Green luminance", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)greenSlider, 1.0,
            GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        addControl( (Component)buttonPanel, 1.0,
            GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
    }

    /*-----------------------  addComponent  -----------------------*/

    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 ) );
    }

    /*-----------------------  enableStart  ------------------------*/

    public void enableStart()
    {
        start.enable();
        step.enable();
        stop.disable();
    }

    /*-----------------------  disableStart  -----------------------*/

    public void disableStart()
    {
        start.disable();
        step.disable();
        stop.enable();
    }

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

    public boolean action( Event event, Object obj )
    {
        if ( event.target == start )
        {
            disableStart();
            canvas.startAnimation();
            return true;
        }
        if ( event.target == stop )
        {
            enableStart();
            canvas.stopAnimation();
            return true;
        }
        if ( event.target == step )
        {
            canvas.step();
            return true;
        }
        return false;
    }

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

    public boolean handleEvent( Event event )
    {
        if ( event.target == speedSlider )
        {
            // simply adjust animation speed
            canvas.period =
                canvas.maxPeriod + canvas.minPeriod - speedSlider.getValue();
            return true;
        }
        else if ( event.target == sizeSlider )
        {
            // adjust block size and redraw
            canvas.blockSize = sizeSlider.getValue();
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == redSlider )
        {
            // update red colour
            canvas.setRed( redSlider.getValue() );
            return true;
        }
        else if ( event.target == greenSlider )
        {
            // update green colour
            canvas.setGreen( greenSlider.getValue() );
            return true;
        }
        else
            return super.handleEvent( event );
    }
}

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

