JavaFX - How to create SnapShot/Screenshot of (invisble) WebView

青春壹個敷衍的年華 提交于 2021-02-06 10:23:02

问题


I want to create a SnapShot/Screenshot/Image from a WebView in JavaFX(8).
This WebView does not need to be visible (in my case).

My question:
Is it possible (in any way), to create a Screenshot/Image from a WebView,
when the WebView is not visible (or not added to any visible container)?

See my example code, when WebView (or it's parent ScrollPane) is visible=false,
the Screenshot won't work (respectively is emtpy/blank).

Example code:

package test;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SnapshotResult;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Duration;

public class JavaFXApplication extends Application
{
   @Override
   public void start(Stage primaryStage)
   {
      ImageView webviewPreviewImage = new ImageView();
      Label waitLabel = new Label("Please wait...");
      WebView webView = new WebView();
      webView.setMaxHeight(480d);
      webView.setMinHeight(480d);
      webView.setMaxWidth(640d);
      webView.setMinWidth(640d);
      webView.setZoom(0.4);

      ScrollPane scrollpane = new ScrollPane(webView);
      scrollpane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
      scrollpane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
      scrollpane.setMaxWidth(0); //WORKAROUND: hide the WebView/ScrollPane
      scrollpane.setMaxHeight(0); //WORKAROUND: hide the WebView/ScrollPane
      scrollpane.setMinWidth(0); //WORKAROUND: hide the WebView/ScrollPane
      scrollpane.setMinHeight(0); //WORKAROUND: hide the WebView/ScrollPane

      //scrollpane.setVisible(false); //when WebView is invisible,  SnapShot doesn't work!

       webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() 
       {
          @Override
          public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) 
          {
              if (newState == Worker.State.SUCCEEDED) 
              {
                 //When SUCCEEDED is called, the WebPage may not has fully finished rendering!
                 //so, wait for few seceonds before making the screenshot...
                 Timeline timeline = new Timeline(new KeyFrame(
                         Duration.millis(1500),
                         ae -> takeSnapshot()));
                 timeline.play();
              }
           }

           private KeyFrame takeSnapshot()
           {
              webView.snapshot((SnapshotResult param) ->
              {
                 webviewPreviewImage.setImage(param.getImage());
                 webviewPreviewImage.setFitHeight(240d);
                 webviewPreviewImage.setFitWidth(320d);
                 webviewPreviewImage.setPreserveRatio(true);
                 waitLabel.setVisible(false);
                 return null;
              }, null, null);
              return null;
          }
      });
      webView.getEngine().load("http://www.bing.com");

      VBox root = new VBox();
      root.setAlignment(Pos.CENTER);
      root.setSpacing(10d);
      root.getChildren().add(waitLabel);
      root.getChildren().add(scrollpane);
      root.getChildren().add(webviewPreviewImage);

      Scene scene = new Scene(root, 800, 600);
      primaryStage.setScene(scene);
      primaryStage.show();
  }

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

回答1:


The class WebView contains the following function:

private boolean isTreeReallyVisible() {
    if (getScene() == null) {
        return false;
    }

    final Window window = getScene().getWindow();

    if (window == null) {
        return false;
    }

    boolean iconified = (window instanceof Stage) ? ((Stage)window).isIconified() : false;

    return impl_isTreeVisible()
           && window.isShowing()
           && window.getWidth() > 0
           && window.getHeight() > 0
           && !iconified;
}

As long as the function returns false, the rendering is blocked. So it might be quite tricky to get it to render. Normally, for snapshot you don't have to put the Node in the scene at all.




回答2:


After a lot of searching and scraping several pieces together I found an example a SO-poster posted in an oracle forum that only had the problem that the size of the webview was fixed and that my css used in the html (not in JavaFX) needed:

overflow-x: hidden;
overflow-y: hidden;

to hide the last scrollbar.

So I come up with the following snapshot method (application with animation just as example of your application):

package application;

import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.web.WebView;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

public class WebViewSnapshot extends Application {

    BorderPane rootPane;
    TranslateTransition animation;

    @Override
    public void start(Stage primaryStage) {

        Rectangle rect = new Rectangle(50, 50, 50, 50);
        rect.setFill(Color.CORAL);

        animation = createAnimation(rect);

        Button snapshotButton = new Button("Take snapshot");

        Pane pane = new Pane(rect);
        pane.setMinSize(600, 150);

        rootPane = new BorderPane(pane, null, null, snapshotButton, new Label("This is the main scene"));

        snapshotButton.setOnAction(e -> {
            // html file being shown in webview
            File htmlFile = new File ("generated/template.html");
            // the resulting snapshot png file
            File aboutFile = new File ("generated/about.png");
            generate(htmlFile, aboutFile, 1280, 720);
        });

        BorderPane.setAlignment(snapshotButton, Pos.CENTER);
        BorderPane.setMargin(snapshotButton, new Insets(5));
        Scene scene = new Scene(rootPane);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private TranslateTransition createAnimation(Rectangle rect) {
        TranslateTransition animation = new TranslateTransition(Duration.seconds(1), rect);
        animation.setByX(400);
        animation.setCycleCount(Animation.INDEFINITE);
        animation.setAutoReverse(true);
        animation.play();
        return animation;
    }

    public void generate(File htmlFile, File outputFile, double width, double height) {
        animation.pause();
        // rootPane is the root of original scene in an FXML controller you get this when you assign it an id
        rootPane.setEffect(new GaussianBlur());
        Stage primaryStage = (Stage)rootPane.getScene().getWindow();
        // creating separate webview holding same html content as in original scene
        WebView webView = new WebView();
        // with the size I want the snapshot
        webView.setPrefSize(width, height);
        AnchorPane snapshotRoot = new AnchorPane(webView);
        webView.getEngine().load(htmlFile.toURI().toString());
        Stage popupStage = new Stage(StageStyle.TRANSPARENT);
        popupStage.initOwner(primaryStage);
        popupStage.initModality(Modality.APPLICATION_MODAL);
        // this popup doesn't really show anything size = 1x1, it just holds the snapshot-webview
        popupStage.setScene(new Scene(snapshotRoot, 1, 1));
        // pausing to make sure the webview/picture is completely rendered
        PauseTransition pt = new PauseTransition(Duration.seconds(2));
        pt.setOnFinished(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                WritableImage image = webView.snapshot(null, null);
                // writing a png to outputFile
                // writing a JPG like this will result in a pink JPG, see other posts
                // if somebody can scrape me simple code to convert it ARGB to RGB or something
                String format = "png";
                try {
                    ImageIO.write(SwingFXUtils.fromFXImage(image, null), format, outputFile);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    rootPane.setEffect(null);
                    popupStage.hide();
                    animation.play();
                }
            }
        });
        // pausing, after pause onFinished event will take + write snapshot
        pt.play();
        // GO!
        popupStage.show();
    }

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



回答3:


Use the Robot class to simulate key presses (Fn and PrintScreen), load and then crop it.



来源:https://stackoverflow.com/questions/30121204/javafx-how-to-create-snapshot-screenshot-of-invisble-webview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!