Swing Java2D 在 Java 11 和现代 iMac

问题描述

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

我今天又去玩了一些,但它不起作用。我一头雾水。我知道我有一个工作示例——某处。但是在我的驱动器上搜索,我的实验都没有奏效。我开始尝试让它再次工作。

最终我发现了问题。它是 JDK 11 和我的新 iMac 的某种组合。上次我在这方面工作是在我的旧 Mac 上(我可能使用过也可能没有使用过 JDK 11,我不记得了)。

这是我的应用程序在 JDK 11 中的样子

enter image description here

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

enter image description here

如果您使用代码,您会发现在 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,i,350);
                g2.draw(line);
                line = new Line2D.Double(0,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 的认变换缩放为 2。

在这代码中,如果你打印出认的转换,在 JDK 11(在我的环境中)你会得到:

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

在 JDK 8 上,您可以获得身份。

AffineTransform[[1.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,不是那么多。

我试过这个,我第一次将认变换 (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();
        }

所以这不是解决方案。

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

编辑:

继续。

因此,在屏幕上创建和显示的窗口是我在 Java 中要求的大小的 2 倍。我通过创建窗口的屏幕截图来检查这一点。

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

在 JDK 11 中,可以看到他们试图通过直接使用 AffineTransform 将其向上移动。

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

JDK 8 中的问题是由于 Transform,屏幕坐标不再与模型坐标匹配。当您绘制一个 400x400 的框时,在屏幕上绘制的是 400x400 乘以 2(由于变换)。但是鼠标坐标不在屏幕坐标中。原始鼠标在 0-400 范围内,而不是 0-800 范围内。因此,您不能再使用图形变换将鼠标坐标转换回模型坐标。幕后隐藏着恶作剧和阴谋诡计。简单地说,转换不是规范的。这就是为什么我的鼠标变得一团糟。

此外,我在另一台分辨率较低的 Mac 上运行了该示例,完全没有问题。 JDK 11 下的认转换是身份。

解决方法

我没有看到 JDK 8 和 11 之间的任何区别,但问题是 Graphics2D 上下文已经转换为匹配屏幕配置,所以你需要做的地方 g2d.setTransform(at) g2d.transform(at)。然后一切都会如你所愿。 (我在 OS X 11.4 上使用 JDK 17)

所以 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,1.0,0.0]]
Final:AffineTransform[[2.0,0.0]]
,

为了后代,我想发布最终代码,以防其他人想要一个工作示例,而不是试图将原始代码与讨论并列。

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,i,350);
                g2.draw(line);
                line = new Line2D.Double(0,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);
        }
    }
}