如何将 BufferedImage 绘制到 JPanel

How to Draw an BufferedImage to a JPanel

我正在尝试使用某种绘制方法将精灵图像绘制到名为 AnimationPanel 的 JPanel 的子class。我创建了一个 Spritesheet class,它可以生成一个包含 sheet 中所有精灵的 BufferedImage[]。在实现 Runnable 的 AnimationPanel class 中,我从在 AnimationPanel 构造函数中实例化的 spritesheet 获取 BufferedImage[]。我希望能够遍历这个数组并将每个精灵显示到屏幕上。我该怎么做?这是我的 AnimationPanel 和 Spritesheet classes.

AnimationPanel

package com.kahl.animation;

import javax.swing.JPanel;

public class AnimationPanel extends JPanel implements Runnable {

//Instance Variables
private Spritesheet sheet;
private int currentFrame;
private Thread animationThread;
private BufferedImage image;

public AnimationPanel(Spritesheet aSheet) {
    super();
    sheet = aSheet;
    setPreferredSize(new Dimension(128,128));
    setFocusable(true);
    requestFocus();

}

public void run() {
    BufferedImage[] frames = sheet.getAllSprites();
    currentFrame = 0;
    while (true) {
        frames[currentFrame].draw(); //some implementation still necessary here
        currentFrame++;
        if (currentFrame >= frames.length) {
            currentFrame = 0;
        }
    }
}

public void addNotify() {
    super.addNotify();
    if (animationThread == null) {
        animationThread = new Thread(this);
        animationThread.start();
    }
}

}

Spritesheet

package com.kahl.animation;

import java.awt.image.BufferedImage;
import java.imageio.ImageIO;
import java.io.IOException;
import java.io.File;

public class Spritesheet {

//Instance Variables
private String path;
private int frameWidth;
private int frameHeight;
private int framesPerRow;
private int frames;
private BufferedImage sheet = null;

//Constructors
public Spritesheet(String aPath,int width,int height,int fpr, int numOfFrames) {

    path = aPath;
    frameWidth = width;
    frameHeight = height;
    framesPerRow = fpr;
    frames = numOfFrames;

    try {
        sheet = ImageIO.read(getClass().getResourceAsStream());
    } catch (IOException e) {
        e.printStackTrace();
    }

}

//Methods

public int getHeight() {
    return frameWidth;
}

public int getWidth() {
    return frameWidth;
}

public int getFramesPerRow() {
    return framesPerRow;
}

private BufferedImage getSprite(int x, int y, int h, int w) {
    BufferedImage sprite = sheet.getSubimage(x,y,h,w);
}

public BufferedImage[] getAllSprites() {
    BufferedImage[] sprites = new BufferedImage[frames];
    int y = 0;
    for (int i = 0; i < frames; i++) {
        x = i * frameWidth;
        currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth);
        sprites.add(currentSprite);
    }
    return sprites;

}

}
  1. 我鼓励使用 javax.swing.Timer 来控制帧速率,而不是不受控制的循环
  2. 一旦计时器"ticks",你需要增加当前帧,获取当前要渲染的图像并在JPanel
  3. 上调用repaint
  4. 使用Graphics#drawImage渲染图像。

见...

了解更多详情

您的 Spritesheet class 存在一系列级联问题,除了它实际上无法编译之外,您还存在从某些方法返回错误值的问题,并且依靠更好计算的值...

我不得不修改你的代码这么多,我不记得大部分了

public int getHeight() {
    return frameWidth;
}

public BufferedImage[] getAllSprites() {
    BufferedImage[] sprites = new BufferedImage[frames];
    int y = 0;
    for (int i = 0; i < frames; i++) {
        x = i * frameWidth;
        currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth);
        sprites.add(currentSprite);
    }
    return sprites;

}

