这些似乎是需要同时满足的条件才能发生:
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()
方法:
以下是它在 Windows 上以 125% 缩放比例在 Java 22 上呈现的方式 使用
drawImage()
方法:
但是,如果我使用 Java 8,那么使用 drawImage()
与不使用
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 根本不尊重显示器分辨率。因此,即使您的显示器设置为 125%,我认为应用程序也会以 100% 启动。当差异为 125% 与 100% 时,这是可以原谅的,但如果您的显示器设置为 200% 或 250%,并且您启动以 100% 渲染的旧应用程序,差异就会变得非常明显。例如,以下是您的应用程序如何使用旧 JRE 与新 JRE 为我呈现:
像素化问题
假设我们希望保持 125% 的分辨率,那么问题就在于我们如何缩放所有内容。
添加 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;
}
}