Java Graphics2D.drawImage() 仅在 Windows 125% 缩放上渲染模糊图像,并且仅在 Java 8 之后的 Java 版本上渲染模糊图像

问题描述 投票:0回答:1
我知道 Java 9 修复了 Java 8 中 Swing 存在的屏幕缩放和分辨率问题。但是,我遇到的情况是,如果在 Java 8 上运行,完全相同的代码会清晰呈现,但如果使用任何版本的 Java 9 则呈现模糊及之后(包括最新的 LTS 版本 Java 21 和 Java 22)。问题是,只有当 Windows 显示设置设置为 125% 缩放时,才会在 Windows 上发生这种情况。

这些似乎是需要同时满足的条件才能发生:

    Windows 设置为使用 125% 缩放
  • Java 9 或更新的 JRE 用于运行代码
  • 图形是专门通过
  • drawImage()
    方法渲染的。
如果我直接在

Graphics2D

 对象上绘制线条和文本(由 
JComponent
paint(Graphics g)
 函数提供给我的对象),那么即使在 Windows 125% 缩放的 Java 9+ 上,一切也能正常工作。但是,如果我渲染完全相同的图形但使用 
drawImage()
 方法,那么它会变得模糊。

因此,如果我在上述任何条件下在 Java 8 上运行代码(Windows 设置为 100% 或 125% 缩放,直接使用

Graphics

 对象或使用 
drawImage()
,并不重要),一切都会清晰呈现(即,没有任何类型的锯齿或抖动或发生任何情况)。

如果我在 Windows

not 的任何版本的 Java 9+ 上以 125% 缩放比例运行我的代码,一切都会清晰地呈现(与 Java 8 相同)。如果我在任何版本的 Java 9+ 上运行我的代码,Windows 的缩放比例为 125%,但直接使用 Graphics

 对象(而不是 
drawImage()
),那么一切都会清晰地呈现。只有当我使用
drawImage()
方法时,事情才会向南发展。

我有一个最小的可重现示例:

这是它在 Windows 的 Java 22 上以 125% 缩放比例呈现的方式,而不是使用

drawImage() 方法:

Minimum Reproducible Example showing clearly rendered graphics以下是它在 Windows 上以 125% 缩放比例在 Java 22 上呈现的方式 使用

drawImage()

方法:

但是,如果我使用 Java 8,那么使用 Minimum Reproducible Example showing blurrily rendered graphicsdrawImage() 与不使用

drawImage()

之间没有任何区别。

这是代码:
public class MainWindow extends JFrame implements ActionListener {
    
    DrawPanel dp;

    public static void main(String[] args) {
        new MainWindow();
    }
    
    public MainWindow() {
        super("Graphics Test");
        setLayout(new BorderLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JButton button = new JButton("Redraw");
        button.addActionListener(this);
        add(button, BorderLayout.PAGE_START);
        
        JPanel center = new JPanel(new FlowLayout());
        dp = new DrawPanel();
        center.add(dp);
        add(center, BorderLayout.CENTER);
        
        pack();
        setLocationRelativeTo(null);
        setVisible(true);       
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        dp.drawOnImage = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
        dp.repaint();
    }
}

还有自定义组件。请注意,按住 CTRL 将触发它使用 drawImage() 进行渲染,而如果不按住 CTRL,它将直接使用 Graphics 对象:

public class DrawPanel extends JComponent {
    
    Graphics2D gSave;
    BufferedImage bi;
    boolean drawOnImage = false;
    
    public DrawPanel() {
    }
    
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }

    @Override
    public void paint(Graphics gr) {
        Graphics2D g = (Graphics2D) gr;
        gSave = g;
        
        if (drawOnImage) {
            bi = new BufferedImage(getSize().width, getSize().height, BufferedImage.TYPE_INT_ARGB);
            g = bi.createGraphics();
        }
        
        Font font = new Font("SansSerif", Font.BOLD, 36);
        g.setFont(font);
        g.setColor(Color.BLACK);
        g.drawString("Test String", 5, 40);
        g.setStroke(new BasicStroke(6.0001f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, null, 0f));
        g.drawRect(100, 70, 100, 100);
        font = new Font("SansSerif", Font.PLAIN, 16);
        g.setFont(font);
        g.drawString("More Text to test the resolution", 5, 200);
        
        Rectangle rect2 = new Rectangle(300, 40, 100, 100);
        g.rotate(Math.toRadians(45));
        g.draw(rect2);
        
        if (drawOnImage) {
            g.dispose();
            gSave.drawImage(bi, null, 0, 0);
        }
    }
}

所以问题是这样的:
为什么旧版本的 Java 没有问题,但新版本的 Java 似乎性能下降了?是否存在 
drawImage()

