在 Java 11 和现代 iMac 上摆动 Java2D

Swing Java2D on Java 11 and a modern iMac

不久前,我编写并使用了一个 Zoom to Mouse Swing 面板,它可以处理突出显示、平移、鼠标缩放、选择等。非常好。

今天又去玩了一会儿,还是不行。我很困惑。我知道我有一个可行的例子——在某个地方。但是搜索我的驱动器,none 我的实验成功了。我开始尝试让它重新工作。

最后我发现了问题所在。它是 JDK 11 和我的新 iMac 的某种组合。我上次做这个是在我的老 Mac 上(我可能用过也可能没用过 JDK 11,我不记得了)。

这是我的应用在 JDK 11 时的样子

这是 JDK 8 的样子(这是应该的样子)

如果您使用代码,您会发现在 JDK 11 下显然有某种(2X?)缩放,但在 JDK 8 下没有。我不知道不知道它是否试图补偿我机器上的大显示屏,或者发生了什么。

但是您尝试在 JDK 11 下进行缩放或平移,发现它没有保持在鼠标中心,以及跟踪突出显示错误等等。

如何在 JDK 11 下正常工作?它是 Mac 唯一的东西吗?或者 Mac 带有“在剧院大小的监视器中开车”的东西?

代码如下:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

public class TestPanel {

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI() {
        JFrame f = new JFrame("Test Zoom");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new BorderLayout());
        TestPanelZoom p = new TestPanelZoom();
        f.add(p, BorderLayout.CENTER);
        f.setPreferredSize(new Dimension(400, 500));
        f.pack();
        f.setVisible(true);
    }

    private static class TestPanelZoom extends JPanel {

        boolean hilighted = false;
        private int hiliteX = -1;
        private int hiliteY = -1;
        boolean selected = false;
        private int selectX = -1;
        private int selectY = -1;

        private AffineTransform at = new AffineTransform();

        public TestPanelZoom() {
            setBackground(Color.WHITE);
            setForeground(Color.BLACK);
            addMouseAdapter();
            at.setToIdentity();
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setTransform(at);
            paintPanel(g2d);
            g2d.dispose();
        }

        private void paintPanel(Graphics2D g2) {
            g2.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            Color c = g2.getColor();
            g2.setColor(Color.WHITE);
            g2.fill(getBounds());
            g2.setColor(c);

            for (int i = 0; i < 400; i += 50) {
                Line2D line = new Line2D.Double(i, 0, i, 350);
                g2.draw(line);
                line = new Line2D.Double(0, i, 350, i);
                g2.draw(line);
            }
            if (hilighted) {
                Rectangle2D rect = new Rectangle2D.Double(hiliteX * 50 + 5, hiliteY * 50 + 5, 40, 40);
                g2.setColor(Color.GREEN);
                g2.fill(rect);
            }
            if (selected) {
                Rectangle2D rect = new Rectangle2D.Double(selectX * 50 + 10, selectY * 50 + 10, 30, 30);
                g2.setColor(Color.RED);
                g2.fill(rect);
            }
        }

        private void addMouseAdapter() {
            MouseAdapter ma = new MouseAdapter() {
                int lx, ly;

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    AffineTransform t = new AffineTransform();
                    double delta = 1 + 0.05f * e.getPreciseWheelRotation();
                    int x = e.getX();
                    int y = e.getY();
                    t.translate(x, y);
                    t.scale(delta, delta);
                    t.translate(-x, -y);
                    t.concatenate(at);
                    at = t;
                    revalidate();
                    repaint();
                }

                @Override
                public void mousePressed(MouseEvent e) {
                    lx = e.getX();
                    ly = e.getY();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    update(e);
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    update(e);
                }

                @Override
                public void mouseClicked(MouseEvent e) {
                    Point2D srcPt = getSrcPoint(e);
                    selected = true;
                    selectX = (int) (srcPt.getX() / 50);
                    selectY = (int) (srcPt.getY() / 50);
                    revalidate();
                    repaint();
                }

                @Override
                public void mouseMoved(MouseEvent e) {
                    Point2D srcPt = getSrcPoint(e);
                    hilighted = true;
                    hiliteX = (int) (srcPt.getX() / 50);
                    hiliteY = (int) (srcPt.getY() / 50);
                    revalidate();
                    repaint();
                }

                public void update(MouseEvent e) {
                    Point2D srcPt = new Point2D.Double(e.getX(), e.getY());
                    Point2D lastPt = new Point2D.Double(lx, ly);
                    try {
                        at.inverseTransform(srcPt, srcPt);
                        at.inverseTransform(lastPt, lastPt);
                    } catch (NoninvertibleTransformException noninvertibleTransformException) {
                        throw new RuntimeException(noninvertibleTransformException);
                    }
                    double dx = srcPt.getX() - lastPt.getX();
                    double dy = srcPt.getY() - lastPt.getY();
                    at.translate(dx, dy);
                    lx = e.getX();
                    ly = e.getY();
                    revalidate();
                    repaint();
                }

                public Point2D getSrcPoint(MouseEvent e) {
                    Point2D srcPt = new Point2D.Double(e.getX(), e.getY());
                    try {
                        at.inverseTransform(srcPt, srcPt);
                    } catch (NoninvertibleTransformException ex) {
                        throw new RuntimeException(ex);
                    }
                    return srcPt;
                }

            };
            addMouseListener(ma);
            addMouseMotionListener(ma);
            addMouseWheelListener(ma);
        }

    }
}

