You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Swing跟随系统缩放时避免图片缩放及自定义Icon异常解决求助

问题描述

我正在梳理对应方案以确定最优解决路径,一年多前我曾提出过关于Java中图片画质的问题,当时处理了项目其他模块需求,现在对相关技术有了更多积累后又回到了这个问题上。

我发现Windows系统缩放设置为125%时,会导致Java Swing程序中的图片画质下降。我的目标是理清Java Swing响应系统缩放的逻辑,调研过程中我找到了JVM启动参数-Dsun.java2d.uiScale=1.0,可以强制程序无论系统缩放比例为多少都以100%比例运行。

使用该参数时图片画质表现优秀,按钮上16x16的小图标相比跟随系统缩放的场景模糊度低很多,效果对比如下:

  • 125%缩放时效果:图标模糊,边缘有明显锯齿
  • 100%缩放时效果:图标清晰,边缘锐利无失真

但我仍希望程序可以跟随系统缩放,方便视力不佳的用户使用,上述强制100%缩放的方案仅作为最终兜底选项。
因此我的核心问题是:如何让Swing程序界面跟随系统缩放的同时,避免内部图片被缩放?

我在相关技术讨论中了解到,缩放系数定义在Graphics对象的AffineTransform中,基于该信息我已经可以控制图片画质,但遇到了两个尚未解决的问题:

  1. 程序首次加载时,按钮上的图标不会显示,只有鼠标hover之后才会出现,窗口最大化或调整大小后图标又会消失,请问这是Graphics2D的相关问题吗?
  2. 自定义的MyIcon类渲染的图标位置不像原生new ImageIcon(...)那样居中,我尝试添加at.setToTranslation(1.25, 1.25);调整位置,但效果不明显,有没有办法让图标像100%缩放时一样居中?

我在125%系统缩放下使用MyIcon类的实现效果画质比默认缩放的效果好很多,除了上述两个问题之外已经满足需求。

以下是复现问题的最小代码,要复现位置问题需要使用16x16的png图片:

import java.awt.GridBagLayout;
import common.Functions;
import java.awt.GridBagConstraints;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import java.awt.geom.AffineTransform;
import java.net.URL;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class TestFrame extends JFrame {
    
    public static void main(String[] args) {
        TestFrame tf = new TestFrame();
        tf.setLocationRelativeTo(null);
        tf.setVisible(true);
    }
    
    public TestFrame() {
        setSize(300, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Icon i = new MyIcon(Functions.class.getResource("/link.png"));
        GridBagLayout gridBagLayout = new GridBagLayout();
        gridBagLayout.columnWidths = new int[]{0, 0};
        gridBagLayout.rowHeights = new int[]{0, 0};
        gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE};
        gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE};
        getContentPane().setLayout(gridBagLayout);
        
        JButton btnNewButton = new JButton("Test Button");
        btnNewButton.setPreferredSize(new Dimension(150, 30));
        btnNewButton.setIcon(i);
        btnNewButton.setFocusPainted(false);
        GridBagConstraints gbc_btnNewButton = new GridBagConstraints();
        gbc_btnNewButton.gridx = 0;
        gbc_btnNewButton.gridy = 0;
        getContentPane().add(btnNewButton, gbc_btnNewButton);
    }
    
    public class MyIcon extends ImageIcon{
        
        public MyIcon(String filename) {
            super(filename);
        }
        
        public MyIcon(URL resource) {
            super(resource);
        }

        @Override
        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
            
            Graphics2D g2d = (Graphics2D) g.create();
            AffineTransform at = new AffineTransform();
            at.setToTranslation(1.25, 1.25);
            g2d.setTransform(at);
            if(getImageObserver() == null) {
               g2d.drawImage(getImage(), x, y, c);
            } else {
               g2d.drawImage(getImage(), x, y, getImageObserver());
            }
            g2d.dispose();
        }
        
    }
}
解决方案

两个问题的根源都在paintIcon方法里直接覆盖了Graphics2D的全局变换矩阵,完全丢掉了原有的坐标映射、缩放偏移参数,导致渲染坐标错误、重绘触发逻辑异常。

问题1修复(图标不显示/消失)

不要直接调用setTransform覆盖全局变换,先读取原Graphics2D的缩放系数,再反向抵消缩放对图片的影响,保留原有平移参数即可:

@Override
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2d = (Graphics2D) g.create();
    // 读取当前系统缩放系数
    AffineTransform originalTransform = g2d.getTransform();
    double scaleX = originalTransform.getScaleX();
    double scaleY = originalTransform.getScaleY();
    // 反向抵消缩放,保证图片按原始像素渲染
    AffineTransform noScaleTransform = AffineTransform.getTranslateInstance(x, y);
    noScaleTransform.scale(1/scaleX, 1/scaleY);
    g2d.setTransform(noScaleTransform);
    // 绘制图片时坐标填0即可,因为已经在变换里处理了x/y偏移
    if(getImageObserver() == null) {
       g2d.drawImage(getImage(), 0, 0, c);
    } else {
       g2d.drawImage(getImage(), 0, 0, getImageObserver());
    }
    g2d.dispose();
}

修改后重绘逻辑会正常识别图标渲染区域,不会出现加载不显示、调整窗口消失的问题。

问题2修复(图标居中)

你之前硬编码的1.25偏移只适配125%缩放,换成动态计算的占位尺寸即可:
重写getIconWidth和getIconHeight方法,返回适配缩放后的尺寸,这样Swing布局计算时会自动给图标预留正确的居中位置:

@Override
public int getIconWidth() {
    // 读取系统缩放系数,返回缩放后的占位宽度
    GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getDefaultScreenDevice().getDefaultConfiguration();
    double scale = gc.getDefaultTransform().getScaleX();
    return (int) Math.round(super.getIconWidth() * scale);
}

@Override
public int getIconHeight() {
    GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getDefaultScreenDevice().getDefaultConfiguration();
    double scale = gc.getDefaultTransform().getScaleY();
    return (int) Math.round(super.getIconHeight() * scale);
}

如果还有细微的像素偏移,可以在noScaleTransform的平移参数里加上微调值,适配不同按钮的内边距。

补充优化

如果要适配多屏不同缩放的场景,可以在绘制时从Component对象读取当前屏幕的缩放系数,不要用全局默认值,避免跨屏拖动时渲染异常。

内容的提问来源于stack exchange,提问作者Kyle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 10:09:03