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

如何修复Javax.swing中缩放图像出现的透明线条问题?

解决低分辨率游戏画面放大后精灵出现随机透明线条的问题

问题分析

你的代码存在几个关键问题,直接导致了精灵图上的随机透明线条:

  1. EDT线程阻塞:游戏循环直接在GamePanel构造函数中启动,占据了Swing的事件调度线程(EDT),导致界面绘制逻辑无法正常执行,引发绘制时序混乱。
  2. Graphics资源未释放:每次调用buffer.getGraphics()后没有调用dispose()释放资源,长期运行会造成图形资源泄漏,引发异常绘制。
  3. 重复创建精灵实例:每次Draw()都重新调用getSubimage生成精灵,额外开销加剧了绘制异常的概率。
  4. 重绘过于频繁:Thread.sleep(3)对应约333FPS的刷新率,远超普通显示器的60FPS,导致重绘请求积压,出现绘制错误。

修复后的代码

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Window extends JFrame {
    private int width;
    private int height;
    public Window(String name) {
        this.width = 1280;
        this.height = 720;
        this.setTitle(name);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(width, height);
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setVisible(true);
    }
}

public class Run {
    public static void main(String[] args) {
        // 在EDT线程中初始化Swing组件,避免线程冲突
        SwingUtilities.invokeLater(() -> {
            try {
                new GamePanel();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

public class GamePanel extends JPanel implements Runnable {
    private static final int BASE_WIDTH = 320;
    private static final int BASE_HEIGHT = 180;
    private static final int SCALE = 4;
    private static final int TARGET_WIDTH = BASE_WIDTH * SCALE;
    private static final int TARGET_HEIGHT = BASE_HEIGHT * SCALE;

    private BufferedImage buffer;
    private AffineTransformOp scaleOp;
    private BufferedImage sprite; // 提前缓存精灵实例
    private Thread gameThread;

    public GamePanel() throws IOException {
        Window frame = new Window("Game");
        frame.add(this);
        frame.setVisible(true);

        // 初始化缓冲画布
        buffer = new BufferedImage(BASE_WIDTH, BASE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        
        // 初始化缩放操作
        AffineTransform at = new AffineTransform();
        at.scale(SCALE, SCALE);
        scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        
        // 一次性加载并缓存精灵
        BufferedImage spriteMap = ImageIO.read(new File("res/image.png"));
        sprite = spriteMap.getSubimage(0, 8, 16, 16);

        // 启动独立的游戏循环线程
        gameThread = new Thread(this);
        gameThread.start();
    }

    @Override
    public void run() {
        // 固定60FPS的游戏循环,匹配普通显示器刷新率
        long targetFrameTime = 1000 / 60;
        while (gameThread != null) {
            long startTime = System.currentTimeMillis();
            
            drawToBuffer();
            repaint();
            
            // 计算并补足帧间隔时间
            long elapsedTime = System.currentTimeMillis() - startTime;
            if (elapsedTime < targetFrameTime) {
                try {
                    Thread.sleep(targetFrameTime - elapsedTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void drawToBuffer() {
        Graphics2D g = buffer.createGraphics();
        try {
            // 清空缓冲画布
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, BASE_WIDTH, BASE_HEIGHT);
            // 绘制缓存的精灵
            g.drawImage(sprite, 50, 50, null);
        } finally {
            // 强制释放Graphics资源,避免泄漏
            g.dispose();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (buffer != null && scaleOp != null) {
            // 直接在绘制阶段完成缩放,简化流程
            g.drawImage(buffer, scaleOp, 0, 0);
        }
    }
}

关键修复点说明

  • 分离EDT与游戏线程:用SwingUtilities.invokeLater初始化界面,游戏循环放到独立线程,避免阻塞EDT导致绘制逻辑混乱。
  • 释放图形资源:用try-finally确保每次使用Graphics后都调用dispose(),杜绝资源泄漏。
  • 缓存精灵实例:仅初始化时生成一次精灵对象,避免重复调用getSubimage的额外开销。
  • 固定帧率:将刷新率限制在60FPS,避免过度重绘导致的请求积压。
  • 简化缩放流程:直接在paintComponent中完成缩放绘制,无需提前创建scaledImage,减少内存占用和绘制异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:47:35