以线程安全的方式有效地使用 BufferedImage?
Using BufferedImage in a thread-safe way efficiently?
假设我有这个简单的 javax.swing.JPanel
组件,它仅用于显示 BufferedImage
。
public class Scratch extends JPanel {
private BufferedImage image;
public Scratch(BufferedImage image) {
this.image = image;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
有时会调用此组件的repaint()
方法,表明Swing应该重绘它。但是,重写的 paintComponent
方法的使用由 Swing 内部处理。因此,我无法准确控制何时读取 BufferedImage
。
现在假设我在注入的 BufferedImage
上执行了一些图像处理算法。这些通常有两种执行方式:
- 正在读取图像的当前状态并使用
setPixel
进行更改。
- 复制当前图像状态(只对RGB值矩阵感兴趣),通过读取原始矩阵并修改复制矩阵进行图像处理,然后用副本替换原始矩阵。这样新的状态就会被渲染而不是原来的 UI.
两个问题:
执行这两个进程最有效(最快)的线程安全方式是什么?为了获得最大的图像处理性能。
从自定义线程在原始实例上调用 setPixel
是线程安全的还是需要在 Swing 事件队列中调用它以避免与 paintComponent
阅读?
也许使用 BufferedImage
不是最好的方法,在这种情况下,您也可以建议其他选项。但我目前想专注于 BufferedImage
.
的 Swing
你是对的,你永远不知道面板的repaint()
什么时候会被执行。为了避免组件中出现任何不需要的视图,我会在后台线程中处理图像。这样,我就不会那么在意(当然我会)图像处理需要多少时间。最后,在处理图像后,我会将其共享到 GUI(返回到 EDT 线程)。
值得一提的是,在 Swing 中 运行 后台执行任务的工具是 Swing Worker. swing worker 将允许您在后台执行长时间任务,然后更新 GUI正确的线程(EDT - 事件调度线程)。
我创建了一个示例,其中框架由图像和 "process image" 按钮组成。
当按下按钮时,worker 启动。它处理图像(在我的例子中 它将图像裁剪到 90%),最后 "refresh" 使用新图像的视图,非常简单。
另外,为了回答你的问题:
Would it be thread-safe to call setPixel on the original instance from
a custom thread or would it need to be called in the Swing event queue
to avoid conflicts with the paintComponent reading?
您不必担心在图像处理任务中要使用什么方法。只是,不要在那里更新 swing 组件。完成后更新它们。
预览:
源代码:
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class TestImage extends JFrame {
private Scratch scratch;
private JButton crop;
public TestImage() {
super("Process image");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
try {
BufferedImage img = loadImage();
scratch = new Scratch(img);
getContentPane().add(scratch, BorderLayout.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
crop = new JButton("Process image");
crop.addActionListener(e -> processImage());
getContentPane().add(crop, BorderLayout.PAGE_END);
setSize(500, 500);
setLocationRelativeTo(null);
}
private void processImage() {
crop.setEnabled(false);
crop.setText("Processing image...");
new ImageProcessorWorker(scratch, () -> {
crop.setEnabled(true);
crop.setText("Process image");
}).execute();
}
private BufferedImage loadImage() throws IOException {
File desktop = new File(System.getProperty("user.home"), "Desktop");
File image = new File(desktop, "img.png");
return ImageIO.read(image);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TestImage().setVisible(true));
}
public static class Scratch extends JPanel implements ImageView {
private static final long serialVersionUID = -5546688149216743458L;
private BufferedImage image;
public Scratch(BufferedImage image) {
this.image = image;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
@Override
public BufferedImage getImage() {
return image;
}
@Override
public void setImage(BufferedImage img) {
this.image = img;
repaint(); //repaint the view after image changes
}
}
public static class ImageProcessorWorker extends SwingWorker<BufferedImage, Void> {
private ImageView view;
private Runnable restoreTask;
public ImageProcessorWorker(ImageView v, Runnable restoreViewTask) {
view = v;
restoreTask = restoreViewTask;
}
@Override
protected BufferedImage doInBackground() throws Exception {
BufferedImage image = view.getImage();
image = crop(image, 0.9d);
Thread.sleep(5000); // Assume it takes 5 second to process
return image;
}
/*
* Taken from
*
*/
public BufferedImage crop(BufferedImage image, double amount) throws IOException {
BufferedImage originalImage = image;
int height = originalImage.getHeight();
int width = originalImage.getWidth();
int targetWidth = (int) (width * amount);
int targetHeight = (int) (height * amount);
// Coordinates of the image's middle
int xc = (width - targetWidth) / 2;
int yc = (height - targetHeight) / 2;
// Crop
BufferedImage croppedImage = originalImage.getSubimage(xc, yc, targetWidth, // widht
targetHeight // height
);
return croppedImage;
}
@Override
protected void done() {
try {
BufferedImage processedImage = get();
view.setImage(processedImage);
if (restoreTask != null)
restoreTask.run();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
super.done();
}
}
public static interface ImageView {
BufferedImage getImage();
void setImage(BufferedImage img);
}
}
假设我有这个简单的 javax.swing.JPanel
组件,它仅用于显示 BufferedImage
。
public class Scratch extends JPanel {
private BufferedImage image;
public Scratch(BufferedImage image) {
this.image = image;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
有时会调用此组件的repaint()
方法,表明Swing应该重绘它。但是,重写的 paintComponent
方法的使用由 Swing 内部处理。因此,我无法准确控制何时读取 BufferedImage
。
现在假设我在注入的 BufferedImage
上执行了一些图像处理算法。这些通常有两种执行方式:
- 正在读取图像的当前状态并使用
setPixel
进行更改。 - 复制当前图像状态(只对RGB值矩阵感兴趣),通过读取原始矩阵并修改复制矩阵进行图像处理,然后用副本替换原始矩阵。这样新的状态就会被渲染而不是原来的 UI.
两个问题:
执行这两个进程最有效(最快)的线程安全方式是什么?为了获得最大的图像处理性能。
从自定义线程在原始实例上调用
setPixel
是线程安全的还是需要在 Swing 事件队列中调用它以避免与paintComponent
阅读?
也许使用 BufferedImage
不是最好的方法,在这种情况下,您也可以建议其他选项。但我目前想专注于 BufferedImage
.
你是对的,你永远不知道面板的repaint()
什么时候会被执行。为了避免组件中出现任何不需要的视图,我会在后台线程中处理图像。这样,我就不会那么在意(当然我会)图像处理需要多少时间。最后,在处理图像后,我会将其共享到 GUI(返回到 EDT 线程)。
值得一提的是,在 Swing 中 运行 后台执行任务的工具是 Swing Worker. swing worker 将允许您在后台执行长时间任务,然后更新 GUI正确的线程(EDT - 事件调度线程)。
我创建了一个示例,其中框架由图像和 "process image" 按钮组成。
当按下按钮时,worker 启动。它处理图像(在我的例子中 它将图像裁剪到 90%),最后 "refresh" 使用新图像的视图,非常简单。
另外,为了回答你的问题:
Would it be thread-safe to call setPixel on the original instance from a custom thread or would it need to be called in the Swing event queue to avoid conflicts with the paintComponent reading?
您不必担心在图像处理任务中要使用什么方法。只是,不要在那里更新 swing 组件。完成后更新它们。
预览:
源代码:
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class TestImage extends JFrame {
private Scratch scratch;
private JButton crop;
public TestImage() {
super("Process image");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
try {
BufferedImage img = loadImage();
scratch = new Scratch(img);
getContentPane().add(scratch, BorderLayout.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
crop = new JButton("Process image");
crop.addActionListener(e -> processImage());
getContentPane().add(crop, BorderLayout.PAGE_END);
setSize(500, 500);
setLocationRelativeTo(null);
}
private void processImage() {
crop.setEnabled(false);
crop.setText("Processing image...");
new ImageProcessorWorker(scratch, () -> {
crop.setEnabled(true);
crop.setText("Process image");
}).execute();
}
private BufferedImage loadImage() throws IOException {
File desktop = new File(System.getProperty("user.home"), "Desktop");
File image = new File(desktop, "img.png");
return ImageIO.read(image);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TestImage().setVisible(true));
}
public static class Scratch extends JPanel implements ImageView {
private static final long serialVersionUID = -5546688149216743458L;
private BufferedImage image;
public Scratch(BufferedImage image) {
this.image = image;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
@Override
public BufferedImage getImage() {
return image;
}
@Override
public void setImage(BufferedImage img) {
this.image = img;
repaint(); //repaint the view after image changes
}
}
public static class ImageProcessorWorker extends SwingWorker<BufferedImage, Void> {
private ImageView view;
private Runnable restoreTask;
public ImageProcessorWorker(ImageView v, Runnable restoreViewTask) {
view = v;
restoreTask = restoreViewTask;
}
@Override
protected BufferedImage doInBackground() throws Exception {
BufferedImage image = view.getImage();
image = crop(image, 0.9d);
Thread.sleep(5000); // Assume it takes 5 second to process
return image;
}
/*
* Taken from
*
*/
public BufferedImage crop(BufferedImage image, double amount) throws IOException {
BufferedImage originalImage = image;
int height = originalImage.getHeight();
int width = originalImage.getWidth();
int targetWidth = (int) (width * amount);
int targetHeight = (int) (height * amount);
// Coordinates of the image's middle
int xc = (width - targetWidth) / 2;
int yc = (height - targetHeight) / 2;
// Crop
BufferedImage croppedImage = originalImage.getSubimage(xc, yc, targetWidth, // widht
targetHeight // height
);
return croppedImage;
}
@Override
protected void done() {
try {
BufferedImage processedImage = get();
view.setImage(processedImage);
if (restoreTask != null)
restoreTask.run();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
super.done();
}
}
public static interface ImageView {
BufferedImage getImage();
void setImage(BufferedImage img);
}
}