从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。如果有更好的方法来实现这一点,欢迎提出建议。
提前致谢。
答案 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
个“像素点”,它会使用该列表从列表中随机删除点,生成虚拟List
和Pixel
回到EDT进行处理。工人将一次处理大约0.5%的像素,这意味着工作总是试图将一堆像素发送回EDT,而不是一次发送一个像素。
最后,publish
允许其他线程运行(希望EDT能够自行更新)
650x975的图像大约需要1m和10s才能完全渲染
完整的代码......
yield