【问题标题】:Problems with BufferedImage in JavaJava中的BufferedImage问题
【发布时间】:2026-01-15 23:20:03
【问题描述】:

我正在制作一个游戏(如文明),它具有不同类型的图块,我想将它们渲染为图像。我有 9 个不同的 16x16 png 图像要加载(称为 Con1、Con2 等),这是我的图像加载代码:(img[] 是我的 BufferedImage 数组)

public void loadImages(){
        for(int i = 0; i < 9; i++){
            try {
                img[i] = ImageIO.read(this.getClass().getResource("Con"+i+".png"));
            }catch (Exception ex) {
                System.out.println("Missing Image");
                ex.printStackTrace();
            }
        }
    }

然后我使用以下代码绘制这些图像:(t[][] 是我的图块类型数组)

public void paint(Graphics g){
        if(loop){
            BufferedImage B = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics r = B.getGraphics();
            for (int x = 0; x < WIDTH; x++){
                for (int y = 0; y < HEIGHT; y++){
                    if(i[x][y] == 0){
                        if (t[x][y] == 0){
                            g.drawImage(img[0], x, y, this);
                        }
                        else if(t[x][y] == 1){
                            g.drawImage(img[1], x, y, this);
                        }
                        else if(t[x][y] == 3){
                            g.drawImage(img[3], x, y, this);
                        }
                        else if(t[x][y] == 4){
                            g.drawImage(img[4], x, y, this);
                        }
                        else if(t[x][y] == 5){
                            g.drawImage(img[5], x, y, this);
                        }
                    }
                    r.fillRect(x*SCALE, y*SCALE, SCALE, SCALE);
                }
            }

            g.drawImage(B, 0, 22, this);
        }
    }

我的问题是当我运行它时它没有正确显示。我得到这张图片:

在窗口的左上角闪烁。我应该看到的是与上面的左上角部分相似的图像(被海洋包围的陆地),除了大到足以填满窗口。这是一些可运行的代码:(我认为如果没有所需的图像,该代码将无法运行,但我希望能在为大家提供图像方面提供一些帮助。)

