【问题标题】:Swing translate scale change order misplacement摆动平移比例更改订单错位
【发布时间】:2018-08-15 20:21:10
【问题描述】:

在使用 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("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();
    }
}

【问题讨论】:

  • 因为position是每个圆的中心,而fillOvaldrawImage要求位置是左上角。
  • 如需尽快获得更好的帮助,请发帖minimal reproducible exampleShort, Self Contained, Correct Example
  • @meowgoesthedog 是的,我知道我重新对齐了图像(例如,我在两个版本中减去半径.. 在第一个版本中使用第一个翻译,在第二个版本中使用 uniq 翻译..)跨度>
  • 是的,但是在draw1 中调用bgTransfo.translate 时,您传递了position 而不是position - radius。这是关键线。
  • Dmitri,我认为这里的基本问题是:您了解AffineTransform 的作用以及scale + translate 的确切组合吗?我认为如果您暂时删除scale(也不要除以stretchx/stretchy),问题就会变得非常明显。然后,当您申请 scale 时,考虑到 stretchx/stretchytranslate 内的划分,它显然对这个问题没有任何作用。

标签: java swing math graphics java-2d


【解决方案1】:

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

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

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

将此与preConcatenate进行比较:

以一种不太常用的方式将 AffineTransform Tx 连接到此 AffineTransform Cx,以便 Tx 修改相对于绝对像素空间而不是相对于现有用户空间的坐标变换。 Cx 已更新以执行组合转换。用更新的变换Cx' 变换一个点p 相当于先用原始变换Cx 变换p,然后用Tx 变换结果,如下所示:Cx'(p) = Tx(Cx(p))

scaletranslate 方法实际上是 concatenate。让我们在 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

【讨论】:

  • 如果围绕对象中心旋转而不是缩放,这会起作用吗?
  • @DimitriOgnibene,我不确定您的附加(轮换)问题究竟是什么意思。它可能有效,也可能无效,具体取决于您的目标和代码。您可能应该提供更多详细信息以获得更具体的答案。
猜你喜欢
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多