所做的不同的事情,我可以减轻以不产生此结果? (对于为什么我要这样做以及为什么这是必要的这个不可避免的问题(例如,如果它有效,为什么不直接在

Graphics
对象上绘制而不是使用

drawImage()

方法?) ,我会注意到,我正在编写的实际代码尝试在鼠标拖动时绘制半透明重叠形状,这就是为什么使用使用

BufferedImage
drawImage()
方法,也许有一种不同的方法可以做到这一点。不需要使用
drawImage()
,但我还没有找到它。)
    

为什么旧版本的 Java 没有问题,但新版本的 Java 似乎性能下降了?
java windows swing java-8 java-21
1个回答
0
投票
我认为答案是:(非常)旧版本的 Java 根本不尊重显示器分辨率。因此,即使您的显示器设置为 125%,我认为应用程序也会以 100% 启动。当差异为 125% 与 100% 时,这是可以原谅的,但如果您的显示器设置为 200% 或 250%,并且您启动以 100% 渲染的旧应用程序,差异就会变得非常明显。

例如,以下是您的应用程序如何使用旧 JRE 与新 JRE 为我呈现:

像素化问题Comparison of GraphicsTest in Java 1.7 vs Java 16 假设我们希望保持 125% 的分辨率,那么问题就在于我们如何缩放所有内容。

默认情况下,您从 Swing 接收的 Graphics2D 具有已缩放至 1.25 的 AffineTransform。这就是为什么当您在没有 BufferedImage 的情况下绘制绘图时,它“正常工作”。

添加 BufferedImage 后事情会变得更加复杂。您要求 BufferedImage 为 400x400。这就是窗口的“虚拟大小”。但在 125% 分辨率下,400x400px 的窗口实际上占据了 500x500px。所以你的图像需要是 500x500px 才能渲染得很好。

解决方案

我们需要一个 500x500 像素的图像。 这是您的应用程序的修改后的副本,应该可以更好地工作。这考虑了目的地的变换/比例因子。

(有很多空间可以讨论设计,但为了简洁起见,这是一个简洁的修复。)

import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; public class MainWindow extends JFrame implements ActionListener { DrawPanel dp; public static void main(String[] args) { new MainWindow(); } public MainWindow() { super("Graphics Test"); setLayout(new BorderLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Redraw"); button.addActionListener(this); add(button, BorderLayout.PAGE_START); JPanel center = new JPanel(new FlowLayout()); dp = new DrawPanel(); center.add(dp); add(center, BorderLayout.CENTER); pack(); setLocationRelativeTo(null); setVisible(true); } @Override public void actionPerformed(ActionEvent e) { dp.drawOnImage = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK; dp.repaint(); } } class DrawPanel extends JComponent { boolean drawOnImage = false; public DrawPanel() { } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } @Override public void paint(Graphics gr) { Graphics2D g = (Graphics2D) gr; if (drawOnImage) { ScaledBufferedImage bi = new ScaledBufferedImage(getSize().width, getSize().height, BufferedImage.TYPE_INT_ARGB, Math.sqrt(g.getTransform().getDeterminant())); Graphics2D imgG = bi.createGraphics(); imgG.setRenderingHints(g.getRenderingHints()); paintImage(imgG); imgG.dispose(); g.drawImage(bi, 0, 0, getSize().width, getSize().height, 0, 0, bi.getWidth(), bi.getHeight(), null); } else { paintImage(g); } } private void paintImage(Graphics2D g) { Font font = new Font("SansSerif", Font.BOLD, 36); g.setFont(font); g.setColor(Color.BLACK); g.drawString("Test String", 5, 40); g.setStroke(new BasicStroke(6.0001f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, null, 0f)); g.drawRect(100, 70, 100, 100); font = new Font("SansSerif", Font.PLAIN, 16); g.setFont(font); g.drawString("More Text to test the resolution", 5, 200); Rectangle rect2 = new Rectangle(300, 40, 100, 100); g.rotate(Math.toRadians(45)); g.draw(rect2); } } class ScaledBufferedImage extends BufferedImage { final int virtualWidth, virtualHeight; final double scaleFactor; public ScaledBufferedImage(int width, int height, int imageType, double scaleFactor) { super( (int) Math.round(width * scaleFactor), (int) Math.round(height * scaleFactor), imageType); virtualHeight = height; virtualWidth = width; this.scaleFactor = scaleFactor; } public Graphics2D createGraphics() { Graphics2D returnValue = super.createGraphics(); returnValue.scale(scaleFactor, scaleFactor); return returnValue; } }

© www.soinside.com 2019 - 2024. All rights reserved.