简体   繁体   English

JavaFX的ImageView中的屏幕共享

[英]Screen sharing in the ImageView of JavaFX

I am trying to build a JavaFX application, where I have a button named "Start" and an ImageView.我正在尝试构建一个 JavaFX 应用程序,其中有一个名为“开始”的按钮和一个 ImageView。 With the robot class of JavaFX-12, I am trying to take a screenshot of the laptop screen when the button is clicked and show the images one by one during the runtime in the ImageView.使用 JavaFX-12 的机器人 class,我试图在单击按钮时截取笔记本电脑屏幕的屏幕截图,并在运行时在 ImageView 中一张一张地显示图像。 My problem is that the JavaFX window does not respond and the program crashes (probably).我的问题是 JavaFX window 没有响应并且程序崩溃(可能)。 Even putting the thread into sleep does not seem to work.即使使线程进入睡眠状态似乎也不起作用。 I assume that it isn't working as I have not set any fps rule, but how can I do that?我认为它不起作用,因为我没有设置任何 fps 规则,但我该怎么做呢? At the moment, I am creating writable images, converting them into a separate image with a number, saving them, and again reusing them.目前,我正在创建可写图像,将它们转换为带有数字的单独图像,保存它们,然后再次重用它们。 My goal is to create a screen sharing of the same laptop in the image view.我的目标是在图像视图中创建同一台笔记本电脑的屏幕共享。 I know that's difficult.我知道这很难。 I'm new to the JavaFx robot class (not he awt one).我是 JavaFx 机器人 class 的新手(不是他一个)。 Any help is appreciated.任何帮助表示赞赏。

PS : The images are properly formed in the directory. PS :图像在目录中正确形成。

package sample;

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;

import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");

        ImageView iv = new ImageView();
        iv.setFitWidth(100);
        iv.setFitHeight(100);
        Button b = new Button("Start");
        VBox v = new VBox(10);
        v.getChildren().addAll(b,iv);
        b.setOnAction(event -> {
            Robot r = new Robot();
            WritableImage wi = new WritableImage(300,300);
            WritableImage i;
            Rectangle2D rect = Screen.getPrimary().getVisualBounds();
            while(true){
                i = r.getScreenCapture(wi,rect);
                try {
                    ImageIO.write(SwingFXUtils.fromFXImage(i,null),"png",new File("F:/Pic/pic" + x + ".png"));
                    iv.setImage(new Image(new FileInputStream("F:/Pic/pic" + x + ".png")));
                    //Thread.sleep(500);
                    //iv.setImage(null);
                } catch (Exception e) {
                    System.out.println(e);
                }
            }
        });
        primaryStage.setScene(new Scene(v, 500, 500));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