编辑:

经过更多探索,无论出于何种原因,在 JDK 11 下,Graphics2D 的默认 t运行sform 被缩放了 2。

在此代码中,如果您在 JDK 11(在我的环境中)上打印出默认的 t运行sform,您将得到:

AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]

在 JDK 8,您获得身份。

AffineTransform[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            System.out.println(g2d.getTransform());
            g2d.setTransform(at);
            paintPanel(g2d);
            g2d.dispose();
        }

我最初使用我自己的原创 AffineTransform 强制它识别。在 JDK 8 上,这是一个 NOP,JDK 11,不是那么多。

我尝试了这个,我第一次将默认的 t运行sform (at) 设置为图形上下文的“默认”(使用其基本世界观与强制我自己的).这使网格大小合适,但它弄乱了光标位置。

        // I remove setting `at` up above, and setting it to identity also.
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            if (at == null) {
                at = g2d.getTransform();
            }
            g2d.setTransform(at);
            paintPanel(g2d);
            g2d.dispose();
        }

所以这不是解决方案。

也许有一种方法可以通过利用上下文的默认 t运行sform 来使其工作,但我还没有弄清楚。我必须假设默认的 t运行sform 是流动的,不仅仅是 JDK 到 JDK,而是机器对机器。它甚至可能在多显示器情况下发生变化。

编辑:

继续。

因此,创建并显示在屏幕上的 window 是我在 Java 中要求的大小的 2 倍。我通过创建 Window.

的屏幕截图来检查这一点

在 JDK 8 中,使 Java 中的 100 像素线在显示器上变成 200 像素线的机制在我的代码中并不明显。

在 JDK 11 中,可以看到他们试图通过直接使用 AffineT运行sform 来提升它。

同样,在JDK 8中,鼠标坐标被缩放回Java坐标系。因此,如果您在屏幕上放置一个 400x400 的框并将鼠标从一个角移动到另一个角,即使该框的实际像素为 800x800,鼠标也只是 运行ges 从 0-400。

JDK8 中的问题是,由于 T运行sform,屏幕坐标不再与模型坐标匹配。当您绘制一个 400x400 的框时,屏幕上绘制的是 400x400 时间 2(由于 t运行sform)。但是鼠标坐标不在屏幕坐标中。原始鼠标在 0-400 运行ge 而不是 0-800 运行ge。因此您不能再使用图形 t运行sform 将鼠标坐标转换回模型坐标。幕后隐藏着恶作剧和诈骗。简单地说,t运行sform 不是规范的。这就是为什么我的鼠标变得一团糟。

另外,我运行另一个Mac分辨率较低的显示器上的样本,完全没有问题。 JDK11下的默认t运行sform是identity.

我没有看到 JDK 8 和 11 之间有任何区别,但问题是 Graphics2D 上下文已经 运行 形成以匹配屏幕配置,所以你在哪里做 g2d.setTransform(at) 你需要做 g2d.transform(at)。然后一切都会如您所愿。 (我 运行 与 JDK 17 在 OS X 11.4)

所以 paintComponent 应该是(我还添加了一些日志记录):

protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            AffineTransform t = g2d.getTransform();
            System.out.println("Base G2D:" + t);
            System.out.println("Ours:" + at);
            g2d.transform(at);
            System.out.println("Final:" + g2d.getTransform());

            paintPanel(g2d);
            g2d.dispose();
        }

我已经在 OpenJDK Runtime Environment 18.9 (build 11.0.2+9)Java(TM) SE Runtime Environment (build 1.8.0_201-b09) 上测试过了。

我现在也用 11.0.11 测试过:

