动态更新JPanel背景不起作用

时间:2013-11-23 04:37:19

标签: java swing jpanel bufferedimage japplet

从JFilechooser读取图像后,我试图逐个读取图像的像素,并在按顺序方式延迟一段时间后将其显示给JPanel。无法更新JPanel的背景。

public class ImageMain extends JFrame implements ActionListener {

/**
 * 
 */
private static final long serialVersionUID = 2916361361443483318L;
private JFileChooser fc = null;
private JMenuItem item1, item2;
private BufferedImage image = null;
private JPanel panel = null;
private int width = 0;
private int height = 0;
private BorderLayout card;
private Container contentPane;
//private int loopcount = 0;
//private int counter = 0;

public ImageMain() {
    JFrame frame = new JFrame("Image Extraction Tool");
    frame.setExtendedState(Frame.MAXIMIZED_BOTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    contentPane = frame.getContentPane();
    panel = new JPanel();
    card = new BorderLayout();
    panel.setLayout(card);
    panel.setBackground(Color.white);

    JMenuBar menuBar = new JMenuBar();
    JMenu menu = new JMenu("Menu");
    menuBar.add(menu);
    item1 = new JMenuItem("Browse an image");
    item2 = new JMenuItem("Exit");

    item1.addActionListener(this);
    item2.addActionListener(this);

    menu.add(item1);
    menu.add(item2);
    frame.setJMenuBar(menuBar);

    contentPane.add(panel);
    frame.pack();
    frame.setVisible(true);
}

public static void main(String[] args) {

    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                ImageMain img = new ImageMain();

            }
        });
    } catch (InvocationTargetException | InterruptedException e) {
        e.printStackTrace();
    }

}

@Override
public void actionPerformed(ActionEvent e) {

    if (e.getSource() == item1) {
        if (fc == null)
            fc = new JFileChooser();
        int retVal = fc.showOpenDialog(null);
        if (retVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            try {
                image = ImageIO.read(file);
                height = image.getHeight();
                width = image.getWidth();

                // final int[][] pixelData = new int[height * width][3];

                // int[] rgb;

                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        System.out.println(i + " " + j);
                        Color c = new Color(image.getRGB(j, i));
                        panel.setBackground(c);
                        panel.invalidate();
                        panel.validate();
                        panel.repaint();
                    }
                }

            } catch (IOException e1) {
                System.out.println("IO::" + e1.getMessage());
            } catch (Exception e1) {
                System.out.println("Exception::" + e1.getMessage());
            }
        }
    }
    if (e.getSource() == item2) {
        System.exit(0);
    }
}}

在ActionPerformed中,我通过读取RGB值获得了Color对象,然后我坚持将它们显示给JApplet。如果有更好的方法来实现这一点,欢迎提出建议。

提前致谢。

1 个答案:

答案 0 :(得分:2)

主要问题是您在事件调度线程的上下文中执行长时间运行的任务,事务调度线程负责处理重绘请求等。

@Override
public void actionPerformed(ActionEvent e) {

    //...

                // Nothing will be updated until after the 
                // actionPerformed method exists
                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        System.out.println(i + " " + j);
                        Color c = new Color(image.getRGB(j, i));
                        panel.setBackground(c);
                        panel.invalidate();
                        panel.validate();
                        panel.repaint();
                    }
                }

您遇到的另一个问题是,您只需要在EDT的上下文中修改UI的状态

根据您的具体需求,您可以使用SwingWorker,这样您就可以在EDT上下文中更新UI时处理背景中的像素,但是,因为SwingWorker巩固它的更新,你可能会错过颜色变化。

更好的解决方案可能是使用java.swing.Timer,这将允许您在指定期间触发更新,这些更新在EDT的上下文中触发。

有关详细信息,请参阅Concurrency in Swing ...

更新了示例

为了绘制像素,你需要一些东西来绘制它们。现在,你可以简单地将你想要绘制的每个像素添加到一个数组中,并在每次需要重新绘制组件时循环该数组,但这有点贵......

相反,拥有某种类型的后备缓冲区会更简单,在上面绘制像素,然后将该缓冲区绘制到组件上,这应该更快。

基本上,它允许您提供预期图像的高度和宽度,然后根据需要提供每个像素。

public class ImagePane extends JPanel {

    private BufferedImage img;

    public ImagePane() {
    }

    public void reset(int width, int height) {
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        revalidate();
    }

    public void reset() {
        img = null;
        revalidate();
    }

    public void setPixelAt(int x, int y, int pixel) {
        img.setRGB(x, y, pixel);
    }

    @Override
    public Dimension getPreferredSize() {
        return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        if (img != null) {

            int x = (getWidth() - img.getWidth()) / 2;
            int y = (getHeight() - img.getHeight()) / 2;
            g2d.drawImage(img, x, y, this);

        }
        g2d.dispose();
    }
}

请查看Performing Custom Painting了解更多详情......

然后你需要一些方法来处理原始图像并更新图像面板......现在,根据更新的要求,我会使用SwingWorker,原因是{{1可以缓存传回EDT的内容,这允许后台线程继续处理和缓存输出,直到EDT(和系统)准备好处理它......

SwingWorker

基本上,这个public class PixelExposerWorker extends SwingWorker<Void, Pixel> { private final BufferedImage img; private final ImagePane imagePane; private final List<Point> points; public PixelExposerWorker(BufferedImage img, ImagePane imagePane) { this.img = img; this.imagePane = imagePane; points = new ArrayList<>(img.getWidth() * img.getHeight()); for (int x = 0; x < img.getWidth(); x++) { for (int y = 0; y < img.getHeight(); y++) { points.add(new Point(x, y)); } } } @Override protected void process(List<Pixel> chunks) { System.out.println("Publish " + chunks.size()); for (Pixel pixel : chunks) { imagePane.setPixelAt(pixel.getX(), pixel.getY(), pixel.getColor()); } imagePane.repaint(); } @Override protected Void doInBackground() throws Exception { int pixelCount = (int) (points.size() * 0.005); while (!points.isEmpty()) { for (int count = 0; count < pixelCount && !points.isEmpty(); count++) { int index = (int) (Math.random() * (points.size() - 1)); Point p = points.remove(index); Pixel pixel = new Pixel(p.x, p.y, img.getRGB(p.x, p.y)); publish(pixel); } Thread.yield(); } return null; } } 会构建SwingWorker个“像素点”,它会使用该列表从列表中随机删除点,生成虚拟ListPixel回到EDT进行处理。工人将一次处理大约0.5%的像素,这意味着工作总是试图将一堆像素发送回EDT,而不是一次发送一个像素。

最后,publish允许其他线程运行(希望EDT能够自行更新)

650x975的图像大约需要1m和10s才能完全渲染

完整的代码......

yield