如何解决Windows DPI缩放对Swing动态渲染图片的影响?
解决Swing JTable高DPI下化学结构图片模糊问题
问题根源
你当前代码获取的是默认屏幕的GraphicsConfiguration变换,这无法反映组件所在显示器的实际DPI缩放比例——Java 11的每显示器DPI缩放是基于组件所在的绘图上下文,而非全局屏幕设置。另外,-Dsun.java2d.uiScale=1参数会强制禁用系统DPI缩放,这是导致scale值始终为1的关键原因。
解决方案
- 从绘图上下文获取真实缩放比例:直接从
paintIcon传入的Graphics对象转换为Graphics2D,获取当前组件的实际缩放变换。 - 按物理像素渲染图片:用逻辑尺寸乘以缩放比例得到物理像素尺寸,让Indigo渲染对应大小的高分辨率图片,避免Swing自动缩放导致模糊。
- 修正JVM参数:移除
-Dsun.java2d.uiScale=1,保留-Dsun.java2d.uiScale.enabled=true,或使用Java 9+支持的-Dsun.java2d.dpiaware=true确保DPI感知开启。 - 缓存对应缩放的图片:根据缩放比例和逻辑尺寸缓存渲染结果,避免重复计算。
修改后的代码
public synchronized void paintIcon(Component c, Graphics g, int x, int y) { Image image = getImage(); if (image == null) { return; } Insets insets = ((Container) c).getInsets(); x = insets.left; y = insets.top; // 从当前绘图上下文获取真实缩放比例 Graphics2D g2d = (Graphics2D) g; AffineTransform transform = g2d.getTransform(); float scaleX = (float) transform.getScaleX(); float scaleY = (float) transform.getScaleY(); // 计算组件的逻辑尺寸(用户看到的尺寸) int logicalWidth = c.getWidth() - x - insets.right; int logicalHeight = c.getHeight() - y - insets.bottom; // 计算需要渲染的物理像素尺寸(避免Swing缩放) int renderWidth = Math.max(16, (int) (logicalWidth * scaleX)); int renderHeight = Math.max(16, (int) (logicalHeight * scaleY)); logger.debug("Rendering Chemical Structure image: Logical W: " + logicalWidth + " H: " + logicalHeight + " ScaleX: " + scaleX + " ScaleY: " + scaleY + " Render W: " + renderWidth + " H: " + renderHeight); // 仅当渲染尺寸变化时重新生成图片 if (renderWidth != width || renderHeight != height) { width = renderWidth; height = renderHeight; // 按物理像素尺寸渲染 indigo.setOption("render-image-size", renderWidth, renderHeight); // 根据缩放比例调整DPI(Windows默认96DPI,缩放后为96*scale) indigo.setOption("image-resolution", (int) (96 * Math.max(scaleX, scaleY))); image = renderImage(); setImage(image); } // 按逻辑尺寸绘制高分辨率图片,Swing会自动适配DPI ImageObserver io = getImageObserver(); g.drawImage(image, x, y, logicalWidth, logicalHeight, io == null ? c : io); } private Image renderImage() { try { if (structureData == null || mol == null) { return null; } if (!mol.hasCoord()) { mol.layout(); } byte[] imageData = renderer.renderToBuffer(mol); ByteArrayInputStream bis = new ByteArrayInputStream(imageData); return ImageIO.read(bis); } catch (IOException e) { logger.catching(e); return null; } }
额外注意事项
- 确保你的JTable和所在容器没有被强制设置
setScaleX(1)或类似禁用缩放的代码。 - 测试多显示器场景:如果组件在不同DPI的显示器间拖动,
paintIcon会自动获取新的缩放比例并重新渲染图片。
内容的提问来源于stack exchange,提问作者beginner_
相关产品推荐
相关产品推荐