$ rm *.class
$ java -version
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment AdoptOpenJDK-11.0.11+9 (build 11.0.11+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9 (build 11.0.11+9, mixed mode)
$ javac TestPanel.java
$ java TestPanel
Base G2D:AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]
Ours:AffineTransform[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
Final:AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]

出于 post 的诚意,我想要 post 最终代码,以防其他人想要一个工作示例,而不是试图将原始代码与讨论并列。

package pkg;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

public class TestPanel {

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI() {
        JFrame f = new JFrame("Test Zoom");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new BorderLayout());
        TestPanelZoom p = new TestPanelZoom();
        f.add(p, BorderLayout.CENTER);
        f.setPreferredSize(new Dimension(400, 500));
        f.pack();
        f.setVisible(true);
    }

    private static class TestPanelZoom extends JPanel {

        boolean hilighted = false;
        private int hiliteX = -1;
        private int hiliteY = -1;
        boolean selected = false;
        private int selectX = -1;
        private int selectY = -1;

        private AffineTransform at = new AffineTransform();

        public TestPanelZoom() {
            setBackground(Color.WHITE);
            setForeground(Color.BLACK);
            addMouseAdapter();
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            g2d.transform(at);
            paintPanel(g2d);
            g2d.dispose();
        }

        private void paintPanel(Graphics2D g2) {
            g2.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            Color c = g2.getColor();
            g2.setColor(Color.WHITE);
            g2.fill(getBounds());
            g2.setColor(c);

            for (int i = 0; i < 400; i += 50) {
                Line2D line = new Line2D.Double(i, 0, i, 350);
                g2.draw(line);
                line = new Line2D.Double(0, i, 350, i);
                g2.draw(line);
            }
            if (hilighted) {
                Rectangle2D rect = new Rectangle2D.Double(hiliteX * 50 + 5, hiliteY * 50 + 5, 40, 40);
                g2.setColor(Color.GREEN);
                g2.fill(rect);
            }
            if (selected) {
                Rectangle2D rect = new Rectangle2D.Double(selectX * 50 + 10, selectY * 50 + 10, 30, 30);
                g2.setColor(Color.RED);
                g2.fill(rect);
            }
        }

        private void addMouseAdapter() {
            MouseAdapter ma = new MouseAdapter() {
                int lx, ly;

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    AffineTransform t = new AffineTransform();
                    double delta = 1 + 0.05f * e.getPreciseWheelRotation();
                    int x = e.getX();
                    int y = e.getY();
                    t.translate(x, y);
                    t.scale(delta, delta);
                    t.translate(-x, -y);
                    t.concatenate(at);
                    at = t;
                    revalidate();
                    repaint();
                }

                @Override
                public void mousePressed(MouseEvent e) {
                    lx = e.getX();
                    ly = e.getY();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    update(e);
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    update(e);
                }

                @Override
                public void mouseClicked(MouseEvent e) {
                    Point2D srcPt = getSrcPoint(e);
                    selected = true;
                    selectX = (int) (srcPt.getX() / 50);
                    selectY = (int) (srcPt.getY() / 50);
                    revalidate();
                    repaint();
                }

                @Override
                public void mouseMoved(MouseEvent e) {
                    Point2D srcPt = getSrcPoint(e);
                    hilighted = true;
                    hiliteX = (int) (srcPt.getX() / 50);
                    hiliteY = (int) (srcPt.getY() / 50);
                    revalidate();
                    repaint();
                }

                public void update(MouseEvent e) {
                    Point2D srcPt = new Point2D.Double(e.getX(), e.getY());
                    Point2D lastPt = new Point2D.Double(lx, ly);
                    try {
                        at.inverseTransform(srcPt, srcPt);
                        at.inverseTransform(lastPt, lastPt);
                    } catch (NoninvertibleTransformException noninvertibleTransformException) {
                        throw new RuntimeException(noninvertibleTransformException);
                    }
                    double dx = srcPt.getX() - lastPt.getX();
                    double dy = srcPt.getY() - lastPt.getY();
                    at.translate(dx, dy);
                    lx = e.getX();
                    ly = e.getY();
                    revalidate();
                    repaint();
                }

                public Point2D getSrcPoint(MouseEvent e) {
                    Point2D srcPt = new Point2D.Double(e.getX(), e.getY());
                    try {
                        at.inverseTransform(srcPt, srcPt);
                    } catch (NoninvertibleTransformException ex) {
                        throw new RuntimeException(ex);
                    }
                    return srcPt;
                }

            };
            addMouseListener(ma);
            addMouseMotionListener(ma);
            addMouseWheelListener(ma);
        }
    }
}