//imports
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class MCVCon extends JFrame implements KeyListener, MouseListener{
    //setting up variables
    public BufferedImage[] img = new BufferedImage[9];
    private final int WIDTH = 50, HEIGHT = 50;
    private boolean loop = false;
    private int SCALE = 16;
    int t[][] = new int[WIDTH][HEIGHT]; //terrain type (since I took out the terrain generation it is set to 0 or ocean)
    public MCVCon(){
        //creating the window
        super("Conqueror");
        setSize(SCALE*WIDTH, SCALE*HEIGHT+22);
        setVisible(true);
        requestFocusInWindow();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        loadImages();
        loop = true;
        while(true){
            this.repaint();
            //delay for repaint
            try{
                Thread.sleep(50);
            }
            catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
    //load images
    public void loadImages(){
        for(int i = 0; i < 9; i++){
            try {
                img[i] = ImageIO.read(this.getClass().getResource("Con"+i+".png"));
            }catch (Exception ex) {
                System.out.println("Missing Image");
                ex.printStackTrace();
            }
        }
    }
    //paint the images
    public void paint(Graphics g){
        if(loop){
            BufferedImage B = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics r = B.getGraphics();
            for (int x = 0; x < WIDTH; x++){
                for (int y = 0; y < HEIGHT; y++){
                    if (t[x][y] == 0){
                        g.drawImage(img[0], x, y, this);
                    }
                    else if(t[x][y] == 1){
                        g.drawImage(img[1], x, y, this);
                    }
                    r.fillRect(x*SCALE, y*SCALE, SCALE, SCALE);
                }
            }

            g.drawImage(B, 0, 22, this);
        }
    }
    //run the program
    public static void main(String[] args) {
        new MCVCon();
    }
    //necessary overrides
    @Override
    public void keyTyped(KeyEvent e) {
    }

    @Override
    public void keyPressed(KeyEvent e) {
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }
}

我想知道问题可能是什么。提前致谢。

【问题讨论】:

  • 看起来您在其中缺少缩放因子,但如果没有更多信息很难说,尤其是原始图像的外观。旁注——你不是在一个活跃的绘画方法中做这个绘图,比如JPanel的paintComponent方法,是吗?如果是这样,它的效率非常低。
  • 为了更好的帮助,创建并发布一个有效的minimal reproducible example,一个但完整的演示程序,我们可以自己编译和运行。请阅读链接。
  • @HovercraftFullOfEels 我没有使用 paintComponent 方法,我使用的是 JFrame 并且只是为每个像素绘制一个矩形,关于这个主题,我有一个用于像素图形的 SCALE 最终变量,但是我不知道如何放大我加载的图像。你能解释一下如何做到这一点或将我链接到一个示例吗?
  • 如果您直接在 JFrame 上绘图,听起来您并没有充分发挥 Swing 绘画的最大优势。
  • g.drawImage(img[1], x, y, this); 不是绘制到BufferedImage,而是直接绘制到组件的Graphics 上下文,但是你可以在顶部绘制缓冲图像,所以我有不知道发生了什么:P

标签: java image bufferedimage


【解决方案1】:

所以,我看了你的代码,没有简单的说法,但它是一团糟,复杂的问题使得很难隔离任何一个问题的根源,除了说,他们都互相补充。

让我们从画开始...

您正在直接在框架上绘画。出于多种原因,通常不鼓励这样做。

让我们从JFrame 不是一个单一的组件开始,它由多个复合组件组成

这使得直接绘制具有固有的危险性,因为任何一个子组件都可以在没有调用框架的paint 方法的情况下绘制。

直接在框架上绘画也意味着您在绘画时不考虑框架的边框/装饰,它们会添加到窗口的可见区域,但请稍等...

setSize(SCALE*WIDTH, SCALE*HEIGHT+22);

建议您尝试对此进行补偿,但这是“猜测”工作,因为装饰可能会根据系统配置占用或多或少的空间

最后,*容器实际上并不是双缓冲的。

“但我正在画自己的缓冲区”你说 - 但你不是

您创建一个 BufferdImage 并将其分配为 Graphics 上下文 t r

BufferedImage B = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics r = B.getGraphics();

但是当你绘制任何东西时,你使用的是g,这是传递给paint方法的Graphics上下文

g.drawImage(img[0], x, y, this);

这也是非常低效的,因为每次调用 paint 时,您都在创建一个新的 BufferedImage,这需要时间来创建,占用内存并在本地对象变为垃圾收集过程时造成额外的压力几乎可以立即处置

甚至不要让我开始“主绘制循环”

你遇到的下一个问题是你没有虚拟和现实世界空间的概念。

您使用t 制作了您的世界的虚拟地图,该地图保存了有关应将哪个图块用于给定 x/y 坐标的信息,但您从未将其映射到现实世界,而是准确地绘制每个图块在相同的像素 x/y 位置,这意味着它们现在重叠,图块具有宽度和高度,这意味着它们在绘制到现实世界时需要偏移。

最后,我认为您的实际问题是关于缩放。您可以通过多种方式应用缩放,您可以在加载磁贴时预先缩放它们,这使您可以很好地控制它们的“缩放方式”,但会将您锁定在单个缩放中。

您可以改为维护从主列表生成的缩放图块列表,如果比例发生变化,可以更新该列表

或者您可以直接缩放Graphics 上下文。

现在,我根据您的代码创建了一个简单的示例,对上述大部分内容进行了更正。它创建了一系列 10x10 像素的随机颜色矩形,而不是平铺,但概念是一样的。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class MCVCon extends JFrame {
    //setting up variables

    public MCVCon() {
        //creating the window
        super("Conqueror");
        add(new GamePane());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MCVCon frame = new MCVCon();
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    //necessary overrides

    public class GamePane extends JPanel {

        public BufferedImage[] img = new BufferedImage[9];
        private final int width = 5, height = 5;
        private int scale = 16;
        int t[][] = new int[width][height]; //terrain type (since I took out the terrain generation it is set to 0 or ocean)
        Color[] colors = new Color[]{
            Color.RED,
            Color.BLUE,
            Color.CYAN,
            Color.DARK_GRAY,
            Color.GRAY,
            Color.GREEN,
            Color.LIGHT_GRAY,
            Color.MAGENTA,
            Color.ORANGE,
            Color.PINK,
            Color.YELLOW
        };
        int tileHeight = 10;
        int tileWidth = 10;

        public GamePane() {
            loadImages();

            Random rnd = new Random();
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    int value = rnd.nextInt(9);
                    System.out.println(value + "- " + colors[value]);
                    t[x][y] = value;
                }
            }

            Timer timer = new Timer(50, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(width * tileWidth * scale, height * tileHeight * scale);
        }

        public void loadImages() {
            for (int i = 0; i < 9; i++) {
                try {
                    img[i] = new BufferedImage(tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB);
                    Graphics2D g2d = img[i].createGraphics();
                    g2d.setColor(colors[i]);
                    g2d.fill(new Rectangle(0, 0, tileWidth, tileHeight));
                    g2d.dispose();
                } catch (Exception ex) {
                    System.out.println("Missing Image");
                    ex.printStackTrace();
                }
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setTransform(AffineTransform.getScaleInstance(scale, scale));
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    g2d.drawImage(img[t[x][y]], x * tileWidth, y * tileHeight, this);
                }
            }
            g2d.dispose();
        }

    }
}

【讨论】:

  • 感谢您的回答,我非常感谢有见地的 cmets。
  • 我试用了您的代码,它运行良好!我真的很感谢你的帮助,我终于可以继续这个项目了。非常感谢。
  • 所以我正在编写代码,我在使用最初在 while(true) 循环中调用的“更新”函数时遇到了一些困难;但是,我不太清楚该循环放在哪里。我尝试将它放在主类中,但我意识到这是因为更新方法在不同的类中。我想知道您是否知道解决此问题的方法。
  • Timer 现在充当“主循环” - 有关详细信息,请参阅 How to use Swing Timers