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,提问作者Ярослав
相关产品推荐
相关产品推荐

