【问题标题】:How to Draw an BufferedImage to a JPanel如何将 BufferedImage 绘制到 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 中看到的图像。
  • 唯一的问题是我还没有让它运行。实际运行的部分是我遇到问题的部分。不过,我会在未来牢记这一点。谢谢你的信息。

标签: java swing


【解决方案1】:
  1. 我鼓励使用javax.swing.Timer 来控制帧速率,而不是不受控制的循环
  2. 一旦计时器“滴答”,您需要增加当前帧,获取要渲染的当前图像并在JPanel 上调用repaint
  3. 使用Graphics#drawImage 渲染图像。

看...

更多详情

您的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 SwingPerforming Custom Painting 了解更多详情
  • 我认为他的意思是你需要调用 super.paintComponent(g);首先
  • 哦,是的。抱歉,我很快就从记忆中消失了,我同意。
  • 你有一个实例变量保存在你的类中作为“私有 BufferedImage 图像;”理想情况下,您的 run 方法会更新 this 的值,然后调用“repaint();”我会在答案中添加一些关于此的内容。
猜你喜欢
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-18
  • 2017-09-16
  • 1970-01-01
相关资源
最近更新 更多