如何将 BufferedImage 绘制到 JPanel
Posted
技术标签:
【中文标题】如何将 BufferedImage 绘制到 JPanel【英文标题】:How to Draw an BufferedImage to a JPanel 【发布时间】:2015-03-12 01:25:19 【问题描述】:我正在尝试使用某种绘图方法将精灵图像绘制到我称为 AnimationPanel 的 JPanel 子类中。我创建了一个 Spritesheet 类,它可以生成一个包含工作表中所有精灵的 BufferedImage[]。在实现 Runnable 的 AnimationPanel 类中,我从 AnimationPanel 构造函数中实例化的 spritesheet 中获取 BufferedImage[]。我希望能够遍历这个数组并将每个精灵显示到屏幕上。我该怎么做?这是我的 AnimationPanel 和 Spritesheet 类。
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) 为了尽快获得更好的帮助,请发布MCVE(最小完整可验证示例)或SSCCE(简短、自包含、正确示例)。严格来说,一个 MCVE 只能有一个源文件和一个public
类。它还需要一个main(string[])
方法来运行它。 2) 例如,获取图像的一种方法是热链接到在this Q&A 中看到的图像。
唯一的问题是我还没有让它运行。实际运行的部分是我遇到问题的部分。不过,我会在未来牢记这一点。谢谢你的信息。
【参考方案1】:
-
我鼓励使用
javax.swing.Timer
来控制帧速率,而不是不受控制的循环
一旦计时器“滴答”,您需要增加当前帧,获取要渲染的当前图像并在JPanel
上调用repaint
使用Graphics#drawImage
渲染图像。
看...
Painting in AWT and Swing Performing Custom Painting How to use Swing TimersGraphics#drawImage(Image, int, int, ImageObserver)
更多详情
您的Spritesheet
类存在一系列级联问题,除了它实际上不会编译之外,还有一些问题是您从某些方法返回错误值并依赖于更好计算的值。 ..
我不得不修改你的代码,大部分我都不记得了
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()
的东西,这将为整个动画留出整整一秒的时间(一秒内的所有帧)。
您可能需要为较长或较短的动画使用不同的值,具体取决于您的需要
【讨论】:
感谢您花时间写下所有这些。我以前从未真正做过这样的事情,所以我的代码有一些问题我并不感到惊讶。 请记住,动画是随时间变化的错觉。除了其他一些事情之外,您的代码缺少“时间”。上面的例子使用了大约 10fps(每个 from 之间有 100 毫秒的延迟),事实上,考虑到只有 6 帧动画,我们可能可以使用更像 166 的帧,这取决于你想要每个动画的周期数第二... 在您的代码中,之前的图像是从屏幕上移除的吗?在使用您的代码一段时间后,我能够在屏幕上显示精灵,但动画只是将它们叠加在一起。 忘记打电话给super.paintComponent
?
对不起,我昨晚要评论,是的,就是这样。【参考方案2】:
这里有一些用于将图像绘制到 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);
我还可以将 run() 修改为如下所示:
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);
【讨论】:
@MadProgrammer,这可能超出了我的知识范围。你这是什么意思? 您未能满足超类的要求并破坏了绘制方法调用链。该示例可能会导致意外和不良的油漆故障,请参阅 Painting in AWT and Swing 和 Performing Custom Painting 了解更多详情 我认为他的意思是你需要调用 super.paintComponent(g);首先 哦,是的。抱歉,我很快就从记忆中消失了,我同意。 你有一个实例变量保存在你的类中作为“私有 BufferedImage 图像;”理想情况下,您的 run 方法会更新 this 的值,然后调用“repaint();”我会在答案中添加一些关于此的内容。以上是关于如何将 BufferedImage 绘制到 JPanel的主要内容,如果未能解决你的问题,请参考以下文章
旋转 BufferedImage 时如何产生清晰的绘制结果?