【问题标题】:dynamically update JPanel background does't work动态更新 JPanel 背景不起作用
【发布时间】:2013-11-23 04:37:19
【问题描述】:

从 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。如果有更好的方法来实现这一点,欢迎提出建议。

提前致谢。

【问题讨论】:

  • setBackground(...) 方法将整个 JPanel 的背景颜色设置为传入的颜色。它不会“逐像素”设置任何内容。此外,鉴于 Swing 的线程规则,唯一会显示的颜色将是传递给 setBackground(...) 的最后一种颜色。

标签: java swing jpanel bufferedimage japplet


【解决方案1】:

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

@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,这样做的原因是SwingWorker可以缓存传递回 EDT 的内容,这允许后台线程继续处理和缓存输出,直到 EDT(和系统)准备好处理它...

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构建了一个“像素点”的List,它使用列表从列表中随机删除点,生成一个虚拟的Pixelpublish返回EDT进行处理。工作人员一次将处理大约 0.5% 的像素,这意味着工作总是试图将“一堆”像素发送回 EDT,而不是一次一个。

最后,它将yield 允许其他线程运行(并希望 EDT 自行更新)

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

还有完整的代码……

import core.util.StopWatch;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;

public class PixelShower implements ActionListener {

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

    public PixelShower() {
        JFrame frame = new JFrame("Image Extraction Tool");
        frame.setExtendedState(Frame.MAXIMIZED_BOTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        contentPane = frame.getContentPane();
        panel = new ImagePane();
        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.setVisible(true);
    }

    public static void main(String[] args) {

        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    PixelShower img = new PixelShower();
                }
            });
        } 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);

                    panel.reset(image.getWidth(), image.getHeight());
                    PixelExposerWorker worker = new PixelExposerWorker(image, panel);
                    worker.execute();
                } catch (IOException e1) {
                    e1.printStackTrace();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        if (e.getSource() == item2) {
            System.exit(0);
        }
    }

    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();
        }

    }

    public class Pixel {

        private int x;
        private int y;
        private int color;

        public Pixel(int x, int y, int color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public int getColor() {
            return color;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

    }

    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 {
            StopWatch sw = StopWatch.newInstance().start();
            int pixelCount = (int) (points.size() * 0.005);
            System.out.println("pixelCount = " + pixelCount + "; " + points.size());
            while (!points.isEmpty()) {
                StopWatch sw1 = StopWatch.newInstance().start();
                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();
            }
            System.out.println("Took " + sw.stop());
            return null;
        }

    }
}

【讨论】:

  • 我是新手。我想在我的解决方案中使用 javax.swing.Timer。我需要在我的源代码中进行哪些更改?提前致谢。
  • 你想达到什么目的?
  • 我想以幻灯片(全屏)方式显示图像像素(逐个像素)。谢谢
  • 这实际上引发了更多问题。您是“滑入”图像(例如从一侧)、淡入还是简单地一次显示一个像素?
猜你喜欢
  • 2014-08-02
  • 1970-01-01
  • 2020-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多