动态更新 JPanel 背景不起作用



从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显示它们。如果有更好的方法来实现这一点,建议是受欢迎的。

提前谢谢。

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

@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的上下文中触发的。

有关更多详细信息,请参阅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();
    }
}

查看"执行自定义绘画"以了解更多详细信息。。。

然后你需要一些方法来处理原始图像并更新图像面板。。。现在,根据更新的要求,我将使用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的图像需要大约1米和10秒才能完全渲染

完整的代码。。。

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

最新更新