Smooth Moves : Animation « Advanced Graphics « Java






Smooth Moves

 
 
 
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

/*
 * SmoothMoves.java
 *
 * Created on May 2, 2007, 4:49 PM
 *
 * Copyright (c) 2007, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * 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.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 THE COPYRIGHT
 * OWNER 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.
 */

/**
 * 
 * @author Chet
 */
public class SmoothMoves extends JComponent implements ActionListener, KeyListener {

  /** image holds the graphics we render for each animating object */
  BufferedImage image = null;

  static int imageW = 100;

  static int imageH = 150;

  /** Location of fading animation */
  int fadeX = 50;

  int fadeY = 50;

  /** X values that moving animation will move between */
  static int moveMinX = 150;

  static int moveMaxX = 350;

  /** Current x/y location of moving animation */
  int moveX = moveMinX;

  int moveY = 50;

  /** Current opacity of fading animation */
  float opacity = 0.0f;

  /** Toggles for various demo options (key to toggle in parentheses) */
  boolean useImage = false; // (i) image instead of rectangle

  boolean useAA = false; // (a) anti-aliased edges (rectangle only)

  boolean motionBlur = false; // (b) ghost images behind moving animation

  boolean alterColor = false; // (c) light-gray instead of black rectangle

  boolean linear = true; // (l) linear vs. non-linear motion

  /** Used for motion blur rendering; holds information for ghost trail */
  int blurSize = 5;

  int prevMoveX[];

  int prevMoveY[];

  float trailOpacity[];

  /** Basic Timer animation info */
  final static int CYCLE_TIME = 2000; // One cycle takes 2 seconds

  int currentResolution = 50; // current Timer resolution

  Timer timer = null; // animation Timer

  long cycleStart; // track start time for each cycle

  /** Creates a new instance of SmoothAnimation */
  public SmoothMoves() {
    // createAnimationImage();
    cycleStart = System.nanoTime() / 1000000;
    startTimer(currentResolution);
  }

  /**
   * Create the image that will be animated. This image may be an actual image
   * (duke.gif), or some graphics (a variation on a black filled rectangle) that
   * are rendered into an image. The contents of this image are dependent upon
   * the runtime toggles that have been set when this method is called.
   */
  void createAnimationImage() {
    GraphicsConfiguration gc = getGraphicsConfiguration();
    image = gc.createCompatibleImage(imageW, imageH, Transparency.TRANSLUCENT);
    Graphics2D gImg = image.createGraphics();
    if (useImage) {
      try {
        URL url = getClass().getResource("duke.gif");
        Image originalImage = ImageIO.read(url);
        gImg.drawImage(originalImage, 0, 0, imageW, imageH, null);
      } catch (Exception e) {
      }
    } else {
      // use graphics
      Color graphicsColor;
      if (alterColor) {
        graphicsColor = Color.LIGHT_GRAY;
      } else {
        graphicsColor = Color.BLACK;
      }
      gImg.setColor(graphicsColor);
      gImg.fillRect(0, 0, imageW, imageH);
      if (useAA) {
        // Antialiasing hack - just draw a fading-out border around the
        // rectangle
        gImg.setComposite(AlphaComposite.Src);
        int red = graphicsColor.getRed();
        int green = graphicsColor.getRed();
        int blue = graphicsColor.getRed();
        gImg.setColor(new Color(red, green, blue, 50));
        gImg.drawRect(0, 0, imageW - 1, imageH - 1);
        gImg.setColor(new Color(red, green, blue, 100));
        gImg.drawRect(1, 1, imageW - 3, imageH - 3);
        gImg.setColor(new Color(red, green, blue, 150));
        gImg.drawRect(2, 2, imageW - 5, imageH - 5);
        gImg.setColor(new Color(red, green, blue, 200));
        gImg.drawRect(3, 3, imageW - 7, imageH - 7);
        gImg.setColor(new Color(red, green, blue, 225));
        gImg.drawRect(4, 4, imageW - 9, imageH - 9);
      }
    }
    gImg.dispose();
  }

  public void paintComponent(Graphics g) {
    if (image == null) {
      createAnimationImage();
    }

    // Erase the background
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, getWidth(), getHeight());

    // Draw the fading image
    Graphics2D gFade = (Graphics2D) g.create();
    gFade.setComposite(AlphaComposite.SrcOver.derive(opacity));
    gFade.drawImage(image, fadeX, fadeY, null);
    gFade.dispose();

    // Draw the moving image
    if (motionBlur) {
      // Draw previous locations of the image as a trail of
      // ghost images
      if (prevMoveX == null) {
        // blur location array not yet created; create it now
        prevMoveX = new int[blurSize];
        prevMoveY = new int[blurSize];
        trailOpacity = new float[blurSize];
        float incrementalFactor = .2f / (blurSize + 1);
        for (int i = 0; i < blurSize; ++i) {
          // default values, act as flag to not render these
          // until they have real values
          prevMoveX[i] = -1;
          prevMoveY[i] = -1;
          // vary the translucency by the number of the ghost
          // image; the further away it is from the current one,
          // the more faded it will be
          trailOpacity[i] = (.2f - incrementalFactor) - i * incrementalFactor;
        }
      } else {
        Graphics2D gTrail = (Graphics2D) g.create();
        for (int i = 0; i < blurSize; ++i) {
          if (prevMoveX[i] >= 0) {
            // Render each blur image with the appropriate
            // amount of translucency
            gTrail.setComposite(AlphaComposite.SrcOver.derive(trailOpacity[i]));
            gTrail.drawImage(image, prevMoveX[i], prevMoveY[i], null);
          }
        }
        gTrail.dispose();
      }
    }
    g.drawImage(image, moveX, moveY, null);
    if (motionBlur) {
      // shift the ghost positions to add the current position and
      // drop the oldest one
      for (int i = blurSize - 1; i > 0; --i) {
        prevMoveX[i] = prevMoveX[i - 1];
        prevMoveY[i] = prevMoveY[i - 1];
      }
      prevMoveX[0] = moveX;
      prevMoveY[0] = moveY;
    }
  }

  /**
   * This method handles the events from the Swing Timer
   */
  public void actionPerformed(ActionEvent ae) {
    // calculate the fraction elapsed of the animation and call animate()
    // to alter the values accordingly
    long currentTime = System.nanoTime() / 1000000;
    long totalTime = currentTime - cycleStart;
    if (totalTime > CYCLE_TIME) {
      cycleStart = currentTime;
    }
    float fraction = (float) totalTime / CYCLE_TIME;
    fraction = Math.min(1.0f, fraction);
    fraction = 1 - Math.abs(1 - (2 * fraction));
    animate(fraction);
  }

  /**
   * Animate the opacity and location factors, according to the current
   * fraction.
   */
  public void animate(float fraction) {
    float animationFactor;
    if (linear) {
      animationFactor = fraction;
    } else {
      // Our "nonlinear" motion just uses a sin function to get a
      // simple bounce behavior
      animationFactor = (float) Math.sin(fraction * (float) Math.PI / 2);
    }
    // Clamp the value to make sure it does not exceed the bounds
    animationFactor = Math.min(animationFactor, 1.0f);
    animationFactor = Math.max(animationFactor, 0.0f);
    // The opacity, used by the fading animation, will just use the
    // animation fraction directly
    opacity = animationFactor;
    // The move animation will calculate a location based on a linear
    // interpolation between its start and end points using the fraction
    moveX = moveMinX + (int) (.5f + animationFactor * (float) (moveMaxX - moveMinX));
    // redisplay our component with the new animated values
    repaint();
  }

  /**
   * Moves the frame rate up or down by changing the Timer resolution
   */
  private void changeResolution(boolean faster) {
    if (faster) {
      currentResolution -= 5;
    } else {
      currentResolution += 5;
    }
    currentResolution = Math.max(currentResolution, 0);
    currentResolution = Math.min(currentResolution, 500);
    startTimer(currentResolution);
  }

  /**
   * Starts the animation
   */
  private void startTimer(int resolution) {
    if (timer != null) {
      timer.stop();
      timer.setDelay(resolution);
    } else {
      timer = new Timer(resolution, this);
    }
    timer.start();
  }

  /**
   * Toggles various rendering flags
   */
  public void keyPressed(KeyEvent ke) {
    int keyCode = ke.getKeyCode();
    if (keyCode == KeyEvent.VK_B) {
      // B: Motion blur - displays trail of ghost images
      motionBlur = !motionBlur;
    } else if (keyCode == KeyEvent.VK_A) {
      // A: Antialiasing - Displays soft edges around graphics
      useAA = !useAA;
      createAnimationImage();
    } else if (keyCode == KeyEvent.VK_C) {
      // C: Color - Toggles rectangle color between dark and light colors
      alterColor = !alterColor;
      createAnimationImage();
    } else if (keyCode == KeyEvent.VK_I) {
      // I: Image - Toggles use of image or filled rectangle to show how
      // straight edges affect animation perception
      useImage = !useImage;
      createAnimationImage();
    } else if (keyCode == KeyEvent.VK_UP) {
      // Up Arrow: Speed - Speeds up frame rate
      changeResolution(true);
    } else if (keyCode == KeyEvent.VK_DOWN) {
      // Down Arrow: Speed - Slows down frame rate
      changeResolution(false);
    } else if (keyCode == KeyEvent.VK_L) {
      // L: Linearity: Toggles linear/nonlinear motion
      linear = !linear;
    } else if (keyCode >= KeyEvent.VK_1 && keyCode <= KeyEvent.VK_9) {
      // 0-9: Blur size: Toggles size of ghost trail for motion blur
      blurSize = keyCode - KeyEvent.VK_0;
      prevMoveX = prevMoveY = null;
    }
  }

  // Unused KeyListener implementations
  public void keyReleased(KeyEvent ke) {
  }

  public void keyTyped(KeyEvent ke) {
  }

  private static void createAndShowGUI() {
    JFrame f = new JFrame("Smooth Moves");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(moveMaxX + imageW + 50, 300);
    SmoothMoves component = new SmoothMoves();
    f.add(component);
    f.setVisible(true);
    f.addKeyListener(component);
  }

  public static void main(String[] args) {
    Runnable doCreateAndShowGUI = new Runnable() {
      public void run() {
        createAndShowGUI();
      }
    };
    SwingUtilities.invokeLater(doCreateAndShowGUI);
  }

}


 








Related examples in the same category

1.Animated Graphics
2.Simple animation techniqueSimple animation technique
3.Custom move interaction, Switching interactions, Update world extent geometry and Scroll handlingCustom move interaction, Switching interactions, Update world extent geometry and Scroll handling
4.Scroll HandlingScroll Handling
5.Curve AnimationCurve Animation
6.ClockClock
7.Moving Button
8.Moving Button Container
9.Background Animation