/** * Copyright (c) 2006, 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. */ package com.sun.animation; import com.sun.animation.timing.Cycle; import com.sun.animation.timing.Envelope; import com.sun.animation.timing.TimingController; import com.sun.animation.timing.TimingTarget; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.net.URL; import java.text.*; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.border.*; import java.io.File; /** * * SmoothAnimation * * This application demonstrates various facets of what makes an animation smooth * (or not). There are various keyboard commands that toggle behaviors, as * seen in the keyPressed() method below. * * @author Chet */ public class SmoothAnimation extends JApplet { static boolean vbLockingCapable = true; // JNI methods for controlling Vertical Retrace synchronization static native void initVBLocker(); static native void vbLockNative(); /** * Attempt to load and initialize the VBLocker native library. If this * fails, we will disable the vbLockingCapable flag to avoid future * errors in calling the vbLockNative method. */ static { try { System.loadLibrary("VBLocker"); initVBLocker(); } catch (Exception e) { // Problem loading native lib; disable capability vbLockingCapable = false; System.out.println("Exception initializing VBLocker: " + e); } catch (UnsatisfiedLinkError e) { // Problem loading native lib; disable capability vbLockingCapable = false; System.out.println("Link error initializing VBLocker: " + e); } } static void vbLock() { if (vbLockingCapable) { vbLockNative(); } } public void init() { AnimationComponent.addComponent(this); } /** * Create the window and start the animation. */ private static void createAndShowGUI() { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(500, 220); AnimationComponent.addComponent(f); f.setVisible(true); } /** * @param args the command line arguments */ public static void main(String[] args) { Runnable doCreateAndShowGUI = new Runnable() { public void run() { createAndShowGUI(); } }; SwingUtilities.invokeLater(doCreateAndShowGUI); } } class AnimationComponent extends JComponent implements TimingTarget, KeyListener { int imageW = 100; int imageH = 150; Image image; int fadeX = 50; int fadeY = 10; int moveMinX = 150; int moveMaxX = 350; int moveX = moveMinX; int moveY = fadeY; float opacity = 0.0f; boolean useImage = false; boolean useAA = false; boolean motionBlur = false; boolean alterColor = false; boolean waitForVB = false; boolean linear = true; int blurSize = 5; int prevMoveX[]; int prevMoveY[]; float trailOpacity[]; static TimingController timer; static final int TIMING_RESOLUTION = 100; /** Creates a new instance of AnimationComponent */ public AnimationComponent() { createAnimationImage(); } /** * Render the graphics that will make up the image used in each of the * animations. This method may be called again as we change the nature * of the graphics based on runtime toggles. */ void createAnimationImage() { GraphicsConfiguration gc = GraphicsEnvironment. getLocalGraphicsEnvironment(). getDefaultScreenDevice().getDefaultConfiguration(); image = gc.createCompatibleImage(imageW, imageH, Transparency.TRANSLUCENT); Graphics2D gImg = (Graphics2D)image.getGraphics(); if (useImage) { try { URL url = getClass().getClassLoader().getResource("duke.gif"); Image originalImage = ImageIO.read(url); gImg.drawImage(originalImage, 0, 0, imageW, imageH, null); gImg.dispose(); } catch (Exception e) { System.out.println("Problem creating Duke image: " + e); } } else { // use graphics Color graphicsColor; if (alterColor) { graphicsColor = new Color((int)(.75 * 255), (int)(.75 * 255), (int)(.75 * 255)); } else { graphicsColor = Color.black; } gImg.setColor(graphicsColor); gImg.fillRect(0, 0, imageW, imageH); if (useAA) { gImg.setComposite(AlphaComposite.Src); int channel = graphicsColor.getRed(); gImg.setColor(new Color(channel, channel, channel, 50)); gImg.drawRect(0, 0, imageW - 1, imageH - 1); gImg.setColor(new Color(channel, channel, channel, 100)); gImg.drawRect(1, 1, imageW - 3, imageH - 3); gImg.setColor(new Color(channel, channel, channel, 150)); gImg.drawRect(2, 2, imageW - 5, imageH - 5); gImg.setColor(new Color(channel, channel, channel, 200)); gImg.drawRect(3, 3, imageW - 7, imageH - 7); gImg.setColor(new Color(channel, channel, channel, 225)); gImg.drawRect(4, 4, imageW - 9, imageH - 9); } } } /** * Main rendering routine. We erase the background, draw the fading animation * with the appropriate translucency, and draw the moving animation in the * appropriate place. We also optionally handle the vertical retrace * artifact. */ public void paintComponent(Graphics g) { // Erase the background g.setColor(Color.white); g.fillRect(0, 0, getWidth(), getHeight()); // Draw the fading image Graphics2D gFade = (Graphics2D)g.create(); AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity); gFade.setComposite(newComposite); gFade.drawImage(image, fadeX, fadeY, null); gFade.dispose(); // Draw the moving image if (motionBlur) { 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 { for (int i = 0; i < blurSize; ++i) { if (prevMoveX[i] >= 0) { // Render each blur image with the appropriate // amount of translucency Graphics2D gTrail = (Graphics2D)g.create(); AlphaComposite trailComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .1f); gTrail.setComposite(trailComposite); gTrail.drawImage(image, prevMoveX[i], prevMoveY[i], null); } } } } 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; } if (waitForVB) { // Do this right before finishing, which is right before Swing's // copy of the back buffer to the screen SmoothAnimation.vbLock(); } } /** * TimingTarget method, called by TimingController for every step of * the animation. Here, we calculate the appropriate position and * opacity of the animations and force a repaint. */ public void timingEvent(long cycleElapsedTime, long totalElapsedTime, float fraction) { float animationFactor; if (linear) { animationFactor = fraction; } else { animationFactor = (float)Math.sin(fraction * (float)Math.PI/2); } animationFactor = Math.min(animationFactor, 1.0f); animationFactor = Math.max(animationFactor, 0.0f); opacity = animationFactor; int prevMoveX = moveX; moveX = moveMinX + (int)(.5f + animationFactor * (float)(moveMaxX - moveMinX)); repaint(); } /** * TimingTarget method; this implementation is stubbed out as we don't have * anything to do here. */ public void begin() {} /** * TimingTarget method; this implementation is stubbed out as we don't have * anything to do here. */ public void end() {} /** * Increment or decrement the timer resolution to slow down or speed up * the animation framerate. */ private void changeResolution(boolean faster) { Cycle oldCycle = timer.getCycle(); int newResolution = oldCycle.getResolution(); if (faster) { newResolution -= 5; } else { newResolution += 5; } newResolution = Math.max(newResolution, 0); newResolution = Math.min(newResolution, 500); Cycle newCycle = new Cycle(oldCycle.getDuration(), newResolution); timer.setCycle(newCycle); } /** * Toggles various rendering flags */ public void keyPressed(KeyEvent ke) { int keyCode = ke.getKeyCode(); if (keyCode == KeyEvent.VK_B) { motionBlur = !motionBlur; } else if (keyCode == KeyEvent.VK_A) { useAA = !useAA; createAnimationImage(); } else if (keyCode == KeyEvent.VK_C) { alterColor = !alterColor; createAnimationImage(); } else if (keyCode == KeyEvent.VK_I) { useImage = !useImage; createAnimationImage(); } else if (keyCode == KeyEvent.VK_UP) { changeResolution(true); } else if (keyCode == KeyEvent.VK_DOWN) { changeResolution(false); } else if (keyCode == KeyEvent.VK_V) { waitForVB = !waitForVB; } else if (keyCode == KeyEvent.VK_L) { linear = !linear;; } else if (keyCode >= KeyEvent.VK_1 && keyCode <= KeyEvent.VK_9) { blurSize = keyCode - KeyEvent.VK_0; prevMoveX = prevMoveY = null; } } public void keyReleased(KeyEvent ke) {} public void keyTyped(KeyEvent ke) {} /** * Create this Component, add it to the given container (which * might be a standalone window or an applet frame) and start * the animation. */ public static void addComponent(Container container) { AnimationComponent component = new AnimationComponent(); container.add(component); container.addKeyListener(component); // Start the animation timer = new TimingController( new Cycle(1000, 100), new Envelope(TimingController.INFINITE, TIMING_RESOLUTION, Envelope.RepeatBehavior.REVERSE, Envelope.EndBehavior.HOLD), component); timer.start(); } }