Swing翻译比例改变顺序错位

时间:2018-03-07 16:31:44

标签: java swing math graphics java-2d

在使用Java Swing将缩放操作拆分为一个转换操作时,我发现了一些奇怪的东西。也许我做了一些愚蠢的事情,但我不确定在哪里。

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

以下是代码的两个版本。他们应该做同样的事情,但事实并非如此。以下是截图:

首先产生:screenshot1

第二次产生:screenshot2

我认为draw1中围绕scale操作的两个转换操作应该等同于draw2中的scale转换操作。

有什么建议吗?

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("https://i.stack.imgur.com/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();
    }
}

1 个答案:

答案 0 :(得分:0)

不确定这个问题是否真的开放。无论如何,这是我的答案。

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

  

以最常用的方式将AffineTransform Tx与此AffineTransform Cx连接,以提供Tx映射到以前用户空间的新用户空间。更新Cx以执行组合转换。通过更新后的转换p转换点Cx'相当于首先将p转换为Tx,然后将结果转换为原始转换Cx,如下所示: Cx'(p) = Cx(Tx(p))

将其与preConcatenate进行比较:

  

以不太常用的方式将AffineTransform Tx与此AffineTransform Cx连接,以便Tx修改相对于绝对像素空间的坐标转换,而不是相对于现有用户空间。更新Cx以执行组合转换。通过更新后的转换p转换点Cx'相当于首先使用原始转换p转换Cx,然后将结果转换为Tx,如下所示:{{1} }

scaletranslate方法实际上是Cx'(p) = Tx(Cx(p))。让我们在concatenate方法draw1(中心),C(比例)和S(翻译)中调用3次转换。因此,您的复合转换实际上是T。特别是,这意味着C(S(T(p)))已应用于S,但未应用于T,因此您的C并未真正将图像置于中心位置。一个简单的解决方法是更改​​CS的顺序,但我认为更合适的解决方案是这样的:

C

我认为这种方法的最大优点是您无需使用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); } / T

重新计算stretchx