java - 第一次绘制时图形上下文未对齐

标签 java swing affinetransform

我一直在为 another question 寻找答案并遇到了一个我以前从未见过的奇怪问题......

基本上,该程序使用 AffineTransform 来提供 Graphics 元素的平移、缩放和旋转,足够简单的东西,以前做过一千次

问题是,当屏幕第一次出现时,输出不在应有的位置,但一旦我触摸其中一个控件(调整其中一个幻灯片),它就会跳到正确的位置。

Slider Slider

根据屏幕截图,Graphics 内容似乎因其他控件的数量而错位。

如果我从 GUI 中删除控件,它会出现在正确的位置(在中心)。如果我调整窗口大小,它不会解决问题,它只会在其中一个 slider 触发 DrawPane 上的 repaint 时修复...

我在输出中添加了诊断,所有值都相同 - 也就是说,当程序首次启动时以及我将所有 slider 值调整为初始值时,它们打印相同的值。

如果我从 AffineTransform 中删除 setRotationsetScale 调用,它不会修复它。如果我删除 setTranslation,则在更新面板之前不会首先绘制正方形(它在屏幕外绘制)

如果我使用 Graphics2D g2d = (Graphics)g; 而不是 g.create(),结果相同(是的,我在 paintComponent 方法退出 ;))

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Parker {

    public static void main(String[] args) {
        new Parker();
    }

    public Parker() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ControlPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ControlPane extends JPanel {

        private JSlider slider; //declare slider
        private DrawPane myPanel; 
        public ControlPane() {
            setLayout(new BorderLayout());
            myPanel = new DrawPane();
            myPanel.setBackground(Color.cyan); //change background color

            slider = new JSlider(SwingConstants.VERTICAL, 0, 400, 100);// restrains the slider from scaling square to 0-300 pixels
            slider.setMajorTickSpacing(20); //will set tick marks every 10 pixels
            slider.setPaintTicks(true); //this actually paints the ticks on the screen

            slider.addChangeListener(
                    new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            myPanel.setScale(slider.getValue()); //Wherever you set the slider, it will pass that value and that will paint on the screen
                        }
                    }
            );

            JSlider rotate = new JSlider(SwingConstants.VERTICAL, 0, 720, 0);
            rotate.setMajorTickSpacing(20); //will set tick marks every 10 pixels
            rotate.setPaintTicks(true); //this actually paints the ticks on the screen

            rotate.addChangeListener(
                    new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
                            JSlider slider = (JSlider) e.getSource();
                            myPanel.setAngle(slider.getValue()); 
                        }
                    }
            );
            add(slider, BorderLayout.WEST);
            add(rotate, BorderLayout.EAST);

            add(myPanel);

            slider.setValue(0);
            slider.setValue(100);
            rotate.setValue(0);

        }

    }

    public class DrawPane extends JPanel {

        private double scale = 1;
        private double angle = 0;

        private final int rectWidth = 20;
        private final int rectHeight = 20;


        @Override
        protected void paintComponent(Graphics g)//paints obj on the screen
        {
            super.paintComponent(g); //prepares graphic object for drawing

            int originX = getWidth() / 2; 
            int originY = getHeight() / 2;

            int xOffset = -(rectWidth / 2);
            int yOffset = -(rectHeight / 2);

            g.setColor(Color.BLACK);
            Graphics2D g2d = (Graphics2D) g.create();
            AffineTransform at = new AffineTransform();
            at.translate(originX, originY);
            g2d.setTransform(at);
            g2d.scale(scale, scale);
            g2d.rotate(Math.toRadians(angle), 0, 0);
            g2d.fillRect(xOffset, yOffset, rectWidth, rectHeight);
            g2d.dispose();

            g.setColor(Color.RED);
            g.drawRect(originX + xOffset, originY + yOffset, rectWidth, rectWidth);
        }

        public void setAngle(double angle) {
            this.angle = angle;
            repaint();
        }

        public void setScale(int scale) {
            // Scaling is normalized so that 1 = 100%
            this.scale = (scale / 100d);
            repaint();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}

基本上,Graphics 上下文似乎在第一次绘制时由于某种原因没有被正确翻译,我不知道为什么......

ps-Windows 7下Java 6和Java 7测试
pps- 我也试过在屏幕可见之前将初始比例和旋转设置为其他值,结果相同

系统属性

awt.toolkit=sun.awt.windows.WToolkit
file.encoding=UTF-8
file.encoding.pkg=sun.io
file.separator=\
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.awt.printerjob=sun.awt.windows.WPrinterJob
java.class.path=C:\DevWork\personal\java\projects\wip\SystemProperties\build\classes
java.class.version=51.0
java.endorsed.dirs=C:\Program Files\Java\jdk1.7.0_51\jre\lib\endorsed
java.ext.dirs=C:\Program Files\Java\jdk1.7.0_51\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
java.home=C:\Program Files\Java\jdk1.7.0_51\jre
java.io.tmpdir=C:\Users\shane\AppData\Local\Temp\
java.runtime.name=Java(TM) SE Runtime Environment
java.runtime.version=1.7.0_51-b13
java.specification.name=Java Platform API Specification
java.specification.vendor=Oracle Corporation
java.specification.version=1.7
java.vendor=Oracle Corporation
java.vendor.url=http://java.oracle.com/
java.vendor.url.bug=http://bugreport.sun.com/bugreport/
java.version=1.7.0_51
java.vm.info=mixed mode
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vm.specification.name=Java Virtual Machine Specification
java.vm.specification.vendor=Oracle Corporation
java.vm.specification.version=1.7
java.vm.vendor=Oracle Corporation
java.vm.version=24.51-b03
os.arch=amd64
os.name=Windows 7
os.version=6.1
path.separator=;
sun.arch.data.model=64
sun.cpu.endian=little
sun.cpu.isalist=amd64
sun.desktop=windows
sun.io.unicode.encoding=UnicodeLittle
sun.java.command=systemproperties.SystemProperties
sun.java.launcher=SUN_STANDARD
sun.jnu.encoding=Cp1252
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
sun.os.patch.level=Service Pack 1
user.country=AU
user.dir=C:\DevWork\personal\java\projects\wip\SystemProperties
user.home=C:\Users\shane
user.language=en
user.name=shane
user.script=
user.timezone=
user.variant=

已更新

如果我用...

g2d.translate(originX, originY);
g2d.scale(scale, scale);
g2d.rotate(Math.toRadians(angle), 0, 0);

代替 AffineTransform,它工作正常。我注意到,无论我如何使用 AffineTransform(仅平移、仅旋转、仅缩放)我似乎都得到了相同的结果

更新了示例图片

显示调整框架大小的示例...

ZoomSpinResizeFrame

nb 矩形的最后位置偏移是下面提到的 MouseListener 的结果

但是,如果我将 MosueListener 添加到 DrawPane(直接在类内或通过 myPanel 实例从外部添加)并调用 repaintmouseClicked 上,它重新正确对齐 :P

已更新

如果我翻译一个Shape,使用下面的

AffineTransform at = new AffineTransform();
at.translate(originX, originY);
at.scale(scale, scale);
at.rotate(Math.toRadians(angle), 0, 0);
g2d.setTransform(at);

Rectangle2D rect = new Rectangle2D.Double(xOffset, yOffset, rectWidth, rectHeight);
Shape shape = at.createTransformedShape(rect);

System.out.println(rect.getBounds());
System.out.println(shape.getBounds());

结果输出符合预期,但(图形)输出仍然错误...

最佳答案

与传递给 paintComponent() 的图形上下文关联的 AffineTransform 并不总是恒等变换。由于不明确的原因,m12 条目最初和调整封闭框架的大小后的值为 38.0。简单地说,可以修改 g.create() 提供的副本。

Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = g2d.getTransform();
at.translate(originX, originY);
g2d.setTransform(at);
g2d.scale(scale, scale);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.fillRect(xOffset, yOffset, rectWidth, rectHeight);
g2d.dispose();

附录:正如@MadProgrammer 观察到的,“如果我从 GUI 中删除控件,它会出现在……中间。”事实上,观察到的水平偏移恰好是 BorderLayout.WESTslider 的首选宽度。我怀疑在调整大小后调整原点以满足 Component#paintAll()契约(Contract)更容易:“图形上下文的原点,它的 (0, 0) 坐标点,是这个组件的左上角。”

关于java - 第一次绘制时图形上下文未对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23507422/

相关文章:

java - 旋转位矩阵

java - 如何在 GWT 中进行两个模块的通信

java - 我想知道扫描器代码在 Java 中是如何工作的

java - 如何在 Java Web 应用程序中正确配置服务器、XML 和 JSP 文件?

java - Selenium webdriver click() 在 jenkins 中失败

java - 如何设置单击 JLabel 的文本字段?

java - Applet.stop() 何时被调用?

math - 两个三角形之间的相似变换

java - Java 中的布局问题 - 在 JFrame 底部设置 20% 不透明面板

java - 使用 AffineTransform 从四边形变换为四边形?