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

Java Graphics如何无缩放渲染图像?Windows高DPI适配问题

问题描述
  • 环境:4K分辨率显示器,Windows系统设置150%缩放
  • 现有Java代码结构:

    Main类代码

    public static void main(String[] args) throws IOException {
            JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
            frame.setPreferredSize(new Dimension(displayMode.getWidth(), displayMode.getHeight()));
            frame.setUndecorated(true);
            frame.add(new Background());
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
    }
    

    Background类代码

    public class Background extends JPanel implements ActionListener {
        public Background() throws IOException {
            backGround = ImageIO.read(getClass().getClassLoader().getResourceAsStream("background.jpg"));
            
            setBackground(Color.BLACK);
            addMouseListener(new MyMouseListener(this));
            addKeyListener(new MyKeyboardListener(this));
            setFocusable(true);
            requestFocusInWindow();
            setOpaque(true);
    }
    

    paintComponent多线程绘制逻辑

    @Override
        protected void paintComponent(Graphics g) {
    for (Map.Entry<String, Map<BaseShip, Integer>> player : playersShips.entrySet()) {
    
                Thread thread = new Thread(new Runnable() {
    @Override
                    public void run() {
                        DisplayMode displayMode = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
                        BufferedImage bufferedImage = new BufferedImage( displayMode.getWidth(), displayMode.getHeight(), BufferedImage.TYPE_INT_ARGB);
                        Graphics graphics = bufferedImage.getGraphics();
                        Map<BaseShip, Integer> value = new HashMap<>(player.getValue());
                        Iterator<Map.Entry<BaseShip, Integer>> iterator = value.entrySet().iterator();
                        while (iterator.hasNext()) {
                            Map.Entry<BaseShip, Integer> entry = iterator.next();
                            BaseShip baseShip = entry.getKey();
                            if (!baseShip.isKilled()) {
                                baseShip.paint(graphics);
                            } else {
                                iterator.remove();
                            }
                        }
    
                        graphics.dispose();
                        bufferedImages.put(player.getKey(), bufferedImage);
                        player.setValue(value);
                    }
                });
                threads.add(thread);
                thread.start();
            }
            for (Thread thread : threads) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (Map.Entry<String, BufferedImage> bufferedImage : bufferedImages.entrySet()) {
                g.drawImage(bufferedImage.getValue(), 0, 0, null);
            }
    }
    
  • 核心问题:
    BufferedImage按DisplayMode创建为3840x2160,但JPanel的paint方法实际以2600x1440分辨率绘制内容,经窗口拉伸后导致画质损失。尝试过移除最大化设置、手动设置尺寸、使用Graphics2D等方法均无效,仅系统缩放调至100%时正常,但不可行;放弃多线程则运行卡顿,需兼顾多线程与无缩放渲染。
解决方案

1. 开启Java高DPI感知

强制Java忽略系统缩放,按原生分辨率渲染,避免拉伸模糊:

// 程序启动时设置
System.setProperty("sun.java2d.uiScale", "1.0");
// 或在JVM启动参数中添加:-Dsun.java2d.uiScale=1.0

2. 修正BufferedImage尺寸计算

不要硬编码DisplayMode尺寸,改用面板实际物理像素尺寸:
在paintComponent中获取系统缩放比例,计算对应的物理分辨率:

Graphics2D g2d = (Graphics2D) g;
double scale = g2d.getTransform().getScaleX();
int physicalWidth = (int) (getWidth() * scale);
int physicalHeight = (int) (getHeight() * scale);

用该尺寸创建BufferedImage,确保与实际渲染像素匹配。

3. 优化多线程绘制逻辑

  • 用线程池替代频繁创建线程,减少开销:
// 提前初始化线程池,复用线程
private ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    List<Future<?>> futures = new ArrayList<>();
    Graphics2D g2d = (Graphics2D) g;
    double scale = g2d.getTransform().getScaleX();
    int physicalWidth = (int) (getWidth() * scale);
    int physicalHeight = (int) (getHeight() * scale);

    for (Map.Entry<String, Map<BaseShip, Integer>> player : playersShips.entrySet()) {
        Future<?> future = executor.submit(() -> {
            BufferedImage bufferedImage = new BufferedImage(physicalWidth, physicalHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics graphics = bufferedImage.createGraphics();
            // 应用缩放比例,保证绘制内容与物理像素对齐
            ((Graphics2D)graphics).scale(scale, scale);
            
            Map<BaseShip, Integer> value = new HashMap<>(player.getValue());
            Iterator<Map.Entry<BaseShip, Integer>> iterator = value.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<BaseShip, Integer> entry = iterator.next();
                BaseShip baseShip = entry.getKey();
                if (!baseShip.isKilled()) {
                    baseShip.paint(graphics);
                } else {
                    iterator.remove();
                }
            }

            graphics.dispose();
            bufferedImages.put(player.getKey(), bufferedImage);
            player.setValue(value);
        });
        futures.add(future);
    }

    // 等待所有绘制任务完成
    for (Future<?> future : futures) {
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    // 按面板逻辑尺寸绘制BufferedImage,避免拉伸
    for (Map.Entry<String, BufferedImage> entry : bufferedImages.entrySet()) {
        g2d.drawImage(entry.getValue(), 0, 0, getWidth(), getHeight(), null);
    }
}
  • 必须调用super.paintComponent(g),确保面板正常刷新,避免绘制残留。

4. 调整窗口初始化逻辑

去掉硬编码的PreferredSize,直接按物理屏幕尺寸设置窗口:

public static void main(String[] args) throws IOException {
    // 开启高DPI感知
    System.setProperty("sun.java2d.uiScale", "1.0");
    
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    Background background = new Background();
    frame.add(background);
    
    // 按物理屏幕尺寸设置窗口
    DisplayMode displayMode = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
    frame.setSize(displayMode.getWidth(), displayMode.getHeight());
    
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}

内容的提问来源于stack exchange,提问作者Ярослав

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:37:44