In this post, I will show you some tips (or tools) for 2D animation on Java Swing component.
There are 2 ways to rendering animation on Swing compinent - Thread vs Timer.
I think Swing rendering is based on single thread model, so we should use Timer. But I have used separate thread for executing animation and it works fine, so should be fine for not critical application.
The class takes Model2D for actual animate model and render it.
One thing you should remember - AnimateCanvas calculate model of animation (coodinates or position or whatever) on animation rendering process, so if the calculation takes takes log time, the animation cannot meet the frame per second passed to constructor.
Phew!! Ok now I can show you Bezier animation example.
I picked up the main code from BezierAnime3D project and made a small change in order to adopt my Model2D interface.
These books are bit old but still very useful for developing rich gui application. I'm not sure why Filthy Rich Clients is not so famous :(.
There are 2 ways to rendering animation on Swing compinent - Thread vs Timer.
I think Swing rendering is based on single thread model, so we should use Timer. But I have used separate thread for executing animation and it works fine, so should be fine for not critical application.
Animator class
Ok then, I can show you a simple animator canvas class I created.The class takes Model2D for actual animate model and render it.
One thing you should remember - AnimateCanvas calculate model of animation (coodinates or position or whatever) on animation rendering process, so if the calculation takes takes log time, the animation cannot meet the frame per second passed to constructor.
package com.dukesoftware.utils.swing.others; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import javax.swing.JComponent; import javax.swing.Timer; import com.dukesoftware.utils.swing.graphics2d.Model2D; public class AnimateCanvas extends JComponent implements ComponentListener, ActionListener{ private int width, height; private final Timer animator2D; private final Model2D model2D; public AnimateCanvas(Model2D model2D, int width, int height, int fps) { this.model2D = model2D; this.width = width; this.height = height; this.animator2D = new Timer(1000/fps, this); model2D.init(width, height); setPreferredSize(new Dimension(width, height)); addComponentListener(this); } public void stop() { animator2D.stop(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; model2D.step(width, height); model2D.render(width, height, g2); g2.dispose(); } public void start() { animator2D.start(); } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { width = getWidth(); height = getHeight(); } public void componentShown(ComponentEvent e) { } public void actionPerformed(ActionEvent e) { repaint(); } }
package com.dukesoftware.utils.swing.graphics2d; import java.awt.Graphics2D; public interface Model2D { void init(int w, int h); void step(int w, int h); void render(int w, int h, Graphics2D g2); boolean isAnimation(); String name(); }
BezierAnime on AnimateCanvas
Ok now I will show you how to use AnimateCanvas class before showing actual animation, let's define reusable JFrame class which holds AnimateCanvas.package com.dukesoftware.utils.swing.others; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import com.dukesoftware.utils.swing.graphics2d.Model2D; public class Graphics2DTester { public final static int WIDTH = 800, HEIGHT = 640; private final AnimateCanvas canvas; private final JFrame frame; private final Model2D model2D; public Graphics2DTester(Model2D model2D){ this.model2D = model2D; canvas = new AnimateCanvas(model2D, WIDTH, HEIGHT, 30); frame = createExitJFrame(canvas, model2D.name()); frame.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { canvas.stop(); } }); } public void start(){ if(model2D.isAnimation()){ canvas.start(); } frame.setVisible(true); } private static JFrame createExitJFrame(Component component, String title, int width, int height){ JFrame frame = new JFrame(title); frame.getContentPane().add(component, BorderLayout.CENTER); frame.setPreferredSize(new Dimension(width, height)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); return frame; } }
Phew!! Ok now I can show you Bezier animation example.
I picked up the main code from BezierAnime3D project and made a small change in order to adopt my Model2D interface.
/** * 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 BezierAnim3D project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.dukesoftware.utils.swing.graphics2d; import static java.awt.Color.BLUE; import static java.awt.Color.RED; import static java.awt.Color.YELLOW; import java.awt.BasicStroke; import java.awt.Color; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.geom.GeneralPath; import com.dukesoftware.utils.swing.others.Graphics2DTester; public class BezierAnim implements Model2D { public static void main(String[] args) { new Graphics2DTester(new BezierAnim()).start(); } private static final int NUMPTS = 6; private BasicStroke solid = new BasicStroke(10.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); private float animpts[] = new float[NUMPTS * 2]; private float deltas[] = new float[NUMPTS * 2]; private Paint fillPaint, drawPaint; private boolean doFill = true; private boolean doDraw = true; private GradientPaint gradient; private BasicStroke stroke; public BezierAnim() { gradient = new GradientPaint(0,0,RED,200,200,YELLOW); fillPaint = gradient; drawPaint = BLUE; stroke = solid; } private void animate(float[] pts, float[] deltas, int index, int limit) { float newpt = pts[index] + deltas[index]; if (newpt <= 0) { newpt = -newpt; deltas[index] = (float) (Math.random() * 4.0 + 2.0); } else if (newpt >= (float) limit) { newpt = 2.0f * limit - newpt; deltas[index] = - (float) (Math.random() * 4.0 + 2.0); } pts[index] = newpt; } public void init(int w, int h) { for (int i = 0; i < animpts.length; i += 2) { animpts[i + 0] = (float) (Math.random() * w); animpts[i + 1] = (float) (Math.random() * h); deltas[i + 0] = (float) (Math.random() * 6.0 + 4.0); deltas[i + 1] = (float) (Math.random() * 6.0 + 4.0); if (animpts[i + 0] > w / 2.0f) { deltas[i + 0] = -deltas[i + 0]; } if (animpts[i + 1] > h / 2.0f) { deltas[i + 1] = -deltas[i + 1]; } } gradient = new GradientPaint(0,0,RED,w*.7f,h*.7f,YELLOW); } public void step(int w, int h) { for (int i = 0; i < animpts.length; i += 2) { animate(animpts, deltas, i + 0, w); animate(animpts, deltas, i + 1, h); } } public void render(int w, int h, Graphics2D g2) { g2.setColor(Color.WHITE); g2.fillRect(0, 0, w, h); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); float[] ctrlpts = animpts; int len = ctrlpts.length; float prevx = ctrlpts[len - 2]; float prevy = ctrlpts[len - 1]; float curx = ctrlpts[0]; float cury = ctrlpts[1]; float midx = (curx + prevx) / 2.0f; float midy = (cury + prevy) / 2.0f; GeneralPath gp = new GeneralPath(GeneralPath.WIND_NON_ZERO); gp.moveTo(midx, midy); for (int i = 2; i <= ctrlpts.length; i += 2) { float x1 = (midx + curx) / 2.0f; float y1 = (midy + cury) / 2.0f; prevx = curx; prevy = cury; if (i < ctrlpts.length) { curx = ctrlpts[i + 0]; cury = ctrlpts[i + 1]; } else { curx = ctrlpts[0]; cury = ctrlpts[1]; } midx = (curx + prevx) / 2.0f; midy = (cury + prevy) / 2.0f; float x2 = (prevx + midx) / 2.0f; float y2 = (prevy + midy) / 2.0f; gp.curveTo(x1, y1, x2, y2, midx, midy); } gp.closePath(); if (doDraw) { g2.setPaint(drawPaint); g2.setStroke(stroke); g2.draw(gp); } if (doFill) { g2.setPaint(fillPaint); g2.fill(gp); } } public boolean isAnimation() { return true; } public String name() { return "BezierAnim"; } }Hope you can see the animation below...
Reference Books
If you are interested in animation on swing, you should definitely read following books.These books are bit old but still very useful for developing rich gui application. I'm not sure why Filthy Rich Clients is not so famous :(.
コメント