Java AWT 图形 - 以亚像素精度插值形状
Java AWT Graphics - Interpolate Shapes with subpixel accuracy
我正在做一个项目,我需要一个球在椭圆(例如圆周)上移动的动画。目前,我通过重写 paintComponent()
方法在 JPanel
上全部绘制,移动效果来自以固定速率重绘它,并改变“球”的位置.
一切都很好,除了球似乎以“梯子”方式移动,而且不流畅。既然是图之间的问题,我想乱搞RenderingHints
是没有出路的。
一个问题的SSCCE(从测试来看,可能没有那么明显,但是当你有超过100个球在移动时,它看起来很奇怪):
import javax.swing.*;
import java.awt.*;
public class SSCCE {
private static long dt;
private static JPanel animationPanel = createAnimationPanel();
private static JPanel createAnimationPanel() {
return new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
Point center = new Point(w / 2, h / 2);
// Drawing circumference.
int radius = 80;
int x = center.x - radius;
int y = center.y - radius;
g2d.drawOval(x, y, radius * 2, radius * 2);
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
int xPos = (int) (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
int yPos = (int) (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
g2d.fillOval(xPos, yPos, ballWidth, ballWidth);
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Draw Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(animationPanel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setEnabled(true);
frame.setVisible(true);
frame.requestFocus();
});
Thread updateThread = new Thread(() -> {
long lastTime = System.currentTimeMillis();
while (true) { // I don't actually use "while (true)", but it doesn't matter since it's a test.
dt = System.currentTimeMillis() - lastTime;
animationPanel.repaint();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Update Thread");
SwingUtilities.invokeLater(updateThread::start);
}
}
所以总而言之,我认为也许我可以将位置插入到子像素级别,并以某种方式在“像素之间”绘制球,类似于抗锯齿效果的工作方式...可能吗?如果没有,是否有任何解决方法?
您的问题与将 double
值截断为 int
有关
int xPos = (int) (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
int yPos = (int) (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
相反,您应该使用 double
值。在这种情况下,您可以简单地 translate
Graphics2D
API,例如...
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
double xPos = (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
double yPos = (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
if (theActualDot == null) {
theActualDot = new Ellipse2D.Double(0, 0, ballWidth, ballWidth);
}
g2d.translate(xPos, yPos);
g2d.fill(theActualDot);
可运行示例
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Main {
private static long dt;
private static JPanel animationPanel = createAnimationPanel();
private static JPanel createAnimationPanel() {
return new JPanel() {
private Shape theActualDot;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
Point center = new Point(w / 2, h / 2);
// Drawing circumference.
int radius = 80;
int x = center.x - radius;
int y = center.y - radius;
g2d.drawOval(x, y, radius * 2, radius * 2);
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
double xPos = (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
double yPos = (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
if (theActualDot == null) {
theActualDot = new Ellipse2D.Double(0, 0, ballWidth, ballWidth);
}
g2d.translate(xPos, yPos);
g2d.fill(theActualDot);
g2d.dispose();
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Draw Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(animationPanel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setEnabled(true);
frame.setVisible(true);
frame.requestFocus();
Timer timer = new Timer(5, new ActionListener() {
long lastTime = System.currentTimeMillis();
@Override
public void actionPerformed(ActionEvent e) {
dt = System.currentTimeMillis() - lastTime;
animationPanel.repaint();
}
});
timer.start();
});
}
}
我正在做一个项目,我需要一个球在椭圆(例如圆周)上移动的动画。目前,我通过重写 paintComponent()
方法在 JPanel
上全部绘制,移动效果来自以固定速率重绘它,并改变“球”的位置.
一切都很好,除了球似乎以“梯子”方式移动,而且不流畅。既然是图之间的问题,我想乱搞RenderingHints
是没有出路的。
一个问题的SSCCE(从测试来看,可能没有那么明显,但是当你有超过100个球在移动时,它看起来很奇怪):
import javax.swing.*;
import java.awt.*;
public class SSCCE {
private static long dt;
private static JPanel animationPanel = createAnimationPanel();
private static JPanel createAnimationPanel() {
return new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
Point center = new Point(w / 2, h / 2);
// Drawing circumference.
int radius = 80;
int x = center.x - radius;
int y = center.y - radius;
g2d.drawOval(x, y, radius * 2, radius * 2);
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
int xPos = (int) (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
int yPos = (int) (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
g2d.fillOval(xPos, yPos, ballWidth, ballWidth);
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Draw Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(animationPanel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setEnabled(true);
frame.setVisible(true);
frame.requestFocus();
});
Thread updateThread = new Thread(() -> {
long lastTime = System.currentTimeMillis();
while (true) { // I don't actually use "while (true)", but it doesn't matter since it's a test.
dt = System.currentTimeMillis() - lastTime;
animationPanel.repaint();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Update Thread");
SwingUtilities.invokeLater(updateThread::start);
}
}
所以总而言之,我认为也许我可以将位置插入到子像素级别,并以某种方式在“像素之间”绘制球,类似于抗锯齿效果的工作方式...可能吗?如果没有,是否有任何解决方法?
您的问题与将 double
值截断为 int
int xPos = (int) (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
int yPos = (int) (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
相反,您应该使用 double
值。在这种情况下,您可以简单地 translate
Graphics2D
API,例如...
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
double xPos = (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
double yPos = (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
if (theActualDot == null) {
theActualDot = new Ellipse2D.Double(0, 0, ballWidth, ballWidth);
}
g2d.translate(xPos, yPos);
g2d.fill(theActualDot);
可运行示例
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Main {
private static long dt;
private static JPanel animationPanel = createAnimationPanel();
private static JPanel createAnimationPanel() {
return new JPanel() {
private Shape theActualDot;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
Point center = new Point(w / 2, h / 2);
// Drawing circumference.
int radius = 80;
int x = center.x - radius;
int y = center.y - radius;
g2d.drawOval(x, y, radius * 2, radius * 2);
// Drawing ball.
g2d.setColor(Color.RED);
int ballWidth = 20;
double rad = Math.toRadians((dt / ((radius * 2) / 8.0)));
double xPos = (center.x - (Math.cos(rad) * radius) - (ballWidth / 2));
double yPos = (center.y - (Math.sin(rad) * radius) - (ballWidth / 2));
if (theActualDot == null) {
theActualDot = new Ellipse2D.Double(0, 0, ballWidth, ballWidth);
}
g2d.translate(xPos, yPos);
g2d.fill(theActualDot);
g2d.dispose();
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Draw Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(animationPanel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setEnabled(true);
frame.setVisible(true);
frame.requestFocus();
Timer timer = new Timer(5, new ActionListener() {
long lastTime = System.currentTimeMillis();
@Override
public void actionPerformed(ActionEvent e) {
dt = System.currentTimeMillis() - lastTime;
animationPanel.repaint();
}
});
timer.start();
});
}
}