java - Swing平移刻度更改顺序错位

标签 java swing math graphics java-2d

当使用 Java Swing 将平移操作围绕缩放操作进行拆分时,我发现了一些奇怪的情况。也许我做了一些愚蠢的事情,但我不确定在哪里。

在第一个版本中,我将图像居中,缩放,然后将其平移到所需的位置。 在第二个版本中,我直接缩放图像,然后平移到所需的位置,以补偿非居中图像。 这两个解决方案应该是等效的。另外,当考虑围绕一个点的旋转和另一个点的运动时,这一点也很重要。我的代码也可以做到这一点...但为什么这不起作用?

这是代码的两个版本。他们应该做完全相同的事情,但他们没有。以下是截图:

首先产生:screenshot1

第二个产品:screenshot2

我认为draw1中围绕缩放操作的两个平移操作应该等同于draw2中的缩放平移操作。

有什么建议吗?

MCVE:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.net.URL;

public class Asteroid extends JComponent implements ActionListener {

    public static final Dimension FRAME_SIZE = new Dimension(640, 480);
    public double x = 200;
    public double y = 200;
    public int radius = 40;
    private AffineTransform bgTransfo;
    private final BufferedImage im2;
    private JCheckBox draw1Check = new JCheckBox("Draw 1", true);

    Asteroid() {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new URL("/image/CWJdo.png"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        im2 = img;
        initUI();
    }

    private final void initUI() {
        draw1Check.addActionListener(this);
        JFrame frame = new JFrame("FrameDemo");
        frame.add(BorderLayout.CENTER, this);
        frame.add(BorderLayout.PAGE_START, draw1Check);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        Asteroid asteroid = new Asteroid();
    }

    @Override
    public Dimension getPreferredSize() {
        return FRAME_SIZE;
    }

    @Override
    public void paintComponent(Graphics g0) {
        Graphics2D g = (Graphics2D) g0;
        g.setColor(Color.white);
        g.fillRect(0, 0, 640, 480);
        if (draw1Check.isSelected()) {
            draw1(g);
        } else {
            draw2(g);
        }
    }

    public void draw1(Graphics2D g) {//Draw method - draws asteroid
        double imWidth = im2.getWidth();
        double imHeight = im2.getHeight();
        double stretchx = (2.0 * radius) / imWidth;
        double stretchy = (2.0 * radius) / imHeight;

        bgTransfo = new AffineTransform();
        //centering
        bgTransfo.translate(-imWidth / 2.0, -imHeight / 2.0);
        //scaling
        bgTransfo.scale(stretchx, stretchy);
        //translation
        bgTransfo.translate(x  / stretchx, y / stretchy);

        //draw correct position
        g.setColor(Color.CYAN);
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

        //draw sprite
        g.drawImage(im2, bgTransfo, this);
    }

    public void draw2(Graphics2D g) {//Draw method - draws asteroid
        double imWidth = im2.getWidth();
        double imHeight = im2.getHeight();
        double stretchx = (2.0 * radius) / imWidth;
        double stretchy = (2.0 * radius) / imHeight;

        bgTransfo = new AffineTransform();

        //scale
        bgTransfo.scale(stretchx, stretchy);

        //translate and center
        bgTransfo.translate((x - radius) / stretchx, (y - radius) / stretchy);

        //draw correct position
        g.setColor(Color.CYAN);
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

        //draw sprite 
        g.drawImage(im2, bgTransfo, this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();
    }
}

最佳答案

不确定这个问题是否仍然存在。无论如何,这是我的答案。

我认为理解这种行为的关键部分是 AffineTransform.concatenate 之间的区别和 AffineTransform.preConcatenate方法。问题是,最终的转换取决于应用子转换的顺序。 引用 concatenate JavaDoc

Concatenates an AffineTransform Tx to this AffineTransform Cx in the most commonly useful way to provide a new user space that is mapped to the former user space by Tx. Cx is updated to perform the combined transformation. Transforming a point p by the updated transform Cx' is equivalent to first transforming p by Tx and then transforming the result by the original transform Cx like this: Cx'(p) = Cx(Tx(p))

将此与 preConcatenate 进行比较:

Concatenates an AffineTransform Tx to this AffineTransform Cx in a less commonly used way such that Tx modifies the coordinate transformation relative to the absolute pixel space rather than relative to the existing user space. Cx is updated to perform the combined transformation. Transforming a point p by the updated transform Cx' is equivalent to first transforming p by the original transform Cx and then transforming the result by Tx like this: Cx'(p) = Tx(Cx(p))

scaletranslate方法可以有效地连接。让我们在 draw1 方法中调用 3 个转换:C(中心)、S(缩放)和 T(平移)。所以你的复合变换实际上是C(S(T(p)))。特别是,这意味着 S 应用于 T 而不是应用于 C,因此您的 C 并未真正使图像居中。一个简单的修复方法是更改​​ SC 的顺序,但我认为更合适的修复方法是这样的:

public void draw3(Graphics2D g) {
    //Draw method - draws asteroid
    double imWidth = im2.getWidth();
    double imHeight = im2.getHeight();
    double stretchx = (2.0 * radius) / imWidth;
    double stretchy = (2.0 * radius) / imHeight;

    AffineTransform bgTransfo = new AffineTransform();

    //translation
    bgTransfo.translate(x, y);

    //scaling
    bgTransfo.scale(stretchx, stretchy);

    //centering
    bgTransfo.translate(-imWidth / 2.0, -imHeight / 2.0);

    //draw correct position
    g.setColor(Color.CYAN);
    g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

    //draw sprite
    g.drawImage(im2, bgTransfo, this);
}

我认为这种方法的一大优点是您不必使用stretchx/stretchy重新计算T

关于java - Swing平移刻度更改顺序错位,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49156875/

相关文章:

java - 在java中设置Jasper Report的字体

java - 自定义 JScrollPane 中的 JRadioButtons 未出现?

java - 几秒钟后更改按钮的背景

java - 当事情发生时随机化

java.lang.OutOfMemory错误: Java heap space error during bulk insert to mysql database?

java - 更新分位数而不是重新计算

javascript - 尝试对数字的每一位进行平方,但我的算法不起作用?

java - Swing 中的可折叠/下拉 Pane

java - 链接两个 JFrame,以便一个始终准确地位于另一个 JFrame 之上

arrays - 使用线段树查找范围内第一个和最后一个元素、倒数第二个和倒数第二个元素的乘积之和