作为两个主要例子脱颖而出...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestSpriteSheet {

    public static void main(String[] args) {
        new TestSpriteSheet();
    }

    public TestSpriteSheet() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Spritesheet spritesheet;
        private BufferedImage currentFrame;
        private int frame;

        public TestPane() {
            spritesheet = new Spritesheet("/Sheet02.gif", 240, 220);
            Timer timer = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    currentFrame = spritesheet.getSprite(frame % spritesheet.getFrameCount());
                    repaint();
                    frame++;
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(240, 220);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (currentFrame != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                int x = (getWidth() - currentFrame.getWidth()) / 2;
                int y = (getHeight() - currentFrame.getHeight()) / 2;
                g2d.drawImage(currentFrame, x, y, this);
                g2d.dispose();
            }
        }

    }

    public class Spritesheet {

//Instance Variables
        private String path;
        private int frameWidth;
        private int frameHeight;
        private BufferedImage sheet = null;
        private BufferedImage[] frameImages;

//Constructors
        public Spritesheet(String aPath, int width, int height) {

            path = aPath;
            frameWidth = width;
            frameHeight = height;

            try {
                sheet = ImageIO.read(getClass().getResourceAsStream(path));
                frameImages = getAllSprites();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        public BufferedImage getSprite(int frame) {
            return frameImages[frame];
        }

//Methods
        public int getHeight() {
            return frameHeight;
        }

        public int getWidth() {
            return frameWidth;
        }

        public int getColumnCount() {
            return sheet.getWidth() / getWidth();
        }

        public int getRowCount() {
            return sheet.getHeight() / getHeight();
        }

        public int getFrameCount() {
            int cols = getColumnCount();
            int rows = getRowCount();
            return cols * rows;
        }

        private BufferedImage getSprite(int x, int y, int h, int w) {
            BufferedImage sprite = sheet.getSubimage(x, y, h, w);
            return sprite;
        }

        public BufferedImage[] getAllSprites() {
            int cols = getColumnCount();
            int rows = getRowCount();
            int frameCount =  getFrameCount();
            BufferedImage[] sprites = new BufferedImage[frameCount];
            int index = 0;
            System.out.println("cols = " + cols);
            System.out.println("rows = " + rows);
            System.out.println("frameCount = " + frameCount);
            for (int row = 0; row < getRowCount(); row++) {
                for (int col = 0; col < getColumnCount(); col++) {
                    int x = col * getWidth();
                    int y = row * getHeight();
                    System.out.println(index + " " + x + "x" + y);
                    BufferedImage currentSprite = getSprite(x, y, getWidth(), getHeight());
                    sprites[index] = currentSprite;
                    index++;
                }
            }
            return sprites;

        }

    }
}

记住,动画是随时间变化的幻觉。您需要在动画的每一帧之间提供延迟,既要足够长以供用户识别,又要足够短以使动画看起来流畅。

在上面的示例中,我使用了 100 毫秒,只是作为一个任意值。可以使用更像 1000 / spritesheet.getFrameCount() 的东西,这将为整个动画留出整整一秒的时间(一秒内的所有帧)。

根据您的需要,您可能需要为更长或更短的动画使用不同的值

下面是一些将图像绘制到 JPanel 的通用代码。调用此方法来绘制 JPanel 组件。

public void paintComponent (Graphics g)
{ 
     super.paintComponent(g);
     //I would have image be a class variable that gets updated in your run() method
     g.drawImage(image, 0, 0, this); 
} 

我也可以将 运行() 修改为如下所示:

public void run() {
  BufferedImage[] frames = sheet.getAllSprites();
  currentFrame = 0;
  while (true) {
    image = frames[currentFrame];
    this.repaint(); //explicitly added "this" for clarity, not necessary.
    currentFrame++;
    if (currentFrame >= frames.length) {
        currentFrame = 0;
    }
  }
}

关于只重绘部分组件,有点复杂

public void run() {
  BufferedImage[] frames = sheet.getAllSprites();
  currentFrame = 0;
  while (true) {
    image = frames[currentFrame];
    Rectangle r = this.getDirtyRect();
    this.repaint(r); 
    currentFrame++;
    if (currentFrame >= frames.length) {
        currentFrame = 0;
    }
  }
}

public Rectangle getDirtyRect() {
  int minX=0; //calculate smallest x value affected
  int maxX=0; //calculate largest x value affected
  int minY=0; //calculate smallest y value affected
  int maxY=0; //calculate largest y value affected 
  return new Rectangle(minX,minY,maxX,maxY);
}