我正在开发一款软件,该软件从摄像头获取图像并实时显示在JavaFX的ImageView
中。我有一个线程获取最后一张图像(在这种情况下是一个BufferedImage
),以及一个AnimationTimer
将其分配给ImageView
。我选择使用AnimationTimer
是因为它似乎比每次获取新图像时填充平台运行的Runnable更好。刷新效果很好,帧率也不错。
然而,我注意到当AnimationTimer
正在运行时,我的软件菜单栏无法正常显示。当我悬停在某些菜单项上时,部分菜单项消失。以下图片对此进行了说明:
左侧是菜单的正常外观,右侧是AnimationTimer
运行时的外观。你可以看到,“Save”菜单项已经消失,并且我的实时图像背景被显示出来。而且,当我打开一个新窗口(在新的Scene
中)时,当我悬停在任何类型的Node
(如按钮、复选框等)上时,背景会变黑。我通过在初始化Scene
时将深度缓冲布尔设置为true来解决了这个问题。然而,我不知道如何修复这个菜单栏错误,并且我认为这些错误表明我正在做的可能不正确。
我认为JavaFX应用程序线程被新图像占据,导致其他元素(例如菜单项)绘制所需时间过长,从而导致这种情况发生。
问题:
- 这个bug真的是从那里产生的吗?
- 有没有一种方法可以改进我的代码,例如使用与
AnimationTimer
不同的东西?
以下是重现此问题的代码片段。在start函数中更改两个字符串以进行图像路径的更改。这些图像应该相对较大(几MB)。
单击“Start”按钮以启动动画计时器。然后尝试打开“File”菜单,并悬停在菜单项上。这个bug不会出现在所有情况下,请反复移动鼠标上下,它应该在某些时候出现。
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class ImageRefresher extends Application {
@Override
public void start(Stage primaryStage) {
//Here change the 2 Strings to a path to an image on your HDD
//The bug appears more easily with large images (>3-4MB)
String pathToImage1 = "/path/to/your/first/image";
String pathToImage2 = "/path/to/your/second/image";
try {
//Image content (contains buffered image, see below)
ImageContent image = new ImageContent(pathToImage1);
//If this line is commented, the bug does not appear
image.setImage(ImageIO.read(new File(pathToImage2)));
//JavaFX class containing nodes (see below)
MainWindow window = new MainWindow(image);
Scene scene = new Scene(window.getPane(), 300, 250);
primaryStage.setTitle("Menu refresh");
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException ex) {
Logger.getLogger(ImageRefresher.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
public class MainWindow {
private BorderPane pane;
private MenuBar menuBar;
private ImageView displayImage;
private Button startRefreshingButton;
private ImageContent imageContent;
private AnimationTimer animationTimer;
public MainWindow(ImageContent imageContent) {
this.imageContent = imageContent;
//Builds the window's components
buildGraphic();
//The image is reset at each frame
animationTimer = new AnimationTimer() {
@Override
public void handle(long now) {
displayImage.setImage(imageContent.getDisplayableImage());
}
};
}
private void buildGraphic() {
pane = new BorderPane();
menuBar = new MenuBar();
Menu menu = new Menu("File");
menu.getItems().addAll(new MenuItem("Save"),
new MenuItem("Open"),
new MenuItem("Close"));
menuBar.getMenus().add(menu);
displayImage = new ImageView();
startRefreshingButton = new Button("Start");
startRefreshingButton.setOnAction((event) -> {
animationTimer.start();
});
pane.setTop(menuBar);
pane.setCenter(displayImage);
pane.setBottom(startRefreshingButton);
}
public Pane getPane() {
return pane;
}
}
public class ImageContent {
private BufferedImage imageContent;
//Initializes bufferedimage with the path specified
public ImageContent(String pathToImage) throws IOException {
imageContent = ImageIO.read(new File(pathToImage));
}
public void setImage(BufferedImage newImage) {
imageContent = newImage;
}
//Function called by the animation timer to
//get a JavaFX image from a bufferedimage
public Image getDisplayableImage() {
return SwingFXUtils.toFXImage(imageContent, null);
}
}
}