我编写了一个使用 AffineTransform 绘制箭头的测试程序。程序以窗口中心为箭头起点,鼠标位置为终点。但是,生成的箭头位置和角度不正确。当我的鼠标位于绿点位置时,正确的箭头位置应如下图红色箭头所示。我想知道是什么导致绘制了错误的箭头。
我用来绘制整个箭头的逻辑如下:
下面是绘制箭头的代码:
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
class CanvasPane extends JPanel {
Rectangle start = new Rectangle();
Line2D.Double arrowShaft = new Line2D.Double();
Rectangle arrowhead = new Rectangle(new Dimension(16, 16));
double shearMultiplier = 0.5;
double arrowheadLength = arrowhead.width * Math.sqrt(2) * (1 + shearMultiplier);
MouseEvent me;
double length, angle;
CanvasPane() {
setPreferredSize(new Dimension(1000, 600));
addMouseMotionListener(new MouseInputAdapter() {
@Override
public void mouseMoved(MouseEvent e)
{
me = e;
repaint();
}
});
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
start.setBounds(getWidth()/2 - 5, getHeight()/2 - 5, 10, 10);
Graphics2D g2d = (Graphics2D) g;
g2d.fill(start);
if (me != null) {
length = Point.distance(start.getCenterX(), start.getCenterY(), me.getX(), me.getY());
angle = Math.atan2(me.getY() - start.getCenterY(), me.getX() - start.getCenterX());
arrowShaft.setLine(0, 0, length - arrowheadLength, 0);
AffineTransform storedTransform = g2d.getTransform();
AffineTransform AT = new AffineTransform();
AT.translate(start.getCenterX(), start.getCenterY());
AT.rotate(angle);
// For the arrowShaft, it is first rotated and then translated.
g2d.transform(AT);
g2d.draw(arrowShaft);
AT.translate(length - arrowheadLength, 0);
AT.rotate(Math.toRadians(-45));
AT.shear(shearMultiplier, shearMultiplier);
// For the arrowhead, it is first sheared, then rotated, translated, rotated again and finally translated.
g2d.transform(AT);
g2d.draw(arrowhead);
g2d.setTransform(storedTransform);
}
}
}
public class DrawLine {
Container createContentPane() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setOpaque(true);
contentPane.add(new CanvasPane(), BorderLayout.CENTER);
return contentPane;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawLine drawLine = new DrawLine();
frame.setContentPane(drawLine.createContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
您能帮我确定是什么原因导致箭头绘制的位置和角度不正确吗?谢谢你。
我会重置箭头的 AffineTransform,因为在进行主要旋转和平移之前需要对其进行独立变换(已应用于 Graphics2D 对象。
AT.translate(start.getCenterX(), start.getCenterY());
AT.rotate(angle);
g2d.transform(AT);
g2d.draw(arrowShaft);
AT.setToIdentity();
AT.translate(length - arrowheadLength, 0);
AT.rotate(Math.toRadians(-45));
AT.shear(shearMultiplier, shearMultiplier);
g2d.transform(AT);
g2d.draw(arrowhead);
或者,先变换头部矩形,然后变换图形坐标,然后绘制:
AffineTransform headTransform = new AffineTransform();
headTransform.translate(length - arrowheadLength, 0);
headTransform.rotate(Math.toRadians(-45));
headTransform.shear(shearMultiplier, shearMultiplier);
Shape arrowhead2 = headTransform.createTransformedShape(new Rectangle(new Dimension(16, 16)));
AT.translate(start.getCenterX(), start.getCenterY());
AT.rotate(angle);
g2d.transform(AT);
g2d.draw(arrowShaft);
g2d.draw(arrowhead2);
无论哪种方式,倾斜的矩形都需要自己的变换。
但是话虽如此,我什至不会使用仿射变换,而是使用包括 Path2D 的
java.awt.geom
库,然后使用基本几何图形根据需要绘制箭头和轴。这样,您仍然可以使用有效的 RenderingHints 来对绘制的形状进行抗锯齿:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
@SuppressWarnings("serial")
public class CanvasPane2 extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 600;
private static final int ARROW_HEAD_LENGTH = 36;
private static final int ARROW_HEAD_WIDTH = 16;
private static final Color ARROW_HEAD_COLOR = Color.RED;
private static final Stroke ARROW_HEAD_STROKE = new BasicStroke(3f);
private double theta;
private double r;
private Line2D arrowShaft;
private Shape arrowHead;
public CanvasPane2() {
addMouseMotionListener(new MyMouse());
}
@Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private class MyMouse extends MouseAdapter {
@Override
public void mouseMoved(MouseEvent e) {
int x1 = e.getX() - getWidth() / 2;
int y1 = e.getY() - getHeight() / 2;
theta = Math.atan2(y1, x1);
r = Point.distance(0, 0, x1, y1);
arrowHead = createArrowHead();
arrowShaft = createShaft();
repaint();
}
}
private Line2D createShaft() {
double x = (r - ARROW_HEAD_LENGTH) * Math.cos(theta);
double y = (r - ARROW_HEAD_LENGTH) * Math.sin(theta);
Line2D line = new Line2D.Double(transX(0), transY(0), transX(x), transY(y));
return line;
}
private Shape createArrowHead() {
Path2D path = new Path2D.Double();
Point2D tip, base, right, left;
double tipX = r * Math.cos(theta);
double tipY = r * Math.sin(theta);
tip = new Point2D.Double(transX(tipX), transY(tipY));
double baseR = (r - ARROW_HEAD_LENGTH);
double baseX = baseR * Math.cos(theta);
double baseY = baseR * Math.sin(theta);
base = new Point2D.Double(transX(baseX), transY(baseY));
double sideR = r - ARROW_HEAD_LENGTH / 2.0;
double deltaTheta = Math.atan2(ARROW_HEAD_WIDTH / 2.0, sideR);
double rightX = sideR * Math.cos(theta + deltaTheta);
double rightY = sideR * Math.sin(theta + deltaTheta);
right = new Point2D.Double(transX(rightX), transY(rightY));
double leftX = sideR * Math.cos(theta - deltaTheta);
double leftY = sideR * Math.sin(theta - deltaTheta);
left = new Point2D.Double(transX(leftX), transY(leftY));
path.moveTo(tip.getX(), tip.getY());
path.lineTo(right.getX(), right.getY());
path.lineTo(base.getX(), base.getY());
path.lineTo(left.getX(), left.getY());
path.closePath();
return path;
}
public double transX(double x0) {
return x0 + getWidth() / 2;
}
public double transY(double y0) {
return y0 + getHeight() / 2;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int delta = 5;
int x = getWidth() / 2 - delta;
int y = getHeight() / 2 - delta;
g2d.fill(new Rectangle(x, y, 2 * delta, 2 * delta));
if (arrowShaft != null) {
g2d.draw(arrowShaft);
}
if (arrowHead != null) {
Graphics2D g2dB = (Graphics2D) g2d.create();
g2dB.setColor(ARROW_HEAD_COLOR);
g2dB.setStroke(ARROW_HEAD_STROKE);
g2dB.fill(arrowHead);
g2dB.dispose();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
CanvasPane2 mainPanel = new CanvasPane2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
顺便说一句,您将需要学习和使用 Java 命名约定。变量名应全部以小写字母开头,而类名应以大写字母开头。学习并遵循这一点将使我们更好地理解您的代码,并使您更好地理解其他人的代码。