JavaFX is, like most UI frameworks, single-threaded. JavaFX 与大多数 UI 框架一样,是单线程的。 You must never block (eg sleep ) or otherwise monopolize (eg while (true) ) the JavaFX Application Thread .您绝不能阻塞(例如sleep )或以其他方式垄断(例如while (true)JavaFX Application Thread That thread is responsible for everything related to the UI and if it's not free to do its job then the UI will become unresponsive.该线程负责与 UI 相关的所有事情,如果它不能自由地完成它的工作,那么 UI 将变得无响应。 Note that a render pass cannot happen until the FX thread returns from whatever it's doing, so setting the image of an ImageView in a loop will have no visible effect until some time after the loop terminates.请注意,在 FX 线程从它正在执行的任何操作中返回之前,渲染过程不会发生,因此在循环中设置ImageView的图像将在循环终止后的一段时间内没有可见的效果。

Also, a full-throttle while loop will attempt to get a screen capture as fast as the CPU can execute said loop.此外,全速while循环将尝试在 CPU 执行所述循环时尽可能快地获取屏幕截图。 That is likely to be much faster than the rate at which your UI refreshes and is thus a waste of resources.这可能比您的 UI 刷新速度快得多,因此会浪费资源。 The rate of screen captures should not exceed the frame rate.屏幕捕获的速率不应超过帧速率。

If you need to loop on the FX thread and/or be constrained by the (JavaFX's) frame rate then use the javafx.animation API.如果您需要在 FX 线程上循环和/或受(JavaFX 的)帧速率限制,请使用javafx.animation API。 In your case, an AnimationTimer seems apt.在您的情况下, AnimationTimer似乎很合适。 Here's an example which continuously screenshots the primary screen:这是一个连续截取主屏幕的示例:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class App extends Application {

  @Override
  public void start(Stage primaryStage) {
    ImageView view = new ImageView();
    view.setFitWidth(720);
    view.setFitHeight(480);

    // Keep a reference to the AnimationTimer instance if
    // you want to be able to start and stop it at will
    new AnimationTimer() {

      final Robot robot = new Robot();
      final Rectangle2D bounds = Screen.getPrimary().getBounds();

      @Override
      public void handle(long now) {
        WritableImage oldImage = (WritableImage) view.getImage();
        WritableImage newImage = robot.getScreenCapture(oldImage, bounds);
        if (oldImage != newImage) {
          view.setImage(newImage);
        }
      }
    }.start();

    primaryStage.setScene(new Scene(new StackPane(view)));
    primaryStage.show();
  }
}

Some notes:一些注意事项:

  • The AnimationTimer#handle(long) method is invoked once per frame. AnimationTimer#handle(long)方法每帧调用一次。 JavaFX tries to target 60 frames-per-second. JavaFX 尝试以每秒 60 帧为目标。 If that's too fast (the above lags somewhat on my computer) then you can use the method's argument, which is the timestamp of the current frame in nanoseconds, to throttle the rate of screen captures.如果这太快(以上在我的计算机上有些滞后),那么您可以使用该方法的参数,即当前帧的时间戳(以纳秒为单位)来限制屏幕捕获的速率。 You could also look into using a Timeline or PauseTransition instead of an AnimationTimer .您还可以考虑使用TimelinePauseTransition而不是AnimationTimer See JavaFX periodic background task for more information.有关详细信息,请参阅JavaFX 定期后台任务
  • The above gives a Droste Effect (I think that's the term?) since the screen capture is displayed on the screen which is being captured.上面给出了Droste 效果(我认为这是术语?),因为屏幕截图显示在正在捕获的屏幕上。

My example does not include saving each image to a file.我的示例不包括将每个图像保存到文件中。 You seem to already understand how to do that so you should be able to easily adapt the code.您似乎已经了解如何做到这一点,因此您应该能够轻松地调整代码。 It'd probably be a good idea, however, to move the I/O to a background thread.然而,将 I/O 移动到后台线程可能是个好主意。 Unfortunately, that will likely require using different WritableImage instances for each capture to avoid the image being mutated by the FX thread while the background thread reads it.不幸的是,这可能需要为每次捕获使用不同的WritableImage实例,以避免在后台线程读取图像时,FX 线程改变图像。 It may also require some tuning or dropped images;它可能还需要一些调整或删除图像; I'm not sure how well the I/O will keep up with the influx of screen captures (ie standard producer-consumer problems).我不确定 I/O 将如何跟上屏幕捕获的涌入(即标准的生产者-消费者问题)。


As an aside, your question explains you're attempting to share the entire screen.顺便说一句,您的问题说明您正在尝试共享整个屏幕。 If that's the case then continue using Robot .如果是这种情况,请继续使用Robot However, if you only need to share something from the JavaFX application itself then consider using Node#snapshot(SnapshotParameters,WritableImage) or Scene#snapshot(WritableImage) (assuming you can't just send model data instead of images).但是,如果您只需要从 JavaFX 应用程序本身共享某些内容,那么请考虑使用Node#snapshot(SnapshotParameters,WritableImage)Scene#snapshot(WritableImage) (假设您不能只发送 model 数据而不是图像)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM