Mega Code Archive

 
Categories / Java / Advanced Graphics
 

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