我有一个Swing UI,其中一部分由BufferedImage
组成,我需要定期修改像素。
创建JLabel
并通过传递setIcon
来调用JLabel
方法似乎很常见:
new ImageIcon(bufferedImage)
例如,具有20K + rep的用户在此处接受的答案中屏幕上显示BufferedImage的方式:
A simple way to setting a bufferedImage into a single colored pixel without placing a image into it?
所以我也是这样做的:一个JLabel,它的Icon设置为一个包含BufferedImage的ImageIcon,我的问题与多线程和Swing重绘相关:我应该如何修改{{1}内的像素这样可以保证更改对用户显示?
我认为如果我从不是EDT线程的线程修改BufferedImage
并且如果没有使用同步/锁定/内存屏障,那么没有什么能保证更改是可见的。< / p>
我可以直接在EDT上修改像素吗?
一旦我修改了像素,我应该调用BufferedImage
的{{1}}方法吗?
这会保证我的更改始终可见吗? (通过“可见”这里我的字面意思是“可见”,如在屏幕上可见,以及“在EDT中可见”)。
我宁愿保持这个简单,我也不想使用非Swing API或3D API等。
答案 0 :(得分:2)
如果我正确理解了这个问题,这个示例需要BufferedImage
,并用红色像素替换该图像中的所有像素。
这是通过使用SwingWorker
来实现的。基本上,这会生成原始图像的副本并遍历像素数据,更新每个像素。然后通过复制它将该图像与UI重新同步。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PixelMe {
public static void main(String[] args) {
new PixelMe();
}
public PixelMe() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public BufferedImage createImage() {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, 100, 100);
g.dispose();
return image;
}
public class TestPane extends JPanel {
private JLabel label;
private BufferedImage master;
public TestPane() {
setLayout(new BorderLayout());
label = new JLabel(new ImageIcon(createImage()));
add(label);
JButton update = new JButton("Update");
add(update, BorderLayout.SOUTH);
update.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
BufferedImage image = (BufferedImage) ((ImageIcon)label.getIcon()).getImage();
new UpdateWorker(image, label).execute();
}
});
}
}
public class UpdateWorker extends SwingWorker<BufferedImage, BufferedImage> {
private BufferedImage copy;
private JLabel target;
public UpdateWorker(BufferedImage master, JLabel target) {
this.target = target;
copy = makeCopy(master);
}
public BufferedImage makeCopy(BufferedImage master) {
BufferedImage image = new BufferedImage(master.getWidth(), master.getHeight(), master.getType());
Graphics2D g = image.createGraphics();
g.drawImage(master, 0, 0, null);
g.dispose();
return image;
}
@Override
protected void process(List<BufferedImage> chunks) {
target.setIcon(new ImageIcon(chunks.get(chunks.size() - 1)));
}
@Override
protected BufferedImage doInBackground() throws Exception {
int pixel = Color.RED.getRGB();
for (int row = 0; row < copy.getHeight(); row++) {
for (int col = 0; col < copy.getWidth(); col++) {
copy.setRGB(col, row, pixel);
publish(makeCopy(copy));
}
}
return null;
}
}
}
应该注意的是,这是一个非常昂贵的例子,因为为每个像素更改创建了一个新的BufferedImage
。您可以建立一个图像池并使用它们,看起来真的只对最后一个图像感兴趣(在process
方法中)或减少更新次数,但它只是一个概念证明。
答案 1 :(得分:2)
我可以直接在EDT上修改像素吗?
没有。提到了几个替代方案here,但主要示例将屏幕外缓冲区视为在单独线程中随时间演变的模型。 javax.swing.Timer
定期通知侦听视图有可用的更新,同步对共享数据的访问。由于模型中的任何像素可能会发生变化,因此会在每个刻度处重新绘制整个视图。
如果更新的像素是几何本地化的,您可以尝试drawImage()
的变体,只更新屏幕的一部分;但要尽量避免无意中缩放。