Java/Swing Components/Animation
Версия от 18:01, 31 мая 2010; (обсуждение)
Содержание
- 1 Animation By jdesktop animation
- 2 Animation Trigger
- 3 Animator Setup
- 4 Basic Race with Animation
- 5 Component Transition Animation
- 6 Discrete Interpolation
- 7 Fade In Button
- 8 Fade In Demo
- 9 Morphing Demo
- 10 Multi Step Race
- 11 NonLinear Race Demo
- 12 Pulse Animation
- 13 Pulse Animation Field
- 14 Setter Race Animation
- 15 Spline Interpolator Test
- 16 Trigger Race Animation
- 17 Tumble Item Project
Animation By jdesktop animation
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTarget;
import org.jdesktop.animation.timing.TimingTargetAdapter;
/*
* MyIntAnim.java
*
* Created on May 3, 2007, 2:24 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 MyIntAnim {
private int myInt;
public void setMyInt(int newValue) {
myInt = newValue;
System.out.println("newValue = " + newValue);
}
public MyIntAnim() {
// Set up the animation
TimingTarget myTarget = new TimingTargetAdapter() {
public void timingEvent(float fraction) {
setMyInt((int)(fraction * 10));
}
};
Animator anim = new Animator(1000, myTarget);
anim.start();
}
public static void main(String[] args) {
new MyIntAnim();
// Sleep so that animation timer starts before main thread dies
// and process quits
try {
Thread.sleep(2000);
} catch (Exception e) {}
}
}
Animation Trigger
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.triggers.ActionTrigger;
import org.jdesktop.animation.timing.triggers.FocusTrigger;
import org.jdesktop.animation.timing.triggers.FocusTriggerEvent;
import org.jdesktop.animation.timing.triggers.MouseTrigger;
import org.jdesktop.animation.timing.triggers.MouseTriggerEvent;
import org.jdesktop.animation.timing.triggers.TimingTrigger;
import org.jdesktop.animation.timing.triggers.TimingTriggerEvent;
/*
* Triggers.java
*
* Created on May 3, 2007, 1:24 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.
*/
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
*
* @author Chet
*/
public class Triggers extends JComponent {
SpherePanel armed, over, action, focus, timing;
static JButton triggerButton;
/** Creates a new instance of Triggers */
public Triggers() {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
action = new SpherePanel("yellow-sphere.png");
focus = new SpherePanel("blue-sphere.png");
armed = new SpherePanel("red-sphere.png");
over = new SpherePanel("green-sphere.png");
timing = new SpherePanel("gray-sphere.png");
add(action);
add(focus);
add(armed);
add(over);
add(timing);
// Add triggers for each sphere, depending on what we want to
// trigger them
ActionTrigger.addTrigger(triggerButton, action.getAnimator());
FocusTrigger.addTrigger(triggerButton,
focus.getAnimator(), FocusTriggerEvent.IN);
MouseTrigger.addTrigger(triggerButton,
armed.getAnimator(), MouseTriggerEvent.PRESS);
MouseTrigger.addTrigger(triggerButton,
over.getAnimator(), MouseTriggerEvent.ENTER);
TimingTrigger.addTrigger(action.getAnimator(),
timing.getAnimator(), TimingTriggerEvent.STOP);
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Triggers");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BorderLayout());
// Note: "Other Button" exists only to provide another component to
// move focus from/to, in order to show how FocusTrigger works
buttonPanel.add(new JButton("Other Button"), BorderLayout.NORTH);
triggerButton = new JButton("Trigger");
buttonPanel.add(triggerButton, BorderLayout.SOUTH);
f.add(buttonPanel, BorderLayout.NORTH);
f.add(new Triggers(), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/*
* SpherePanel.java
*
* Created on February 19, 2007, 9:09 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
/**
* This class encapsulates both the rendering of a sphere, at a location
* that may be animating, and the animation that drives the sphere
* movement.
*
* @author Chet
*/
class SpherePanel extends JPanel {
BufferedImage sphereImage = null;
private static final int PADDING = 5;
private static final int PANEL_HEIGHT = 300;
private int sphereX = PADDING, sphereY = 0;
Animator bouncer;
/**
* The animation changes the location of the sphere over time through
* this property setter. We force a repaint to display the sphere in
* its new location.
*/
public void setSphereY(int sphereY) {
this.sphereY = sphereY;
repaint();
}
/**
* Load the named image and create the animator that will bounce the
* image down and back up in this panel.
*/
SpherePanel(String filename) {
try {
URL url = getClass().getResource(filename);
sphereImage = ImageIO.read(url);
} catch (Exception e) {
System.out.println("Problem loading image " + filename + ": " + e);
return;
}
setPreferredSize(new Dimension(sphereImage.getWidth() + 2 * PADDING,
PANEL_HEIGHT));
bouncer = PropertySetter.createAnimator(2000, this, "sphereY",
0, (PANEL_HEIGHT - sphereImage.getHeight()), 0);
bouncer.setAcceleration(.5f);
bouncer.setDeceleration(.5f);
}
Animator getAnimator() {
return bouncer;
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(sphereImage, sphereX, sphereY, null);
}
}
Animator Setup
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/*
* MyIntAnimPS.java
*
* Created on May 3, 2007, 2:32 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.
*/
/**
* Compare this version to MyIntAnim; PropertySetter simplifies setting up
* and running animations.
*
* @author Chet
*/
public class MyIntAnimPS {
private int myInt;
public void setMyInt(int newValue) {
myInt = newValue;
System.out.println("newValue = " + newValue);
}
public MyIntAnimPS() {
// Set up the animation
Animator anim = PropertySetter.createAnimator(1000,
this, "myInt", 0, 10);
anim.start();
}
public static void main(String[] args) {
new MyIntAnimPS();
// Sleep so that animation timer starts before main thread dies
// and process quits
try {
Thread.sleep(2000);
} catch (Exception e) {}
}
}
Basic Race with Animation
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
/*
* BasicRace.java
*
* Created on May 3, 2007, 7:37 AM
*
* 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.
*/
/**
* The simplest version of the animation; set up a Animator to
* move the car from one position to another over a given time period.
*
*
* @author Chet
*/
public class BasicRace extends TimingTargetAdapter implements ActionListener {
public static final int RACE_TIME = 2000;
Point start = TrackView.START_POS;
Point end = TrackView.FIRST_TURN_START;
Point current = new Point();
protected Animator animator;
TrackView track;
RaceControlPanel controlPanel;
/** Creates a new instance of BasicRace */
public BasicRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
controlPanel = basicGUI.getControlPanel();
controlPanel.addListener(this);
track = basicGUI.getTrack();
animator = new Animator(RACE_TIME, this);
}
//
// Events
//
/**
* This receives the Go/Stop events that start/stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Go")) {
animator.stop();
animator.start();
} else if (ae.getActionCommand().equals("Stop")) {
animator.stop();
}
}
/**
* TimingTarget implementation: calculate and set the current
* car position based on the animation fraction
*/
public void timingEvent(float fraction) {
// Simple linear interpolation to find current position
current.x = (int)(start.x + (end.x - start.x) * fraction);
current.y = (int)(start.y + (end.y - start.y) * fraction);
// set the new position; this will force a repaint in TrackView
// and will display the car in the new position
track.setCarPosition(current);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
BasicRace race = new BasicRace("BasicRace");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* 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.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
/**
* 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.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* 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.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
Component Transition Animation
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.transitions.Effect;
import org.jdesktop.animation.transitions.EffectsManager;
import org.jdesktop.animation.transitions.ScreenTransition;
import org.jdesktop.animation.transitions.TransitionTarget;
import org.jdesktop.animation.transitions.effects.rupositeEffect;
import org.jdesktop.animation.transitions.effects.FadeIn;
/*
* SearchTransition.java
*
* Created on May 3, 2007, 3:05 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 SearchTransition extends JComponent implements TransitionTarget,
ActionListener {
//
// GUI components used in the application screens
//
JLabel instructions = new JLabel("Search and ye shall find...");
JLabel searchLabel = new JLabel("Search:");
JTextField searchField = new JTextField("");
JEditorPane results = new JEditorPane("text/html",
"<html><body><b>Dung Beetles</b>: An Ode<br/>" +
"My Life with <b>Dung Beetles</b><br/>" +
"<b>Beetle</b> Bailey Gets Latrine Duty<br/>" +
"Evolution"s Oddities<br/>" +
"Society"s Parasites<br/>" +
"You <b>Dung</b> Me Wrong: A Country Music History<br/>" +
"Ding, <b>Dung</b>, The Witch is Dead<br/>" +
""To be or not to <b>beetle</b>"<br/>" +
"Gross Insects of the World<br/>" +
"Nature"s Sanitation Engineers<br/>" +
"Why are they here?<br/>" +
"</body></html>");
JScrollPane scroller = new JScrollPane(results);
private static final int LABEL_W = 50;
private static final int LABEL_H = 20;
private static final int FIELD_W = 100;
private static final int FIELD_H = 20;
private static final int INSTRUCTIONS_W = 170;
private static final int INSTRUCTIONS_H = 20;
private static final int RESULTS_X = 30;
//
// Animation variables
//
Animator animator = new Animator(500); // Animate for half-second
// Setup transition with:
// "this" as the transition container
// "this" as the TransitionTarget callback object
// animator as the animator that drives the transition
ScreenTransition transition = new ScreenTransition(this,
this, animator);
private CompositeEffect moverFader = null;
//
// Misc other instance variables
//
private int currentScreen = 0; // Which screen are we on?
private int prevHeight = -1;
Paint bgGradient = null;
int prevW, prevH;
/** Creates a new instance of SearchTransition */
public SearchTransition() {
results.setEditable(false);
// Setup the animation parameters
animator.setAcceleration(.2f); // Accelerate for first 20%
animator.setDeceleration(.4f); // Decelerate for last 40%
// Set this as the listener for entries in the search field
searchField.addActionListener(this);
instructions.setFont(instructions.getFont().deriveFont(15f));
}
@Override
public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, y, w, h);
if (w != prevW || h != prevH) {
// Setup GUI for current screen given new size of our container
setupNextScreen();
prevW = w;
prevH = h;
}
}
/**
* Arrange the GUI for the initial search screen.
*/
private void setupSearchScreen() {
int instructionsX = (getWidth() - INSTRUCTIONS_W) / 2;
int instructionsY = getHeight() / 4;
int searchX = (getWidth() - LABEL_W - FIELD_W - 10) / 2;
int searchY = instructionsY + INSTRUCTIONS_H + 20;
int fieldX = searchX + LABEL_W + 10;
int fieldY = searchY;
add(instructions);
add(searchLabel);
add(searchField);
instructions.setBounds(instructionsX, instructionsY,
INSTRUCTIONS_W, INSTRUCTIONS_H);
searchLabel.setBounds(searchX, searchY, LABEL_W, LABEL_H);
searchField.setBounds(fieldX, fieldY, FIELD_W, FIELD_H);
}
/**
* Arrange the GUI for the results screen
*/
public void setupResultsScreen() {
int searchX = getWidth() - LABEL_W - FIELD_W - RESULTS_X - 10;
int searchY = 10;
int fieldX = searchX + LABEL_W + 10;
int fieldY = searchY;
int resultsX = RESULTS_X;
int resultsY = searchY + LABEL_H + 20;
add(searchLabel);
add(searchField);
add(scroller);
searchLabel.setBounds(searchX, 10, LABEL_W, LABEL_H);
searchField.setBounds(fieldX, fieldY, FIELD_W, FIELD_H);
scroller.setBounds(resultsX, resultsY,
getWidth() - (2 * resultsX), getHeight() - resultsY - 20);
}
/**
* Change the gradient and effect according to the new window size
*/
private void setupBackgroundAndEffect() {
// init the background gradient according to current height
bgGradient = new GradientPaint(0, 0, Color.LIGHT_GRAY.brighter(),
0, getHeight(), Color.DARK_GRAY.brighter());
// Init resultsEffect with current component size info
MoveIn mover = new MoveIn(RESULTS_X, getHeight());
FadeIn fader = new FadeIn();
moverFader = new CompositeEffect(mover);
moverFader.addEffect(fader);
EffectsManager.setEffect(scroller, moverFader, EffectsManager.TransitionType.APPEARING);
prevHeight = getHeight();
}
/**
* Override of paintComponent() to draw the gradient background
*/
@Override
protected void paintComponent(Graphics g) {
if (bgGradient == null || getHeight() != prevHeight) {
setupBackgroundAndEffect();
}
((Graphics2D)g).setPaint(bgGradient);
g.fillRect(0, 0, getWidth(), getHeight());
}
/**
* TransitionTarget callback; clear current state and set up
* state for next screen
*/
public void setupNextScreen() {
// Clear out current GUI state
removeAll();
switch (currentScreen) {
case 0:
setupSearchScreen();
break;
case 1:
setupResultsScreen();
break;
default:
break;
}
}
// Handle user hitting Enter in the search field
public void actionPerformed(ActionEvent ae) {
if (moverFader == null || prevHeight != getHeight()) {
setupBackgroundAndEffect();
}
// Change currentScreen, used later in setupNextScreen() callback
currentScreen = (currentScreen == 0) ? 1 : 0;
transition.start();
}
private static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400, 300);
SearchTransition component = new SearchTransition();
f.add(component);
f.setVisible(true);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* Custom effect: moves a component in to its end location
* from a specified starting point
*/
class MoveIn extends Effect {
private Point startLocation = new Point();
public MoveIn(int x, int y) {
startLocation.x = x;
startLocation.y = y;
}
/**
* Handles setup of animation that will vary the location during the
* transition
*/
@Override
public void init(Animator animator, Effect parentEffect) {
Effect targetEffect = (parentEffect == null) ? this : parentEffect;
PropertySetter ps;
ps = new PropertySetter(targetEffect, "location",
startLocation, new Point(getEnd().getX(), getEnd().getY()));
animator.addTarget(ps);
super.init(animator, parentEffect);
}
}
Discrete Interpolation
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.DiscreteInterpolator;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/*
* DiscreteInterpolation.java
*
* Created on May 3, 2007, 2:42 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 DiscreteInterpolation {
private int intValue;
/** Creates a new instance of DiscreteInterpolation */
public DiscreteInterpolation() {
}
public void setIntValue(int intValue) {
this.intValue = intValue;
System.out.println("intValue = " + intValue);
}
public static void main(String[] args) {
KeyValues keyValues = KeyValues.create(2, 6, 3, 5, 4);
KeyFrames keyFrames = new KeyFrames(keyValues,
DiscreteInterpolator.getInstance());
Animator anim = PropertySetter.createAnimator(1000,
new DiscreteInterpolation(), "intValue", keyFrames);
anim.start();
}
}
Fade In Button
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.Animator.Direction;
import org.jdesktop.animation.timing.Animator.RepeatBehavior;
import org.jdesktop.animation.timing.TimingTarget;
/*
* FadingButtonTF.java
*
* Created on May 3, 2007, 7:20 AM
*
* 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 FadingButtonTF extends JButton
implements ActionListener, TimingTarget {
float alpha = 1.0f; // current opacity of button
Animator animator; // for later start/stop actions
int animationDuration = 2000; // each cycle will take 2 seconds
BufferedImage buttonImage = null;
/** Creates a new instance of FadingButtonTF */
public FadingButtonTF(String label) {
super(label);
setOpaque(false);
animator = new Animator(animationDuration/2, Animator.INFINITE,
RepeatBehavior.REVERSE, this);
animator.setStartFraction(1.0f);
animator.setStartDirection(Direction.BACKWARD);
addActionListener(this);
}
public void paint(Graphics g) {
// Create an image for the button graphics if necessary
if (buttonImage == null || buttonImage.getWidth() != getWidth() ||
buttonImage.getHeight() != getHeight()) {
buttonImage = getGraphicsConfiguration().
createCompatibleImage(getWidth(), getHeight());
}
Graphics gButton = buttonImage.getGraphics();
gButton.setClip(g.getClip());
// Have the superclass render the button for us
super.paint(gButton);
// Make the graphics object sent to this paint() method translucent
Graphics2D g2d = (Graphics2D)g;
AlphaComposite newComposite =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(newComposite);
// Copy the button"s image to the destination graphics, translucently
g2d.drawImage(buttonImage, 0, 0, null);
}
/**
* This method receives click events, which start and stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (!animator.isRunning()) {
this.setText("Stop Animation");
animator.start();
} else {
animator.stop();
this.setText("Start Animation");
// reset alpha to opaque
alpha = 1.0f;
}
}
// Ununsed MouseListener implementations
public void begin() {}
public void end() {}
public void repeat() {}
/**
* TimingTarget implementation: this method sets the alpha of our button
* to be equal to the current elapsed fraction of the animation
*/
public void timingEvent(float fraction) {
alpha = fraction;
// redisplay our cbutton
repaint();
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Fading Button TF");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
JPanel checkerboard = new Checkerboard();
checkerboard.add(new FadingButtonTF("Start Animation"));
f.add(checkerboard);
f.setVisible(true);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
private static class Checkerboard extends JPanel {
private static final int DIVISIONS = 10;
static final int CHECKER_SIZE = 60;
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
for (int stripeX = 0; stripeX < getWidth(); stripeX += CHECKER_SIZE) {
for (int y = 0, row = 0; y < getHeight(); y += CHECKER_SIZE/2, ++row) {
int x = (row % 2 == 0) ? stripeX : (stripeX + CHECKER_SIZE/2);
g.fillRect(x, y, CHECKER_SIZE/2, CHECKER_SIZE/2);
}
}
}
}
}
Fade In Demo
/*
* Copyright (c) 2007, Romain Guy
* 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.
*/
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
*
* @author Romain Guy <romain.guy@mac.ru>
*/
public class FadingDemo extends JFrame {
private ImageViewer imageViewer;
private JButton nextButton;
private JButton previousButton;
private HelpGlassPane glass;
private JTextField titleField;
public FadingDemo() {
super("Fading Demo");
add(buildTitle(), BorderLayout.NORTH);
add(buildImageViewer(), BorderLayout.CENTER);
add(buildControls(), BorderLayout.SOUTH);
pack();
setupGlassPane();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
}
private JComponent buildTitle() {
titleField = new JTextField("Suzhou");
return titleField;
}
private void setupGlassPane() {
glass = new HelpGlassPane();
setGlassPane(glass);
glass.setVisible(true);
}
public static void setTextAndAnimate(final JTextComponent textComponent,
final String text) {
Color c = textComponent.getForeground();
KeyFrames keyFrames = new KeyFrames(KeyValues.create(
new Color(c.getRed(), c.getGreen(), c.getBlue(), 255),
new Color(c.getRed(), c.getGreen(), c.getBlue(), 0),
new Color(c.getRed(), c.getGreen(), c.getBlue(), 255)
));
PropertySetter setter = new PropertySetter(textComponent, "foreground",
keyFrames);
Animator animator = new Animator(200, setter);
animator.addTarget(new TimingTargetAdapter() {
private boolean textSet = false;
public void timingEvent(float fraction) {
if (fraction >= 0.5f && !textSet) {
textComponent.setText(text);
textSet = true;
}
}
});
animator.start();
}
private JComponent buildControls() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
panel.add(previousButton = new JButton("Previous"));
previousButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageViewer.previous();
setTextAndAnimate(titleField, "Suzhou");
}
});
panel.add(nextButton = new JButton("Next"));
nextButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageViewer.next();
setTextAndAnimate(titleField, "Shanghai");
if (glass.isVisible()) {
Animator animator = new Animator(200);
animator.addTarget(new PropertySetter(glass, "alpha", 0.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
}
});
return panel;
}
private JComponent buildImageViewer() {
return imageViewer = new ImageViewer();
}
public class HelpGlassPane extends JComponent {
private BufferedImage helpImage;
private float alpha = 1.0f;
private HelpGlassPane() {
try {
helpImage = GraphicsUtilities.loadCompatibleImage(
getClass().getResource("help.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Animator animator = new Animator(200);
animator.addTarget(new PropertySetter(
HelpGlassPane.this, "alpha", 0.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
});
}
public void setAlpha(float alpha) {
this.alpha = alpha;
if (alpha <= 0.01f) {
setVisible(false);
}
repaint();
}
public float getAlpha() {
return this.alpha;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
Point p = nextButton.getLocationOnScreen();
p.x += nextButton.getWidth() / 2 - 16;
p.y += nextButton.getHeight() / 2 - helpImage.getHeight() + 10;
SwingUtilities.convertPointFromScreen(p, this);
g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
g2.drawImage(helpImage, p.x, p.y, null);
}
}
public static class ImageViewer extends JComponent {
private BufferedImage firstImage;
private BufferedImage secondImage;
private float alpha = 0.0f;
private ImageViewer() {
try {
firstImage = GraphicsUtilities.loadCompatibleImage(
getClass().getResource("suzhou.jpg"));
secondImage = GraphicsUtilities.loadCompatibleImage(
getClass().getResource("shanghai.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(firstImage.getWidth(), firstImage.getHeight());
}
public void next() {
Animator animator = new Animator(1000);
animator.addTarget(new PropertySetter(this, "alpha", 1.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
public void previous() {
Animator animator = new Animator(1000);
animator.addTarget(new PropertySetter(this, "alpha", 0.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
public float getAlpha() {
return this.alpha;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(1.0f - alpha));
g2.drawImage(firstImage, 0, 0, null);
g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
g2.drawImage(secondImage, 0, 0, null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new FadingDemo().setVisible(true);
}
});
}
}
/**
* <p><code>GraphicsUtilities</code> contains a set of tools to perform
* common graphics operations easily. These operations are divided into
* several themes, listed below.</p>
* <h2>Compatible Images</h2>
* <p>Compatible images can, and should, be used to increase drawing
* performance. This class provides a number of methods to load compatible
* images directly from files or to convert existing images to compatibles
* images.</p>
* <h2>Creating Thumbnails</h2>
* <p>This class provides a number of methods to easily scale down images.
* Some of these methods offer a trade-off between speed and result quality and
* shouuld be used all the time. They also offer the advantage of producing
* compatible images, thus automatically resulting into better runtime
* performance.</p>
* <p>All these methodes are both faster than
* {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
* better-looking results than the various <code>drawImage()</code> methods
* in {@link java.awt.Graphics}, which can be used for image scaling.</p>
* <h2>Image Manipulation</h2>
* <p>This class provides two methods to get and set pixels in a buffered image.
* These methods try to avoid unmanaging the image in order to keep good
* performance.</p>
*
* @author Romain Guy <romain.guy@mac.ru>
*/
class GraphicsUtilities {
private GraphicsUtilities() {
}
// Returns the graphics configuration for the primary screen
private static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
}
/**
* <p>Returns a new <code>BufferedImage</code> using the same color model
* as the image passed as a parameter. The returned image is only compatible
* with the image passed as a parameter. This does not mean the returned
* image is compatible with the hardware.</p>
*
* @param image the reference image from which the color model of the new
* image is obtained
* @return a new <code>BufferedImage</code>, compatible with the color model
* of <code>image</code>
*/
public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
ColorModel cm = image.getColorModel();
return new BufferedImage(cm,
cm.createCompatibleWritableRaster(image.getWidth(),
image.getHeight()),
cm.isAlphaPremultiplied(), null);
}
/**
* <p>Returns a new compatible image with the same width, height and
* transparency as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(int, int)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param image the reference image from which the dimension and the
* transparency of the new image are obtained
* @return a new compatible <code>BufferedImage</code> with the same
* dimension and transparency as <code>image</code>
*/
public static BufferedImage createCompatibleImage(BufferedImage image) {
return createCompatibleImage(image, image.getWidth(), image.getHeight());
}
/**
* <p>Returns a new compatible image of the specified width and height, and
* the same transparency setting as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @param image the reference image from which the transparency of the new
* image is obtained
* @return a new compatible <code>BufferedImage</code> with the same
* transparency as <code>image</code> and the specified dimension
*/
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
image.getTransparency());
}
/**
* <p>Returns a new opaque compatible image of the specified width and
* height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new opaque compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleImage(int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height);
}
/**
* <p>Returns a new translucent compatible image of the specified width
* and height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleTranslucentImage(int width,
int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
/**
* <p>Returns a new compatible image from a URL. The image is loaded from the
* specified location and then turned, if necessary into a compatible
* image.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param resource the URL of the picture to load as a compatible image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
* @throws java.io.IOException if the image cannot be read or loaded
*/
public static BufferedImage loadCompatibleImage(URL resource)
throws IOException {
BufferedImage image = ImageIO.read(resource);
return toCompatibleImage(image);
}
/**
* <p>Return a new compatible image that contains a copy of the specified
* image. This method ensures an image is compatible with the hardware,
* and therefore optimized for fast blitting operations.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @param image the image to copy into a new compatible image
* @return a new compatible copy, with the
* same width and height and transparency and content, of <code>image</code>
*/
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(
getGraphicsConfiguration().getColorModel())) {
return image;
}
BufferedImage compatibleImage =
getGraphicsConfiguration().createCompatibleImage(
image.getWidth(), image.getHeight(),
image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newSize) {
float ratio;
int width = image.getWidth();
int height = image.getHeight();
if (width > height) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) width / (float) height;
width = newSize;
height = (int) (newSize / ratio);
} else {
if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) height / (float) width;
height = newSize;
width = (int) (newSize / ratio);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image</code> or if one of the dimensions
* is <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newWidth, int newHeight) {
if (newWidth >= image.getWidth() ||
newHeight >= image.getHeight()) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newSize) {
int width = image.getWidth();
int height = image.getHeight();
boolean isWidthGreater = width > height;
if (isWidthGreater) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
}
} else if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
}
if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
float ratioWH = (float) width / (float) height;
float ratioHW = (float) height / (float) width;
BufferedImage thumb = image;
do {
if (isWidthGreater) {
width /= 2;
if (width < newSize) {
width = newSize;
}
height = (int) (width / ratioWH);
} else {
height /= 2;
if (height < newSize) {
height = newSize;
}
width = (int) (height / ratioHW);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (newSize != (isWidthGreater ? width : height));
return thumb;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image or if one the dimensions is not > 0</code>
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newWidth, int newHeight) {
int width = image.getWidth();
int height = image.getHeight();
if (newWidth >= width || newHeight >= height) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage thumb = image;
do {
if (width > newWidth) {
width /= 2;
if (width < newWidth) {
width = newWidth;
}
}
if (height > newHeight) {
height /= 2;
if (height < newHeight) {
height = newHeight;
}
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (width != newWidth || height != newHeight);
return thumb;
}
/**
* <p>Returns an array of pixels, stored as integers, from a
* <code>BufferedImage</code>. The pixels are grabbed from a rectangular
* area defined by a location and two dimensions. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the source image
* @param x the x location at which to start grabbing pixels
* @param y the y location at which to start grabbing pixels
* @param w the width of the rectangle of pixels to grab
* @param h the height of the rectangle of pixels to grab
* @param pixels a pre-allocated array of pixels of size w*h; can be null
* @return <code>pixels</code> if non-null, a new array of integers
* otherwise
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static int[] getPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
Raster raster = img.getRaster();
return (int[]) raster.getDataElements(x, y, w, h, pixels);
}
// Unmanages the image
return img.getRGB(x, y, w, h, pixels, 0, w);
}
/**
* <p>Writes a rectangular area of pixels in the destination
* <code>BufferedImage</code>. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the destination image
* @param x the x location at which to start storing pixels
* @param y the y location at which to start storing pixels
* @param w the width of the rectangle of pixels to store
* @param h the height of the rectangle of pixels to store
* @param pixels an array of pixels, stored as integers
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static void setPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (pixels == null || w == 0 || h == 0) {
return;
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
WritableRaster raster = img.getRaster();
raster.setDataElements(x, y, w, h, pixels);
} else {
// Unmanages the image
img.setRGB(x, y, w, h, pixels, 0, w);
}
}
}
Morphing Demo
/*
* Copyright (c) 2007, Romain Guy
* 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.
*/
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.timing.triggers.MouseTrigger;
import org.jdesktop.animation.timing.triggers.MouseTriggerEvent;
/**
*
* @author Romain Guy <romain.guy@mac.ru>
*/
public class MorphingDemo extends JFrame {
private ImageViewer imageViewer;
public MorphingDemo() {
super("Morphing Demo");
add(buildImageViewer());
add(buildControls(), BorderLayout.SOUTH);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
private JComponent buildImageViewer() {
return imageViewer = new ImageViewer();
}
private JComponent buildControls() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
JButton button;
panel.add(button = new DirectionButton("Backward",
DirectionButton.Direction.LEFT));
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageViewer.previous();
}
});
panel.add(button = new DirectionButton("Forward",
DirectionButton.Direction.RIGHT));
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageViewer.next();
}
});
return panel;
}
public static class DirectionButton extends JButton {
public enum Direction {
LEFT,
RIGHT
};
private DirectionButton.Direction direction;
private Map desktopHints;
private float morphing = 0.0f;
private DirectionButton(String text, Direction direction) {
super(text);
this.direction = direction;
setupTriggers();
setFont(getFont().deriveFont(Font.BOLD));
setOpaque(false);
setBorderPainted(false);
setContentAreaFilled(false);
setFocusPainted(false);
}
private void setupTriggers() {
Animator animator = PropertySetter.createAnimator(
150, this, "morphing", 0.0f, 1.0f);
animator.setAcceleration(0.2f);
animator.setDeceleration(0.3f);
MouseTrigger.addTrigger(this, animator, MouseTriggerEvent.ENTER, true);
}
private Morphing2D createMorph() {
Shape sourceShape = new RoundRectangle2D.Double(2.0, 2.0,
getWidth() - 4.0, getHeight() - 4.0, 12.0, 12.0);
GeneralPath.Double destinationShape = new GeneralPath.Double();
destinationShape.moveTo(2.0, getHeight() / 2.0);
destinationShape.lineTo(22.0, 0.0);
destinationShape.lineTo(22.0, 5.0);
destinationShape.lineTo(getWidth() - 2.0, 5.0);
destinationShape.lineTo(getWidth() - 2.0, getHeight() - 5.0);
destinationShape.lineTo(22.0, getHeight() - 5.0);
destinationShape.lineTo(22.0, getHeight());
destinationShape.closePath();
return new Morphing2D(sourceShape, destinationShape);
}
public float getMorphing() {
return morphing;
}
public void setMorphing(float morphing) {
this.morphing = morphing;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (desktopHints == null) {
Toolkit tk = Toolkit.getDefaultToolkit();
desktopHints = (Map) (tk.getDesktopProperty("awt.font.desktophints"));
}
if (desktopHints != null) {
g2.addRenderingHints(desktopHints);
}
LinearGradientPaint p;
Color[] colors;
if (!getModel().isArmed()) {
colors = new Color[] {
new Color(0x63a5f7),
new Color(0x3799f4),
new Color(0x2d7eeb),
new Color(0x30a5f9) };
} else {
colors = new Color[] {
new Color(0x63a5f7).darker(),
new Color(0x3799f4).darker(),
new Color(0x2d7eeb).darker(),
new Color(0x30a5f9).darker() };
}
p = new LinearGradientPaint(0.0f, 0.0f, 0.0f, getHeight(),
new float[] { 0.0f, 0.5f, 0.501f, 1.0f },
colors);
g2.setPaint(p);
Morphing2D morph = createMorph();
morph.setMorphing(getMorphing());
if (direction == Direction.RIGHT) {
g2.translate(getWidth(), 0.0);
g2.scale(-1.0, 1.0);
}
g2.fill(morph);
if (direction == Direction.RIGHT) {
g2.scale(-1.0, 1.0);
g2.translate(-getWidth(), 0.0);
}
int width = g2.getFontMetrics().stringWidth(getText());
int x = (getWidth() - width) / 2;
int y = getHeight() / 2 + g2.getFontMetrics().getAscent() / 2 - 1;
g2.setColor(Color.BLACK);
g2.drawString(getText(), x, y + 1);
g2.setColor(Color.WHITE);
g2.drawString(getText(), x, y);
}
}
public static class ImageViewer extends JComponent {
private BufferedImage firstImage;
private BufferedImage secondImage;
private float alpha = 0.0f;
private ImageViewer() {
try {
firstImage = GraphicsUtilities.loadCompatibleImage(
getClass().getResource("suzhou.jpg"));
secondImage = GraphicsUtilities.loadCompatibleImage(
getClass().getResource("shanghai.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(firstImage.getWidth(), firstImage.getHeight());
}
public void next() {
Animator animator = new Animator(500);
animator.addTarget(new PropertySetter(this, "alpha", 1.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
public void previous() {
Animator animator = new Animator(500);
animator.addTarget(new PropertySetter(this, "alpha", 0.0f));
animator.setAcceleration(0.2f);
animator.setDeceleration(0.4f);
animator.start();
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
public float getAlpha() {
return this.alpha;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(1.0f - alpha));
g2.drawImage(firstImage, 0, 0, null);
g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
g2.drawImage(secondImage, 0, 0, null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MorphingDemo().setVisible(true);
}
});
}
}
/*
* $Id: Morphing2D.java,v 1.1 2007/01/26 17:35:35 gfx Exp $
*
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* Licensed under LGPL.
*/
/**
* <p>A morphing shape is a shape which geometry is constructed from two
* other shapes: a start shape and an end shape.</p>
* <p>The morphing property of a morphing shape defines the amount of
* transformation applied to the start shape to turn it into the end shape.</p>
* <p>Both shapes must have the same winding rule.</p>
*
* @author Jim Graham
* @author Romain Guy <romain.guy@mac.ru> (Maintainer)
*/
class Morphing2D implements Shape {
private double morph;
private Geometry startGeometry;
private Geometry endGeometry;
/**
* <p>Creates a new morphing shape. A morphing shape can be used to turn
* one shape into another one. The transformation can be controlled by the
* morph property.</p>
*
* @param startShape the shape to morph from
* @param endShape the shape to morph to
*
* @throws IllegalPathStateException if the shapes do not have the same
* winding rule
* @see #getMorphing()
* @see #setMorphing(double)
*/
public Morphing2D(Shape startShape, Shape endShape) {
startGeometry = new Geometry(startShape);
endGeometry = new Geometry(endShape);
if (startGeometry.getWindingRule() != endGeometry.getWindingRule()) {
throw new IllegalPathStateException("shapes must use same " +
"winding rule");
}
double tvals0[] = startGeometry.getTvals();
double tvals1[] = endGeometry.getTvals();
double masterTvals[] = mergeTvals(tvals0, tvals1);
startGeometry.setTvals(masterTvals);
endGeometry.setTvals(masterTvals);
}
/**
* <p>Returns the morphing value between the two shapes.</p>
*
* @return the morphing value between the two shapes
*
* @see #setMorphing(double)
*/
public double getMorphing() {
return morph;
}
/**
* <p>Sets the morphing value between the two shapes. This value controls
* the transformation from the start shape to the end shape. A value of 0.0
* is the start shap. A value of 1.0 is the end shape. A value of 0.5 is a
* new shape, morphed half way from the start shape to the end shape.</p>
* <p>The specified value should be between 0.0 and 1.0. If not, the value
* is clamped in the appropriate range.</p>
*
* @param morph the morphing value between the two shapes
*
* @see #getMorphing()
*/
public void setMorphing(double morph) {
if (morph > 1) {
morph = 1;
} else if (morph >= 0) {
// morphing is finite, not NaN, and in range
} else {
// morph is < 0 or NaN
morph = 0;
}
this.morph = morph;
}
private static double interp(double v0, double v1, double t) {
return (v0 + ((v1 - v0) * t));
}
private static double[] mergeTvals(double tvals0[], double tvals1[]) {
int i0 = 0;
int i1 = 0;
int numtvals = 0;
while (i0 < tvals0.length && i1 < tvals1.length) {
double t0 = tvals0[i0];
double t1 = tvals1[i1];
if (t0 <= t1) {
i0++;
}
if (t1 <= t0) {
i1++;
}
numtvals++;
}
double newtvals[] = new double[numtvals];
i0 = 0;
i1 = 0;
numtvals = 0;
while (i0 < tvals0.length && i1 < tvals1.length) {
double t0 = tvals0[i0];
double t1 = tvals1[i1];
if (t0 <= t1) {
newtvals[numtvals] = t0;
i0++;
}
if (t1 <= t0) {
newtvals[numtvals] = t1;
i1++;
}
numtvals++;
}
return newtvals;
}
/**
* @{inheritDoc}
*/
public Rectangle getBounds() {
return getBounds2D().getBounds();
}
/**
* @{inheritDoc}
*/
public Rectangle2D getBounds2D() {
int n = startGeometry.getNumCoords();
double xmin, ymin, xmax, ymax;
xmin = xmax = interp(startGeometry.getCoord(0), endGeometry.getCoord(0),
morph);
ymin = ymax = interp(startGeometry.getCoord(1), endGeometry.getCoord(1),
morph);
for (int i = 2; i < n; i += 2) {
double x = interp(startGeometry.getCoord(i),
endGeometry.getCoord(i), morph);
double y = interp(startGeometry.getCoord(i + 1),
endGeometry.getCoord(i + 1), morph);
if (xmin > x) {
xmin = x;
}
if (ymin > y) {
ymin = y;
}
if (xmax < x) {
xmax = x;
}
if (ymax < y) {
ymax = y;
}
}
return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
}
/**
* @{inheritDoc}
*/
public boolean contains(double x, double y) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
/**
* @{inheritDoc}
*/
public boolean intersects(double x, double y, double w, double h) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean intersects(Rectangle2D r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* @{inheritDoc}
*/
public boolean contains(double x, double y, double w, double h) {
throw new InternalError("unimplemented");
}
/**
* @{inheritDoc}
*/
public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* @{inheritDoc}
*/
public PathIterator getPathIterator(AffineTransform at) {
return new Iterator(at, startGeometry, endGeometry, morph);
}
/**
* @{inheritDoc}
*/
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
private static class Geometry {
static final double THIRD = (1.0 / 3.0);
static final double MIN_LEN = 0.001;
double bezierCoords[];
int numCoords;
int windingrule;
double myTvals[];
public Geometry(Shape s) {
// Multiple of 6 plus 2 more for initial moveto
bezierCoords = new double[20];
PathIterator pi = s.getPathIterator(null);
windingrule = pi.getWindingRule();
if (pi.isDone()) {
// We will have 1 segment and it will be all zeros
// It will have 8 coordinates (2 for moveto, 6 for cubic)
numCoords = 8;
}
double coords[] = new double[6];
int type = pi.currentSegment(coords);
pi.next();
if (type != PathIterator.SEG_MOVETO) {
throw new IllegalPathStateException("missing initial moveto");
}
double curx = bezierCoords[0] = coords[0];
double cury = bezierCoords[1] = coords[1];
double newx, newy;
numCoords = 2;
while (!pi.isDone()) {
if (numCoords + 6 > bezierCoords.length) {
// Keep array size to a multiple of 6 plus 2
int newsize = (numCoords - 2) * 2 + 2;
double newCoords[] = new double[newsize];
System.arraycopy(bezierCoords, 0, newCoords, 0, numCoords);
bezierCoords = newCoords;
}
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
throw new InternalError(
"Cannot handle multiple subpaths");
case PathIterator.SEG_CLOSE:
if (curx == bezierCoords[0] && cury == bezierCoords[1])
{
break;
}
coords[0] = bezierCoords[0];
coords[1] = bezierCoords[1];
/* NO BREAK */
case PathIterator.SEG_LINETO:
newx = coords[0];
newy = coords[1];
// A third of the way from curxy to newxy:
bezierCoords[numCoords++] = interp(curx, newx, THIRD);
bezierCoords[numCoords++] = interp(cury, newy, THIRD);
// A third of the way from newxy back to curxy:
bezierCoords[numCoords++] = interp(newx, curx, THIRD);
bezierCoords[numCoords++] = interp(newy, cury, THIRD);
bezierCoords[numCoords++] = curx = newx;
bezierCoords[numCoords++] = cury = newy;
break;
case PathIterator.SEG_QUADTO:
double ctrlx = coords[0];
double ctrly = coords[1];
newx = coords[2];
newy = coords[3];
// A third of the way from ctrlxy back to curxy:
bezierCoords[numCoords++] = interp(ctrlx, curx, THIRD);
bezierCoords[numCoords++] = interp(ctrly, cury, THIRD);
// A third of the way from ctrlxy to newxy:
bezierCoords[numCoords++] = interp(ctrlx, newx, THIRD);
bezierCoords[numCoords++] = interp(ctrly, newy, THIRD);
bezierCoords[numCoords++] = curx = newx;
bezierCoords[numCoords++] = cury = newy;
break;
case PathIterator.SEG_CUBICTO:
bezierCoords[numCoords++] = coords[0];
bezierCoords[numCoords++] = coords[1];
bezierCoords[numCoords++] = coords[2];
bezierCoords[numCoords++] = coords[3];
bezierCoords[numCoords++] = curx = coords[4];
bezierCoords[numCoords++] = cury = coords[5];
break;
}
pi.next();
}
// Add closing segment if either:
// - we only have initial moveto - expand it to an empty cubic
// - or we are not back to the starting point
if ((numCoords < 8) ||
curx != bezierCoords[0] ||
cury != bezierCoords[1]) {
newx = bezierCoords[0];
newy = bezierCoords[1];
// A third of the way from curxy to newxy:
bezierCoords[numCoords++] = interp(curx, newx, THIRD);
bezierCoords[numCoords++] = interp(cury, newy, THIRD);
// A third of the way from newxy back to curxy:
bezierCoords[numCoords++] = interp(newx, curx, THIRD);
bezierCoords[numCoords++] = interp(newy, cury, THIRD);
bezierCoords[numCoords++] = newx;
bezierCoords[numCoords++] = newy;
}
// Now find the segment endpoint with the smallest Y coordinate
int minPt = 0;
double minX = bezierCoords[0];
double minY = bezierCoords[1];
for (int ci = 6; ci < numCoords; ci += 6) {
double x = bezierCoords[ci];
double y = bezierCoords[ci + 1];
if (y < minY || (y == minY && x < minX)) {
minPt = ci;
minX = x;
minY = y;
}
}
// If the smallest Y coordinate is not the first coordinate,
// rotate the points so that it is...
if (minPt > 0) {
// Keep in mind that first 2 coords == last 2 coords
double newCoords[] = new double[numCoords];
// Copy all coordinates from minPt to the end of the
// array to the beginning of the new array
System.arraycopy(bezierCoords, minPt,
newCoords, 0,
numCoords - minPt);
// Now we do not want to copy 0,1 as they are duplicates
// of the last 2 coordinates which we just copied. So
// we start the source copy at index 2, but we still
// copy a full minPt coordinates which copies the two
// coordinates that were at minPt to the last two elements
// of the array, thus ensuring that thew new array starts
// and ends with the same pair of coordinates...
System.arraycopy(bezierCoords, 2,
newCoords, numCoords - minPt,
minPt);
bezierCoords = newCoords;
}
/* Clockwise enforcement:
* - This technique is based on the formula for calculating
* the area of a Polygon. The standard formula is:
* Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
* - The returned area is negative if the polygon is
* "mostly clockwise" and positive if the polygon is
* "mostly counter-clockwise".
* - One failure mode of the Area calculation is if the
* Polygon is self-intersecting. This is due to the
* fact that the areas on each side of the self-intersection
* are bounded by segments which have opposite winding
* direction. Thus, those areas will have opposite signs
* on the acccumulation of their area summations and end
* up canceling each other out partially.
* - This failure mode of the algorithm in determining the
* exact magnitude of the area is not actually a big problem
* for our needs here since we are only using the sign of
* the resulting area to figure out the overall winding
* direction of the path. If self-intersections cause
* different parts of the path to disagree as to the
* local winding direction, that is no matter as we just
* wait for the final answer to tell us which winding
* direction had greater representation. If the final
* result is zero then the path was equal parts clockwise
* and counter-clockwise and we do not care about which
* way we order it as either way will require half of the
* path to unwind and re-wind itself.
*/
double area = 0;
// Note that first and last points are the same so we
// do not need to process coords[0,1] against coords[n-2,n-1]
curx = bezierCoords[0];
cury = bezierCoords[1];
for (int i = 2; i < numCoords; i += 2) {
newx = bezierCoords[i];
newy = bezierCoords[i + 1];
area += curx * newy - newx * cury;
curx = newx;
cury = newy;
}
if (area < 0) {
/* The area is negative so the shape was clockwise
* in a Euclidean sense. But, our screen coordinate
* systems have the origin in the upper left so they
* are flipped. Thus, this path "looks" ccw on the
* screen so we are flipping it to "look" clockwise.
* Note that the first and last points are the same
* so we do not need to swap them.
* (Not that it matters whether the paths end up cw
* or ccw in the end as long as all of them are the
* same, but above we called this section "Clockwise
* Enforcement", so we do not want to be liars. ;-)
*/
// Note that [0,1] do not need to be swapped with [n-2,n-1]
// So first pair to swap is [2,3] and [n-4,n-3]
int i = 2;
int j = numCoords - 4;
while (i < j) {
curx = bezierCoords[i];
cury = bezierCoords[i + 1];
bezierCoords[i] = bezierCoords[j];
bezierCoords[i + 1] = bezierCoords[j + 1];
bezierCoords[j] = curx;
bezierCoords[j + 1] = cury;
i += 2;
j -= 2;
}
}
}
public int getWindingRule() {
return windingrule;
}
public int getNumCoords() {
return numCoords;
}
public double getCoord(int i) {
return bezierCoords[i];
}
public double[] getTvals() {
if (myTvals != null) {
return myTvals;
}
// assert(numCoords >= 8);
// assert(((numCoords - 2) % 6) == 0);
double tvals[] = new double[(numCoords - 2) / 6 + 1];
// First calculate total "length" of path
// Length of each segment is averaged between
// the length between the endpoints (a lower bound for a cubic)
// and the length of the control polygon (an upper bound)
double segx = bezierCoords[0];
double segy = bezierCoords[1];
double tlen = 0;
int ci = 2;
int ti = 0;
while (ci < numCoords) {
double prevx, prevy, newx, newy;
prevx = segx;
prevy = segy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
double len = Math.sqrt(prevx * prevx + prevy * prevy);
prevx = newx;
prevy = newy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
len += Math.sqrt(prevx * prevx + prevy * prevy);
prevx = newx;
prevy = newy;
newx = bezierCoords[ci++];
newy = bezierCoords[ci++];
prevx -= newx;
prevy -= newy;
len += Math.sqrt(prevx * prevx + prevy * prevy);
// len is now the total length of the control polygon
segx -= newx;
segy -= newy;
len += Math.sqrt(segx * segx + segy * segy);
// len is now sum of linear length and control polygon length
len /= 2;
// len is now average of the two lengths
/* If the result is zero length then we will have problems
* below trying to do the math and bookkeeping to split
* the segment or pair it against the segments in the
* other shape. Since these lengths are just estimates
* to map the segments of the two shapes onto corresponding
* segments of "approximately the same length", we will
* simply fudge the length of this segment to be at least
* a minimum value and it will simply grow from zero or
* near zero length to a non-trivial size as it morphs.
*/
if (len < MIN_LEN) {
len = MIN_LEN;
}
tlen += len;
tvals[ti++] = tlen;
segx = newx;
segy = newy;
}
// Now set tvals for each segment to its proportional
// part of the length
double prevt = tvals[0];
tvals[0] = 0;
for (ti = 1; ti < tvals.length - 1; ti++) {
double nextt = tvals[ti];
tvals[ti] = prevt / tlen;
prevt = nextt;
}
tvals[ti] = 1;
return (myTvals = tvals);
}
public void setTvals(double newTvals[]) {
double oldCoords[] = bezierCoords;
double newCoords[] = new double[2 + (newTvals.length - 1) * 6];
double oldTvals[] = getTvals();
int oldci = 0;
double x0, xc0, xc1, x1;
double y0, yc0, yc1, y1;
x0 = xc0 = xc1 = x1 = oldCoords[oldci++];
y0 = yc0 = yc1 = y1 = oldCoords[oldci++];
int newci = 0;
newCoords[newci++] = x0;
newCoords[newci++] = y0;
double t0 = 0;
double t1 = 0;
int oldti = 1;
int newti = 1;
while (newti < newTvals.length) {
if (t0 >= t1) {
x0 = x1;
y0 = y1;
xc0 = oldCoords[oldci++];
yc0 = oldCoords[oldci++];
xc1 = oldCoords[oldci++];
yc1 = oldCoords[oldci++];
x1 = oldCoords[oldci++];
y1 = oldCoords[oldci++];
t1 = oldTvals[oldti++];
}
double nt = newTvals[newti++];
// assert(nt > t0);
if (nt < t1) {
// Make nt proportional to [t0 => t1] range
double relt = (nt - t0) / (t1 - t0);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
xc0 = interp(xc0, xc1, relt);
yc0 = interp(yc0, yc1, relt);
xc1 = interp(xc1, x1, relt);
yc1 = interp(yc1, y1, relt);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
xc0 = interp(xc0, xc1, relt);
yc0 = interp(yc0, yc1, relt);
newCoords[newci++] = x0 = interp(x0, xc0, relt);
newCoords[newci++] = y0 = interp(y0, yc0, relt);
} else {
newCoords[newci++] = xc0;
newCoords[newci++] = yc0;
newCoords[newci++] = xc1;
newCoords[newci++] = yc1;
newCoords[newci++] = x1;
newCoords[newci++] = y1;
}
t0 = nt;
}
bezierCoords = newCoords;
numCoords = newCoords.length;
myTvals = newTvals;
}
}
private static class Iterator implements PathIterator {
AffineTransform at;
Geometry g0;
Geometry g1;
double t;
int cindex;
public Iterator(AffineTransform at,
Geometry g0, Geometry g1,
double t) {
this.at = at;
this.g0 = g0;
this.g1 = g1;
this.t = t;
}
/**
* @{inheritDoc}
*/
public int getWindingRule() {
return g0.getWindingRule();
}
/**
* @{inheritDoc}
*/
public boolean isDone() {
return (cindex > g0.getNumCoords());
}
/**
* @{inheritDoc}
*/
public void next() {
if (cindex == 0) {
cindex = 2;
} else {
cindex += 6;
}
}
double dcoords[];
/**
* @{inheritDoc}
*/
public int currentSegment(float[] coords) {
if (dcoords == null) {
dcoords = new double[6];
}
int type = currentSegment(dcoords);
if (type != SEG_CLOSE) {
coords[0] = (float) dcoords[0];
coords[1] = (float) dcoords[1];
if (type != SEG_MOVETO) {
coords[2] = (float) dcoords[2];
coords[3] = (float) dcoords[3];
coords[4] = (float) dcoords[4];
coords[5] = (float) dcoords[5];
}
}
return type;
}
/**
* @{inheritDoc}
*/
public int currentSegment(double[] coords) {
int type;
int n;
if (cindex == 0) {
type = SEG_MOVETO;
n = 2;
} else if (cindex >= g0.getNumCoords()) {
type = SEG_CLOSE;
n = 0;
} else {
type = SEG_CUBICTO;
n = 6;
}
if (n > 0) {
for (int i = 0; i < n; i++) {
coords[i] = interp(g0.getCoord(cindex + i),
g1.getCoord(cindex + i),
t);
}
if (at != null) {
at.transform(coords, 0, coords, 0, n / 2);
}
}
return type;
}
}
}
/**
* <p><code>GraphicsUtilities</code> contains a set of tools to perform
* common graphics operations easily. These operations are divided into
* several themes, listed below.</p>
* <h2>Compatible Images</h2>
* <p>Compatible images can, and should, be used to increase drawing
* performance. This class provides a number of methods to load compatible
* images directly from files or to convert existing images to compatibles
* images.</p>
* <h2>Creating Thumbnails</h2>
* <p>This class provides a number of methods to easily scale down images.
* Some of these methods offer a trade-off between speed and result quality and
* shouuld be used all the time. They also offer the advantage of producing
* compatible images, thus automatically resulting into better runtime
* performance.</p>
* <p>All these methodes are both faster than
* {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
* better-looking results than the various <code>drawImage()</code> methods
* in {@link java.awt.Graphics}, which can be used for image scaling.</p>
* <h2>Image Manipulation</h2>
* <p>This class provides two methods to get and set pixels in a buffered image.
* These methods try to avoid unmanaging the image in order to keep good
* performance.</p>
*
* @author Romain Guy <romain.guy@mac.ru>
*/
class GraphicsUtilities {
private GraphicsUtilities() {
}
// Returns the graphics configuration for the primary screen
private static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
}
/**
* <p>Returns a new <code>BufferedImage</code> using the same color model
* as the image passed as a parameter. The returned image is only compatible
* with the image passed as a parameter. This does not mean the returned
* image is compatible with the hardware.</p>
*
* @param image the reference image from which the color model of the new
* image is obtained
* @return a new <code>BufferedImage</code>, compatible with the color model
* of <code>image</code>
*/
public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
ColorModel cm = image.getColorModel();
return new BufferedImage(cm,
cm.createCompatibleWritableRaster(image.getWidth(),
image.getHeight()),
cm.isAlphaPremultiplied(), null);
}
/**
* <p>Returns a new compatible image with the same width, height and
* transparency as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(int, int)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param image the reference image from which the dimension and the
* transparency of the new image are obtained
* @return a new compatible <code>BufferedImage</code> with the same
* dimension and transparency as <code>image</code>
*/
public static BufferedImage createCompatibleImage(BufferedImage image) {
return createCompatibleImage(image, image.getWidth(), image.getHeight());
}
/**
* <p>Returns a new compatible image of the specified width and height, and
* the same transparency setting as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @param image the reference image from which the transparency of the new
* image is obtained
* @return a new compatible <code>BufferedImage</code> with the same
* transparency as <code>image</code> and the specified dimension
*/
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
image.getTransparency());
}
/**
* <p>Returns a new opaque compatible image of the specified width and
* height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new opaque compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleImage(int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height);
}
/**
* <p>Returns a new translucent compatible image of the specified width
* and height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleTranslucentImage(int width,
int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
/**
* <p>Returns a new compatible image from a URL. The image is loaded from the
* specified location and then turned, if necessary into a compatible
* image.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param resource the URL of the picture to load as a compatible image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
* @throws java.io.IOException if the image cannot be read or loaded
*/
public static BufferedImage loadCompatibleImage(URL resource)
throws IOException {
BufferedImage image = ImageIO.read(resource);
return toCompatibleImage(image);
}
/**
* <p>Return a new compatible image that contains a copy of the specified
* image. This method ensures an image is compatible with the hardware,
* and therefore optimized for fast blitting operations.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @param image the image to copy into a new compatible image
* @return a new compatible copy, with the
* same width and height and transparency and content, of <code>image</code>
*/
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(
getGraphicsConfiguration().getColorModel())) {
return image;
}
BufferedImage compatibleImage =
getGraphicsConfiguration().createCompatibleImage(
image.getWidth(), image.getHeight(),
image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newSize) {
float ratio;
int width = image.getWidth();
int height = image.getHeight();
if (width > height) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) width / (float) height;
width = newSize;
height = (int) (newSize / ratio);
} else {
if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) height / (float) width;
height = newSize;
width = (int) (newSize / ratio);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image</code> or if one of the dimensions
* is <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newWidth, int newHeight) {
if (newWidth >= image.getWidth() ||
newHeight >= image.getHeight()) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newSize) {
int width = image.getWidth();
int height = image.getHeight();
boolean isWidthGreater = width > height;
if (isWidthGreater) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
}
} else if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
}
if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
float ratioWH = (float) width / (float) height;
float ratioHW = (float) height / (float) width;
BufferedImage thumb = image;
do {
if (isWidthGreater) {
width /= 2;
if (width < newSize) {
width = newSize;
}
height = (int) (width / ratioWH);
} else {
height /= 2;
if (height < newSize) {
height = newSize;
}
width = (int) (height / ratioHW);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (newSize != (isWidthGreater ? width : height));
return thumb;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image or if one the dimensions is not > 0</code>
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newWidth, int newHeight) {
int width = image.getWidth();
int height = image.getHeight();
if (newWidth >= width || newHeight >= height) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage thumb = image;
do {
if (width > newWidth) {
width /= 2;
if (width < newWidth) {
width = newWidth;
}
}
if (height > newHeight) {
height /= 2;
if (height < newHeight) {
height = newHeight;
}
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (width != newWidth || height != newHeight);
return thumb;
}
/**
* <p>Returns an array of pixels, stored as integers, from a
* <code>BufferedImage</code>. The pixels are grabbed from a rectangular
* area defined by a location and two dimensions. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the source image
* @param x the x location at which to start grabbing pixels
* @param y the y location at which to start grabbing pixels
* @param w the width of the rectangle of pixels to grab
* @param h the height of the rectangle of pixels to grab
* @param pixels a pre-allocated array of pixels of size w*h; can be null
* @return <code>pixels</code> if non-null, a new array of integers
* otherwise
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static int[] getPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
Raster raster = img.getRaster();
return (int[]) raster.getDataElements(x, y, w, h, pixels);
}
// Unmanages the image
return img.getRGB(x, y, w, h, pixels, 0, w);
}
/**
* <p>Writes a rectangular area of pixels in the destination
* <code>BufferedImage</code>. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the destination image
* @param x the x location at which to start storing pixels
* @param y the y location at which to start storing pixels
* @param w the width of the rectangle of pixels to store
* @param h the height of the rectangle of pixels to store
* @param pixels an array of pixels, stored as integers
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static void setPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (pixels == null || w == 0 || h == 0) {
return;
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
WritableRaster raster = img.getRaster();
raster.setDataElements(x, y, w, h, pixels);
} else {
// Unmanages the image
img.setRGB(x, y, w, h, pixels, 0, w);
}
}
}
Multi Step Race
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.Animator.RepeatBehavior;
import org.jdesktop.animation.timing.interpolation.Interpolator;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyTimes;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.timing.interpolation.SplineInterpolator;
import org.jdesktop.animation.timing.triggers.ActionTrigger;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import java.applet.AudioClip;
import java.net.URL;
import org.jdesktop.animation.timing.TimingTarget;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
/*
* MultiStepRace.java
*
* Created on May 3, 2007, 2:45 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.
*/
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* The full-blown demo with all of the bells and whistles. This one uses
* the facilities shown in all of the other variations, but adds
* both multi-step and non-linear interpolation. It does this by
* creating a KeyFrames object to hold the times/values/splines
* used for each segment of the race. It also adds an animation for
* the rotation of the car (since the car should turn as it goes around the
* curves) and sound effects (just to go completely overboard).
*
* @author Chet
*/
public class MultiStepRace {
protected Animator animator;
private SoundEffects soundEffects;
public static final int RACE_TIME = 10000;
/** Creates a new instance of BasicRace */
public MultiStepRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
// We"re going to need a more involved PropertyRange object
// that has all curves of the track in it, as well as
// non-linear movement around the curves
Point values[] = {
TrackView.START_POS,
TrackView.FIRST_TURN_START, TrackView.FIRST_TURN_END,
TrackView.SECOND_TURN_START, TrackView.SECOND_TURN_END,
TrackView.THIRD_TURN_START, TrackView.THIRD_TURN_END,
TrackView.FOURTH_TURN_START,
TrackView.START_POS};
KeyValues keyValues = KeyValues.create(values);
// Calculate the keyTimes based on the distances that must be
// traveled on each leg of the journey
double totalDistance = 0;
double segmentDistance[] = new double[values.length];
for (int i = 0; i < (values.length - 1); ++i) {
segmentDistance[i] = values[i].distance(values[i + 1]);
totalDistance += segmentDistance[i];
}
segmentDistance[(values.length-1)] =
values[(values.length - 1)].distance(values[0]);
totalDistance += segmentDistance[(values.length-1)];
float times[] = new float[values.length];
float elapsedTime = 0.0f;
times[0] = 0.0f;
times[values.length - 1] = 1.0f;
for (int i = 0; i < (values.length - 2); ++i) {
times[i + 1] = elapsedTime + (float)(segmentDistance[i] / totalDistance);
elapsedTime = times[i + 1];
}
KeyTimes keyTimes = new KeyTimes(times);
// For realistic movement, we want a big acceleration
// on the straightaways
Interpolator initialSpline = new SplineInterpolator(1.00f, 0.00f, 0.2f, .2f);
Interpolator straightawaySpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator curveSpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator finalSpline = new SplineInterpolator(0.50f, 0.00f, .50f, 1.00f);
KeyFrames keyFrames = new KeyFrames(keyValues, keyTimes,
initialSpline, curveSpline, straightawaySpline, curveSpline,
straightawaySpline, curveSpline,
straightawaySpline, finalSpline);
// This PropertySetter enables the animation for the car movement all
// the way around the track
PropertySetter modifier = new PropertySetter(basicGUI.getTrack(),
"carPosition", keyFrames);
animator = new Animator(RACE_TIME, Animator.INFINITE,
RepeatBehavior.LOOP, modifier);
// Now create similar keyframes for rotation of car
keyValues = KeyValues.create(360, 315, 270, 225, 180, 135, 90, 45, 0);
Interpolator straightawayTurnSpline = new SplineInterpolator(1.0f, 0.0f, 1.0f, 0.0f);
Interpolator curveTurnSpline = new SplineInterpolator(0.0f, 0.5f, 0.5f, 1.0f);
keyFrames = new KeyFrames(keyValues, keyTimes,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline);
modifier = new PropertySetter(basicGUI.getTrack(), "carRotation",
keyFrames);
animator.addTarget(modifier);
// Finally, add sound effects, triggered by the same animator
soundEffects = new SoundEffects(keyFrames);
animator.addTarget(soundEffects);
// Instead of manually tracking the events, have the framework do
// the work by setting up a trigger
JButton goButton = basicGUI.getControlPanel().getGoButton();
JButton stopButton = basicGUI.getControlPanel().getStopButton();
ActionTrigger trigger = ActionTrigger.addTrigger(goButton, animator);
stopButton.addActionListener(new Stopper(animator));
}
/**
* Handle clicks on the Stop button to stop the race
*/
private class Stopper implements ActionListener {
Animator timer;
Stopper(Animator timer) {
this.timer = timer;
}
public void actionPerformed(ActionEvent ae) {
timer.stop();
}
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
MultiStepRace race = new MultiStepRace("Multi Step Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* 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.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
/**
* 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.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* 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.
*/
/**
* Simple utility class used to load and play sound effects for
* MultiStepRace.
*
* @author Chet
*/
class SoundEffects implements TimingTarget {
AudioClip drivingClip;
AudioClip turningClip;
KeyFrames keyFrames;
/** Creates a new instance of SoundEffects */
public SoundEffects(KeyFrames keyFrames) {
this.keyFrames = keyFrames;
try {
URL url = SoundEffects.class.getResource("vroom.wav");
drivingClip = java.applet.Applet.newAudioClip(url);
url = SoundEffects.class.getResource("drift.wav");
turningClip = java.applet.Applet.newAudioClip(url);
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
}
/**
* Plays the driving clip
*/
public void drive() {
if (drivingClip != null) {
drivingClip.loop();
}
}
/**
* Stops current clips
*/
public void stop() {
if (drivingClip != null) {
drivingClip.stop();
}
if (turningClip != null) {
turningClip.stop();
}
}
/**
* Plays the turning clip
*/
public void turn() {
if (turningClip != null) {
turningClip.play();
}
}
// TimingTarget implementation
boolean pastFirstTurn = false;
boolean pastSecondTurn = false;
boolean pastThirdTurn = false;
boolean pastFourthTurn = false;
public void begin() {
drive();
pastFirstTurn = false;
pastSecondTurn = false;
pastThirdTurn = false;
pastFourthTurn = false;
}
public void end() {
stop();
}
/**
* This method figures out when the car hits one of the turns
* and plays the turn clip appropriately
*/
public void timingEvent(float fraction) {
if (!pastFirstTurn) {
if (keyFrames.getInterval(fraction) == 1) {
turn();
pastFirstTurn = true;
}
} else if (!pastSecondTurn) {
if (keyFrames.getInterval(fraction) == 3) {
turn();
pastSecondTurn = true;
}
} else if (!pastThirdTurn) {
if (keyFrames.getInterval(fraction) == 5) {
turn();
pastThirdTurn = true;
}
} else if (!pastFourthTurn) {
if (keyFrames.getInterval(fraction) == 7) {
turn();
pastFourthTurn = true;
}
}
}
public void repeat() {
pastFirstTurn = false;
pastSecondTurn = false;
pastThirdTurn = false;
pastFourthTurn = false;
}
}
/**
* 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.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
NonLinear Race Demo
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
/*
* NonLinearRace.java
*
* Created on May 3, 2007, 7:37 AM
*
* 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.
*/
/**
* The simplest version of the animation; set up a Animator to
* move the car from one position to another over a given time period.
*
*
* @author Chet
*/
public class NonLinearRaceDemo extends TimingTargetAdapter implements ActionListener {
public static final int RACE_TIME = 2000;
Point start = TrackView.START_POS;
Point end = TrackView.FIRST_TURN_START;
Point current = new Point();
protected Animator animator;
TrackView track;
RaceControlPanel controlPanel;
/** Creates a new instance of NonLinearRaceDemo */
public NonLinearRaceDemo(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
controlPanel = basicGUI.getControlPanel();
controlPanel.addListener(this);
track = basicGUI.getTrack();
animator = new Animator(RACE_TIME, this);
}
//
// Events
//
/**
* This receives the Go/Stop events that start/stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Go")) {
animator.stop();
animator.start();
} else if (ae.getActionCommand().equals("Stop")) {
animator.stop();
}
}
/**
* TimingTarget implementation: calculate and set the current
* car position based on the animation fraction
*/
public void timingEvent(float fraction) {
// Simple linear interpolation to find current position
current.x = (int)(start.x + (end.x - start.x) * fraction);
current.y = (int)(start.y + (end.y - start.y) * fraction);
// set the new position; this will force a repaint in TrackView
// and will display the car in the new position
track.setCarPosition(current);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
NonLinearRaceDemo race = new NonLinearRaceDemo("NonLinearRaceDemo");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/*
* NonLinearRace.java
*
* Created on May 3, 2007, 7:49 AM
*
* 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.
*/
/**
* Simple subclass of SetterRace that merely sets acceleration/deceleration
* factors to give a non-linear motion effect to the car"s movement.
*
* @author Chet
*/
class NonLinearRace extends BasicRace {
/** Creates a new instance of NonLinearRace */
public NonLinearRace(String appName) {
super(appName);
// Accelerate for first half of duration, decelerate for final 10%
animator.setAcceleration(.5f);
animator.setDeceleration(.1f);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
NonLinearRace race = new NonLinearRace("Non-Linear Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/*
* BasicRace.java
*
* Created on May 3, 2007, 7:37 AM
*
* 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.
*/
/**
* The simplest version of the animation; set up a Animator to
* move the car from one position to another over a given time period.
*
*
* @author Chet
*/
class BasicRace extends TimingTargetAdapter implements ActionListener {
public static final int RACE_TIME = 2000;
Point start = TrackView.START_POS;
Point end = TrackView.FIRST_TURN_START;
Point current = new Point();
protected Animator animator;
TrackView track;
RaceControlPanel controlPanel;
/** Creates a new instance of BasicRace */
public BasicRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
controlPanel = basicGUI.getControlPanel();
controlPanel.addListener(this);
track = basicGUI.getTrack();
animator = new Animator(RACE_TIME, this);
}
//
// Events
//
/**
* This receives the Go/Stop events that start/stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Go")) {
animator.stop();
animator.start();
} else if (ae.getActionCommand().equals("Stop")) {
animator.stop();
}
}
/**
* TimingTarget implementation: calculate and set the current
* car position based on the animation fraction
*/
public void timingEvent(float fraction) {
// Simple linear interpolation to find current position
current.x = (int)(start.x + (end.x - start.x) * fraction);
current.y = (int)(start.y + (end.y - start.y) * fraction);
// set the new position; this will force a repaint in TrackView
// and will display the car in the new position
track.setCarPosition(current);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
BasicRace race = new BasicRace("BasicRace");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* 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.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
/**
* 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.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* 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.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
Pulse Animation
/*
* Copyright (c) 2007, Romain Guy
* 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.
*/
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
*
* @author Romain Guy <romain.guy@mac.ru>
*/
public class PulseDemo extends JFrame {
public PulseDemo() {
super("Pulse Demo");
setContentPane(buildBlackPanel());
add(buildPulsatingText());
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(320, 280);
setLocationRelativeTo(null);
}
private JComponent buildBlackPanel() {
return new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
Rectangle clip = g2.getClipBounds();
g2.setPaint(new GradientPaint(0.0f, 0.0f, new Color(0x666f7f).darker(),
0.0f, getHeight(), new Color(0x262d3d).darker()));
g2.fillRect(clip.x, clip.y, clip.width, clip.height);
}
};
}
private JComponent buildPulsatingText() {
return new PulsatingLogo("network-wireless.png");
}
public static class PulsatingLogo extends JComponent {
private BufferedImage image;
private BufferedImage glow;
private float alpha = 0.0f;
public PulsatingLogo(String imageName) {
try {
image = GraphicsUtilities.loadCompatibleImage(
getClass().getResource(imageName));
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2;
if (glow == null) {
glow = GraphicsUtilities.createCompatibleImage(image);
g2 = glow.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
BufferedImageOp filter = getGaussianBlurFilter(24, true);
glow = filter.filter(glow, null);
filter = getGaussianBlurFilter(24, false);
glow = filter.filter(glow, null);
filter = new ColorTintFilter(Color.WHITE, 1.0f);
glow = filter.filter(glow, null);
startAnimator();
}
int x = (getWidth() - image.getWidth()) / 2;
int y = (getHeight() - image.getHeight()) / 2;
g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(getAlpha()));
g2.drawImage(glow, x, y, null);
g2.setComposite(AlphaComposite.SrcOver);
g2.drawImage(image, x, y, null);
}
private void startAnimator() {
PropertySetter setter = new PropertySetter(this, "alpha", 0.0f, 1.0f);
Animator animator = new Animator(600, Animator.INFINITE,
Animator.RepeatBehavior.REVERSE, setter);
animator.start();
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
public static ConvolveOp getGaussianBlurFilter(int radius,
boolean horizontal) {
if (radius < 1) {
throw new IllegalArgumentException("Radius must be >= 1");
}
int size = radius * 2 + 1;
float[] data = new float[size];
float sigma = radius / 3.0f;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
for (int i = -radius; i <= radius; i++) {
float distance = i * i;
int index = i + radius;
data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
total += data[index];
}
for (int i = 0; i < data.length; i++) {
data[i] /= total;
}
Kernel kernel = null;
if (horizontal) {
kernel = new Kernel(size, 1, data);
} else {
kernel = new Kernel(1, size, data);
}
return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new PulseDemo().setVisible(true);
}
});
}
}
/**
* <p><code>GraphicsUtilities</code> contains a set of tools to perform
* common graphics operations easily. These operations are divided into
* several themes, listed below.</p>
* <h2>Compatible Images</h2>
* <p>Compatible images can, and should, be used to increase drawing
* performance. This class provides a number of methods to load compatible
* images directly from files or to convert existing images to compatibles
* images.</p>
* <h2>Creating Thumbnails</h2>
* <p>This class provides a number of methods to easily scale down images.
* Some of these methods offer a trade-off between speed and result quality and
* shouuld be used all the time. They also offer the advantage of producing
* compatible images, thus automatically resulting into better runtime
* performance.</p>
* <p>All these methodes are both faster than
* {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
* better-looking results than the various <code>drawImage()</code> methods
* in {@link java.awt.Graphics}, which can be used for image scaling.</p>
* <h2>Image Manipulation</h2>
* <p>This class provides two methods to get and set pixels in a buffered image.
* These methods try to avoid unmanaging the image in order to keep good
* performance.</p>
*
* @author Romain Guy <romain.guy@mac.ru>
*/
class GraphicsUtilities {
private GraphicsUtilities() {
}
// Returns the graphics configuration for the primary screen
private static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
}
/**
* <p>Returns a new <code>BufferedImage</code> using the same color model
* as the image passed as a parameter. The returned image is only compatible
* with the image passed as a parameter. This does not mean the returned
* image is compatible with the hardware.</p>
*
* @param image the reference image from which the color model of the new
* image is obtained
* @return a new <code>BufferedImage</code>, compatible with the color model
* of <code>image</code>
*/
public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
ColorModel cm = image.getColorModel();
return new BufferedImage(cm,
cm.createCompatibleWritableRaster(image.getWidth(),
image.getHeight()),
cm.isAlphaPremultiplied(), null);
}
/**
* <p>Returns a new compatible image with the same width, height and
* transparency as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(int, int)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param image the reference image from which the dimension and the
* transparency of the new image are obtained
* @return a new compatible <code>BufferedImage</code> with the same
* dimension and transparency as <code>image</code>
*/
public static BufferedImage createCompatibleImage(BufferedImage image) {
return createCompatibleImage(image, image.getWidth(), image.getHeight());
}
/**
* <p>Returns a new compatible image of the specified width and height, and
* the same transparency setting as the image specified as a parameter.</p>
*
* @see java.awt.Transparency
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @param image the reference image from which the transparency of the new
* image is obtained
* @return a new compatible <code>BufferedImage</code> with the same
* transparency as <code>image</code> and the specified dimension
*/
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
image.getTransparency());
}
/**
* <p>Returns a new opaque compatible image of the specified width and
* height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new opaque compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleImage(int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height);
}
/**
* <p>Returns a new translucent compatible image of the specified width
* and height.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param width the width of the new image
* @param height the height of the new image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
*/
public static BufferedImage createCompatibleTranslucentImage(int width,
int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
/**
* <p>Returns a new compatible image from a URL. The image is loaded from the
* specified location and then turned, if necessary into a compatible
* image.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #toCompatibleImage(java.awt.image.BufferedImage)
* @param resource the URL of the picture to load as a compatible image
* @return a new translucent compatible <code>BufferedImage</code> of the
* specified width and height
* @throws java.io.IOException if the image cannot be read or loaded
*/
public static BufferedImage loadCompatibleImage(URL resource)
throws IOException {
BufferedImage image = ImageIO.read(resource);
return toCompatibleImage(image);
}
/**
* <p>Return a new compatible image that contains a copy of the specified
* image. This method ensures an image is compatible with the hardware,
* and therefore optimized for fast blitting operations.</p>
*
* @see #createCompatibleImage(java.awt.image.BufferedImage)
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
* @see #createCompatibleImage(int, int)
* @see #createCompatibleTranslucentImage(int, int)
* @see #loadCompatibleImage(java.net.URL)
* @param image the image to copy into a new compatible image
* @return a new compatible copy, with the
* same width and height and transparency and content, of <code>image</code>
*/
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(
getGraphicsConfiguration().getColorModel())) {
return image;
}
BufferedImage compatibleImage =
getGraphicsConfiguration().createCompatibleImage(
image.getWidth(), image.getHeight(),
image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newSize) {
float ratio;
int width = image.getWidth();
int height = image.getHeight();
if (width > height) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) width / (float) height;
width = newSize;
height = (int) (newSize / ratio);
} else {
if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
ratio = (float) height / (float) width;
height = newSize;
width = (int) (newSize / ratio);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method favors speed over quality. When the new size is less than
* half the longest dimension of the source image,
* {@link #createThumbnail(BufferedImage, int)} or
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
* to ensure the quality of the result without sacrificing too much
* performance.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image</code> or if one of the dimensions
* is <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newWidth, int newHeight) {
if (newWidth >= image.getWidth() ||
newHeight >= image.getHeight()) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
* the length of the longest dimension of the thumbnail. The other
* dimension is then computed according to the dimensions ratio of the
* original picture.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
* @param image the source image
* @param newSize the length of the largest dimension of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newSize</code> is larger than
* the largest dimension of <code>image</code> or <= 0
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newSize) {
int width = image.getWidth();
int height = image.getHeight();
boolean isWidthGreater = width > height;
if (isWidthGreater) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image width");
}
} else if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than" +
" the image height");
}
if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" +
" be greater than 0");
}
float ratioWH = (float) width / (float) height;
float ratioHW = (float) height / (float) width;
BufferedImage thumb = image;
do {
if (isWidthGreater) {
width /= 2;
if (width < newSize) {
width = newSize;
}
height = (int) (width / ratioWH);
} else {
height /= 2;
if (height < newSize) {
height = newSize;
}
width = (int) (height / ratioHW);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (newSize != (isWidthGreater ? width : height));
return thumb;
}
/**
* <p>Returns a thumbnail of a source image.</p>
* <p>This method offers a good trade-off between speed and quality.
* The result looks better than
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
* the new size is less than half the longest dimension of the source
* image, yet the rendering speed is almost similar.</p>
*
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
* @see #createThumbnail(java.awt.image.BufferedImage, int)
* @param image the source image
* @param newWidth the width of the thumbnail
* @param newHeight the height of the thumbnail
* @return a new compatible <code>BufferedImage</code> containing a
* thumbnail of <code>image</code>
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
* the width of <code>image</code> or if code>newHeight</code> is larger
* than the height of <code>image or if one the dimensions is not > 0</code>
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newWidth, int newHeight) {
int width = image.getWidth();
int height = image.getHeight();
if (newWidth >= width || newHeight >= height) {
throw new IllegalArgumentException("newWidth and newHeight cannot" +
" be greater than the image" +
" dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must" +
" be greater than 0");
}
BufferedImage thumb = image;
do {
if (width > newWidth) {
width /= 2;
if (width < newWidth) {
width = newWidth;
}
}
if (height > newHeight) {
height /= 2;
if (height < newHeight) {
height = newHeight;
}
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (width != newWidth || height != newHeight);
return thumb;
}
/**
* <p>Returns an array of pixels, stored as integers, from a
* <code>BufferedImage</code>. The pixels are grabbed from a rectangular
* area defined by a location and two dimensions. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the source image
* @param x the x location at which to start grabbing pixels
* @param y the y location at which to start grabbing pixels
* @param w the width of the rectangle of pixels to grab
* @param h the height of the rectangle of pixels to grab
* @param pixels a pre-allocated array of pixels of size w*h; can be null
* @return <code>pixels</code> if non-null, a new array of integers
* otherwise
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static int[] getPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
Raster raster = img.getRaster();
return (int[]) raster.getDataElements(x, y, w, h, pixels);
}
// Unmanages the image
return img.getRGB(x, y, w, h, pixels, 0, w);
}
/**
* <p>Writes a rectangular area of pixels in the destination
* <code>BufferedImage</code>. Calling this method on
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
*
* @param img the destination image
* @param x the x location at which to start storing pixels
* @param y the y location at which to start storing pixels
* @param w the width of the rectangle of pixels to store
* @param h the height of the rectangle of pixels to store
* @param pixels an array of pixels, stored as integers
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
* of length < w*h
*/
public static void setPixels(BufferedImage img,
int x, int y, int w, int h, int[] pixels) {
if (pixels == null || w == 0 || h == 0) {
return;
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" +
" >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_INT_RGB) {
WritableRaster raster = img.getRaster();
raster.setDataElements(x, y, w, h, pixels);
} else {
// Unmanages the image
img.setRGB(x, y, w, h, pixels, 0, w);
}
}
}
/*
* $Id: ColorTintFilter.java,v 1.1 2007/01/28 01:45:30 gfx Exp $
*
* Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
*
* Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* Copyright (c) 2006 Romain Guy <romain.guy@mac.ru>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
/**
* <p>A color tint filter can be used to mix a solid color to an image. The
* result is an image tinted by the specified color. The force of the effect
* can be controlled with the <code>mixValue</code>, a number between 0.0 and
* 1.0 that can be seen as the percentage of the mix (0.0 does not affect the
* source image and 1.0 replaces all the pixels by the solid color).</p>
* <p>The color of the pixels in the resulting image is computed as follows:</p>
* <pre>
* cR = cS * (1 - mixValue) + cM * mixValue
* </pre>
* <p>Definition of the parameters:</p>
* <ul>
* <li><code>cR</code>: color of the resulting pixel</li>
* <li><code>cS</code>: color of the source pixel</li>
* <li><code>cM</code>: the solid color to mix with the source image</li>
* <li><code>mixValue</code>: strength of the mix, a value between 0.0 and 1.0</li>
* </ul>
*
* @author Romain Guy <romain.guy@mac.ru>
*/
class ColorTintFilter extends AbstractFilter {
private final Color mixColor;
private final float mixValue;
private int[] preMultipliedRed;
private int[] preMultipliedGreen;
private int[] preMultipliedBlue;
/**
* <p>Creates a new color mixer filter. The specified color will be used
* to tint the source image, with a mixing strength defined by
* <code>mixValue</code>.</p>
*
* @param mixColor the solid color to mix with the source image
* @param mixValue the strength of the mix, between 0.0 and 1.0; if the
* specified value lies outside this range, it is clamped
* @throws IllegalArgumentException if <code>mixColor</code> is null
*/
public ColorTintFilter(Color mixColor, float mixValue) {
if (mixColor == null) {
throw new IllegalArgumentException("mixColor cannot be null");
}
this.mixColor = mixColor;
if (mixValue < 0.0f) {
mixValue = 0.0f;
} else if (mixValue > 1.0f) {
mixValue = 1.0f;
}
this.mixValue = mixValue;
int mix_r = (int) (mixColor.getRed() * mixValue);
int mix_g = (int) (mixColor.getGreen() * mixValue);
int mix_b = (int) (mixColor.getBlue() * mixValue);
// Since we use only lookup tables to apply the filter, this filter
// could be implemented as a LookupOp.
float factor = 1.0f - mixValue;
preMultipliedRed = new int[256];
preMultipliedGreen = new int[256];
preMultipliedBlue = new int[256];
for (int i = 0; i < 256; i++) {
int value = (int) (i * factor);
preMultipliedRed[i] = value + mix_r;
preMultipliedGreen[i] = value + mix_g;
preMultipliedBlue[i] = value + mix_b;
}
}
/**
* <p>Returns the mix value of this filter.</p>
*
* @return the mix value, between 0.0 and 1.0
*/
public float getMixValue() {
return mixValue;
}
/**
* <p>Returns the solid mix color of this filter.</p>
*
* @return the solid color used for mixing
*/
public Color getMixColor() {
return mixColor;
}
/**
* {@inheritDoc}
*/
@Override
public BufferedImage filter(BufferedImage src, BufferedImage dst) {
if (dst == null) {
dst = createCompatibleDestImage(src, null);
}
int width = src.getWidth();
int height = src.getHeight();
int[] pixels = new int[width * height];
GraphicsUtilities.getPixels(src, 0, 0, width, height, pixels);
mixColor(pixels);
GraphicsUtilities.setPixels(dst, 0, 0, width, height, pixels);
return dst;
}
private void mixColor(int[] pixels) {
for (int i = 0; i < pixels.length; i++) {
int argb = pixels[i];
pixels[i] = (argb & 0xFF000000) |
preMultipliedRed[(argb >> 16) & 0xFF] << 16 |
preMultipliedGreen[(argb >> 8) & 0xFF] << 8 |
preMultipliedBlue[argb & 0xFF];
}
}
}
/*
* $Id: AbstractFilter.java,v 1.1 2007/01/28 01:45:27 gfx Exp $
*
* Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
*
* Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* Copyright (c) 2006 Romain Guy <romain.guy@mac.ru>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
/**
* <p>Provides an abstract implementation of the <code>BufferedImageOp</code>
* interface. This class can be used to created new image filters based
* on <code>BufferedImageOp</code>.</p>
*
* @author Romain Guy <romain.guy@mac.ru>
*/
abstract class AbstractFilter implements BufferedImageOp {
public abstract BufferedImage filter(BufferedImage src, BufferedImage dest);
/**
* {@inheritDoc}
*/
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(0, 0, src.getWidth(), src.getHeight());
}
/**
* {@inheritDoc}
*/
public BufferedImage createCompatibleDestImage(BufferedImage src,
ColorModel destCM) {
if (destCM == null) {
destCM = src.getColorModel();
}
return new BufferedImage(destCM,
destCM.createCompatibleWritableRaster(
src.getWidth(), src.getHeight()),
destCM.isAlphaPremultiplied(), null);
}
/**
* {@inheritDoc}
*/
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
return (Point2D) srcPt.clone();
}
/**
* {@inheritDoc}
*/
public RenderingHints getRenderingHints() {
return null;
}
}
Pulse Animation Field
/*
* Copyright (c) 2007, Romain Guy
* 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.
*/
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.ruponent;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.rupoundBorder;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.timing.triggers.FocusTrigger;
import org.jdesktop.animation.timing.triggers.FocusTriggerEvent;
/**
*
* @author Romain Guy <romain.guy@mac.ru>
*/
public class PulseFieldDemo extends JFrame {
public PulseFieldDemo() {
super("PulseField Demo");
add(buildPulsatingField());
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
}
private JComponent buildPulsatingField() {
JTextField field = new JTextField(20);
PulsatingBorder border = new PulsatingBorder(field);
field.setBorder(new CompoundBorder(field.getBorder(), border));
PropertySetter setter = new PropertySetter(
border, "thickness", 0.0f, 1.0f);
Animator animator = new Animator(900, Animator.INFINITE,
Animator.RepeatBehavior.REVERSE, setter);
animator.start();
JPanel panel = new JPanel(new FlowLayout());
panel.add(field);
panel.add(new JButton("OK"));
panel.add(new JButton("Cancel"));
return panel;
}
public static class PulsatingBorder implements Border {
private float thickness = 0.0f;
private JComponent c;
public PulsatingBorder(JComponent c) {
this.c = c;
}
public void paintBorder(Component c, Graphics g,
int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Rectangle2D r = new Rectangle2D.Double(x, y, width - 1, height - 1);
g2.setStroke(new BasicStroke(2.0f * getThickness()));
g2.setComposite(AlphaComposite.SrcOver.derive(getThickness()));
g2.setColor(new Color(0x54A4DE));
g2.draw(r);
}
public Insets getBorderInsets(Component c) {
return new Insets(2, 2, 2, 2);
}
public boolean isBorderOpaque() {
return false;
}
public float getThickness() {
return thickness;
}
public void setThickness(float thickness) {
this.thickness = thickness;
c.repaint();
}
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName());
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new PulseFieldDemo().setVisible(true);
}
});
}
}
Setter Race Animation
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
/*
* SetterRace.java
*
* Created on May 3, 2007, 2:37 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.
*/
/**
* Like BasicRace, only this version uses the property setter capabilities
* of the framework. Instead of manually calculating the position of the
* car, we have the framework do it for us, based on how we set up the
* PropertySetter object. All of this is done at
* construction time and we merely start or stop the animation based on
* the Go/Stop buttons at runtime.
*
* @author Chet
*/
public class SetterRace implements ActionListener {
protected Animator timer;
public static final int RACE_TIME = 2000;
/** Creates a new instance of BasicRace */
public SetterRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
// Now set up an animation that will automatically
// run itself with PropertySetter
timer = PropertySetter.createAnimator(RACE_TIME, basicGUI.getTrack(),
"carPosition", TrackView.START_POS, TrackView.FIRST_TURN_START);
basicGUI.getControlPanel().addListener(this);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
SetterRace race = new SetterRace("Property Setter Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
/**
* Handles clicks on Go/Stop buttons to start/stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Go")) {
if (timer != null) {
timer.stop();
timer.start();
} else {
timer.start();
}
} else if (ae.getActionCommand().equals("Stop")) {
if (timer != null) {
timer.stop();
}
}
}
}
/**
* 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.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* 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.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
/**
* 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.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
Spline Interpolator Test
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import org.jdesktop.animation.timing.interpolation.SplineInterpolator;
/*
* SplineInterpolatorTest.java
*
* Created on May 3, 2007, 8:24 AM
*
* 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 SplineInterpolatorTest extends TimingTargetAdapter {
private long startTime;
private final static int DURATION = 5000;
public void begin() {
startTime = System.nanoTime() / 1000000;
System.out.println("Real\tInterpolated");
System.out.println("----\t------------");
}
/**
* TimingTarget implementation: Calculate the real fraction elapsed and
* output that along with the fraction parameter, which has been
* non-linearly interpolated.
*/
public void timingEvent(float fraction) {
long currentTime = System.nanoTime() / 1000000;
long elapsedTime = currentTime - startTime;
float realFraction = (float)elapsedTime / DURATION;
System.out.println(realFraction + "\t" + fraction);
}
public static void main(String args[]) {
Animator animator =
new Animator(DURATION, new SplineInterpolatorTest());
SplineInterpolator interpolator = new SplineInterpolator(1f, 0f, 0f, 1f);
animator.setInterpolator(interpolator);
// Note that you could get similar behavior by setting the following
// acceleration/deceleration properties instead of the interpolator
// above:
//animator.setAcceleration(.5f);
//animator.setDeceleration(.5f);
animator.setStartDelay(1000);
animator.setResolution(DURATION / 10);
animator.start();
}
}
Trigger Race Animation
import javax.swing.SwingUtilities;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.triggers.ActionTrigger;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
/*
* TriggerRace.java
*
* Created on May 3, 2007, 1:43 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.
*/
/**
* Exactly like SetterRace, only this version uses Triggers to
* start/stop the animation automatically based on the user
* clicking the Go/Stop buttons (no need for an ActionListener here)
*
* @author Chet
*/
public class TriggerRace extends NonLinearRace {
/** Creates a new instance of TriggerRace */
public TriggerRace(String appName) {
super(appName);
// Clicks on the Go button will atuomatically start the animator
JButton goButton = controlPanel.getGoButton();
ActionTrigger trigger = ActionTrigger.addTrigger(goButton, animator);
}
/**
* Handle clicks on the Stop button. Clicks on Go are handled through
* the ActionTrigger above.
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Stop")) {
animator.stop();
}
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
TriggerRace race = new TriggerRace("Trigger Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* 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.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
/**
* 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.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* 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.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
/*
* NonLinearRace.java
*
* Created on May 3, 2007, 7:49 AM
*
* 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.
*/
/**
* Simple subclass of SetterRace that merely sets acceleration/deceleration
* factors to give a non-linear motion effect to the car"s movement.
*
* @author Chet
*/
class NonLinearRace extends BasicRace {
/** Creates a new instance of NonLinearRace */
public NonLinearRace(String appName) {
super(appName);
// Accelerate for first half of duration, decelerate for final 10%
animator.setAcceleration(.5f);
animator.setDeceleration(.1f);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
NonLinearRace race = new NonLinearRace("Non-Linear Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/*
* BasicRace.java
*
* Created on May 3, 2007, 7:37 AM
*
* 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.
*/
/**
* The simplest version of the animation; set up a Animator to
* move the car from one position to another over a given time period.
*
*
* @author Chet
*/
class BasicRace extends TimingTargetAdapter implements ActionListener {
public static final int RACE_TIME = 2000;
Point start = TrackView.START_POS;
Point end = TrackView.FIRST_TURN_START;
Point current = new Point();
protected Animator animator;
TrackView track;
RaceControlPanel controlPanel;
/** Creates a new instance of BasicRace */
public BasicRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
controlPanel = basicGUI.getControlPanel();
controlPanel.addListener(this);
track = basicGUI.getTrack();
animator = new Animator(RACE_TIME, this);
}
//
// Events
//
/**
* This receives the Go/Stop events that start/stop the animation
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Go")) {
animator.stop();
animator.start();
} else if (ae.getActionCommand().equals("Stop")) {
animator.stop();
}
}
/**
* TimingTarget implementation: calculate and set the current
* car position based on the animation fraction
*/
public void timingEvent(float fraction) {
// Simple linear interpolation to find current position
current.x = (int)(start.x + (end.x - start.x) * fraction);
current.y = (int)(start.y + (end.y - start.y) * fraction);
// set the new position; this will force a repaint in TrackView
// and will display the car in the new position
track.setCarPosition(current);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
BasicRace race = new BasicRace("BasicRace");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
Tumble Item Project
/*
* Copyright (c) 1995 - 2008 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 Sun Microsystems 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 components;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
/**
* @author jag
* @author mem
* @author kwalrath
* @author ir71389
*/
/*
* TumbleItem.java requires these files:
* all the images in the images/tumble directory
* (or, if specified in the applet tag, another directory [dir]
* with images named T1.gif ... Tx.gif, where x is the total
* number of images [nimgs])
* the appropriate code to specify that the applet be executed,
* such as the HTML code in TumbleItem.html or TumbleItem.atag,
* or the JNLP code in TumbleItem.jnlp
*
*/
public class TumbleItem extends JApplet
implements ActionListener {
int loopslot = -1; //the current frame number
String dir; //the directory relative to the codebase
//from which the images are loaded
Timer timer;
//the timer animating the images
int pause; //the length of the pause between revs
int offset; //how much to offset between loops
int off; //the current offset
int speed; //animation speed
int nimgs; //number of images to animate
int width; //width of the applet"s content pane
Animator animator; //the applet"s content pane
ImageIcon imgs[]; //the images
int maxWidth; //width of widest image
JLabel statusLabel;
//Called by init.
protected void loadAppletParameters() {
//Get the applet parameters.
String at = getParameter("img");
dir = (at != null) ? at : "images/tumble";
at = getParameter("pause");
pause = (at != null) ? Integer.valueOf(at).intValue() : 1900;
at = getParameter("offset");
offset = (at != null) ? Integer.valueOf(at).intValue() : 0;
at = getParameter("speed");
speed = (at != null) ? (1000 / Integer.valueOf(at).intValue()) : 100;
at = getParameter("nimgs");
nimgs = (at != null) ? Integer.valueOf(at).intValue() : 16;
at = getParameter("maxwidth");
maxWidth = (at != null) ? Integer.valueOf(at).intValue() : 0;
}
/**
* Create the GUI. For thread safety, this method should
* be invoked from the event-dispatching thread.
*/
private void createGUI() {
//Animate from right to left if offset is negative.
width = getSize().width;
if (offset < 0) {
off = width - maxWidth;
}
//Custom component to draw the current image
//at a particular offset.
animator = new Animator();
animator.setOpaque(true);
animator.setBackground(Color.white);
setContentPane(animator);
//Put a "Loading Images..." label in the middle of
//the content pane. To center the label"s text in
//the applet, put it in the center part of a
//BorderLayout-controlled container, and center-align
//the label"s text.
statusLabel = new JLabel("Loading Images...",
JLabel.CENTER);
animator.add(statusLabel, BorderLayout.CENTER);
}
//Background task for loading images.
SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
@Override
public ImageIcon[] doInBackground() {
final ImageIcon[] innerImgs = new ImageIcon[nimgs];
for (int i = 0; i < nimgs; i++) {
innerImgs[i] = loadImage(i + 1);
}
return innerImgs;
}
@Override
public void done() {
//Remove the "Loading images" label.
animator.removeAll();
loopslot = -1;
try {
imgs = get();
} catch (InterruptedException ignore) {}
catch (java.util.concurrent.ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
};
//Called when this applet is loaded into the browser.
public void init() {
loadAppletParameters();
//Execute a job on the event-dispatching thread:
//creating this applet"s GUI.
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
createGUI();
}
});
} catch (Exception e) {
System.err.println("createGUI didn"t successfully complete");
}
//Set up timer to drive animation events.
timer = new Timer(speed, this);
timer.setInitialDelay(pause);
timer.start();
//Start loading the images in the background.
worker.execute();
}
//The component that actually presents the GUI.
public class Animator extends JPanel {
public Animator() {
super(new BorderLayout());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (worker.isDone() &&
(loopslot > -1) && (loopslot < nimgs)) {
if (imgs != null && imgs[loopslot] != null) {
imgs[loopslot].paintIcon(this, g, off, 0);
}
}
}
}
//Handle timer event. Update the loopslot (frame number) and the
//offset. If it"s the last frame, restart the timer to get a long
//pause between loops.
public void actionPerformed(ActionEvent e) {
//If still loading, can"t animate.
if (!worker.isDone()) {
return;
}
loopslot++;
if (loopslot >= nimgs) {
loopslot = 0;
off += offset;
if (off < 0) {
off = width - maxWidth;
} else if (off + maxWidth > width) {
off = 0;
}
}
animator.repaint();
if (loopslot == nimgs - 1) {
timer.restart();
}
}
public void start() {
if (worker.isDone() && (nimgs > 1)) {
timer.restart();
}
}
public void stop() {
timer.stop();
}
/**
* Load the image for the specified frame of animation. Since
* this runs as an applet, we use getResourceAsStream for
* efficiency and so it"ll work in older versions of Java Plug-in.
*/
protected ImageIcon loadImage(int imageNum) {
String path = dir + "/T" + imageNum + ".gif";
int MAX_IMAGE_SIZE = 2400; //Change this to the size of
//your biggest image, in bytes.
int count = 0;
BufferedInputStream imgStream = new BufferedInputStream(
this.getClass().getResourceAsStream(path));
if (imgStream != null) {
byte buf[] = new byte[MAX_IMAGE_SIZE];
try {
count = imgStream.read(buf);
imgStream.close();
} catch (java.io.IOException ioe) {
System.err.println("Couldn"t read stream from file: " + path);
return null;
}
if (count <= 0) {
System.err.println("Empty file: " + path);
return null;
}
return new ImageIcon(Toolkit.getDefaultToolkit().createImage(buf));
} else {
System.err.println("Couldn"t find file: " + path);
return null;
}
}
public String getAppletInfo() {
return "Title: TumbleItem v1.2, 23 Jul 1997\n"
+ "Author: James Gosling\n"
+ "A simple Item class to play an image loop.";
}
public String[][] getParameterInfo() {
String[][] info = {
{"img", "string", "the directory containing the images to loop"},
{"pause", "int", "pause between complete loops; default is 3900"},
{"offset", "int", "offset of each image to simulate left (-) or "
+ "right (+) motion; default is 0 (no motion)"},
{"speed", "int", "the speed at which the frames are looped; "
+ "default is 100"},
{"nimgs", "int", "the number of images to be looped; default is 16"},
{"maxwidth", "int", "the maximum width of any image in the loop; "
+ "default is 0"}
};
return info;
}
}