【问题标题】:Trying to render javafx WebView to offscreen buffer or FBO试图将 javafx WebView 渲染到屏幕外缓冲区或 FBO
【发布时间】:2017-03-31 06:25:30
【问题描述】:

最终目标是能够以 30fps 或更高的速度记录 WebView 的输出,也许是通过为 javafx 设置 FBO?然后我可以以我想要的任何帧速率拉出帧。

我浏览了一些,我在 ViewScene 中遇到了 UploadingPainter,这让我觉得这是可能的。困难在于,这似乎是在幕后,对我来说有点新。

有谁知道这样的方法?

这是我在调试时遇到的代码:

@Override
public void setStage(GlassStage stage) {
    super.setStage(stage);
    if (stage != null) {
        WindowStage wstage  = (WindowStage)stage;
        if (wstage.needsUpdateWindow() || GraphicsPipeline.getPipeline().isUploading()) {
            if (Pixels.getNativeFormat() != Pixels.Format.BYTE_BGRA_PRE ||
                ByteOrder.nativeOrder() != ByteOrder.LITTLE_ENDIAN) {
                throw new UnsupportedOperationException(UNSUPPORTED_FORMAT);
            }
            painter = new UploadingPainter(this);
        } else {
            painter = new PresentingPainter(this);
        }
        painter.setRoot(getRoot());
        paintRenderJob = new PaintRenderJob(this, PaintCollector.getInstance().getRendered(), painter);
    }
}

【问题讨论】:

    标签: javafx jogl


    【解决方案1】:

    这是在 WebView 中捕获动画的示例。

    从 web 视图中捕获的图像被放置在 Paginator 中以供查看,以便于查看它们。如果您愿意,您可以使用SwingFXUtilsImageIO 将它们写入文件。如果要将生成的图像放入缓冲区,可以使用他们的PixelReader

    它并没有按照我想要的方式工作。我想在不将其置于可见阶段的情况下对 WebView 进行快照。拍摄不在舞台中的节点的快照对于 JavaFX 中的所有其他节点类型都可以正常工作(据我所知),但是,由于某些奇怪的原因,它不适用于 WebView。因此,该示例实际上在显示窗口后面创建了一个新阶段,用于显示动画捕获结果的图像序列。我知道这不是你想要的,但它就是这样......

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.beans.property.*;
    import javafx.collections.*;
    import javafx.concurrent.Worker;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.control.*;
    import javafx.scene.image.*;
    import javafx.scene.layout.*;
    import javafx.scene.web.WebView;
    import javafx.stage.Stage;
    
    public class WebViewAnimationCaptor extends Application {
    
        private static final String CAPTURE_URL =
                "https://upload.wikimedia.org/wikipedia/commons/d/dd/Muybridge_race_horse_animated.gif";
    
        private static final int N_CAPS_PER_SECOND = 10;
        private static final int MAX_CAPTURES = N_CAPS_PER_SECOND * 5;
        private static final int W = 186, H = 124;
    
        class CaptureResult {
            ObservableList<Image> images = FXCollections.observableArrayList();
            DoubleProperty progress = new SimpleDoubleProperty();
        }
    
        @Override public void start(Stage stage) {
            CaptureResult captures = captureAnimation(CAPTURE_URL);
            Pane captureViewer = createCaptureViewer(captures);
    
            stage.setScene(new Scene(captureViewer, W + 40, H + 80));
            stage.show();
        }
    
        private StackPane createCaptureViewer(CaptureResult captures) {
            ProgressIndicator progressIndicator = new ProgressIndicator();
            progressIndicator.progressProperty().bind(captures.progress);
            progressIndicator.setPrefSize(W, H);
    
            StackPane stackPane = new StackPane(progressIndicator);
            stackPane.setPadding(new Insets(10));
            if (captures.progress.get() >= 1.0) {
                stackPane.getChildren().setAll(
                    createImagePages(captures.images)
                );
            } else {
                captures.progress.addListener((observable, oldValue, newValue) -> {
                    if (newValue.doubleValue() >= 1.0) {
                        stackPane.getChildren().setAll(
                                createImagePages(captures.images)
                        );
                    }
                });
            }
    
            return stackPane;
        }
    
        private Pagination createImagePages(ObservableList<Image> captures) {
            Pagination pagination = new Pagination();
            pagination.setPageFactory(param -> {
                ImageView currentImage = new ImageView();
                currentImage.setImage(
                        param < captures.size()
                                ? captures.get(param)
                                : null
                );
    
                StackPane pageContent = new StackPane(currentImage);
                pageContent.setPrefSize(W, H);
    
                return pageContent;
            });
    
            pagination.setCurrentPageIndex(0);
            pagination.setPageCount(captures.size());
            pagination.setMaxPageIndicatorCount(captures.size());
    
            return pagination;
        }
    
        private CaptureResult captureAnimation(final String url) {
            CaptureResult captureResult = new CaptureResult();
    
            WebView webView = new WebView();
            webView.getEngine().load(url);
            webView.setPrefSize(W, H);
    
            Stage captureStage = new Stage();
            captureStage.setScene(new Scene(webView, W, H));
            captureStage.show();
    
            SnapshotParameters snapshotParameters = new SnapshotParameters();
            captureResult.progress.set(0);
    
            AnimationTimer timer = new AnimationTimer() {
                long last = 0;
    
                @Override
                public void handle(long now) {
                    if (now > last + 1_000_000_000.0 / N_CAPS_PER_SECOND) {
                        last = now;
                        captureResult.images.add(webView.snapshot(snapshotParameters, null));
                        captureResult.progress.setValue(
                                captureResult.images.size() * 1.0 / MAX_CAPTURES
                        );
                    }
    
                    if (captureResult.images.size() > MAX_CAPTURES) {
                        captureStage.hide();
                        this.stop();
                    }
                }
            };
    
            webView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
                if (Worker.State.SUCCEEDED.equals(newValue)) {
                    timer.start();
                }
            });
    
            return captureResult;
        }
    
        public static void main(String[] args) { launch(args); }
    }
    

    要微调动画序列捕获,您可以查看此info on AnimationTimers in JavaFX

    如果你需要让这个东西“无头”,这样就不需要可见阶段,你可以试试这个gist by danialfarid which performs "Java Image Capture, HTML Snapshot, HTML to image"(虽然我没有写链接的要点也没有尝试过)。


    就我而言,无头是关键。有问题的(linux)机器在完全无头的服务器场中运行。至于要点,我在那里看到了一个 show() ,但我会仔细看看以确保我没有忽略某些东西。

    要点基于Monocle glass rendering toolkit for JavaFX systems。该工具包支持在任何系统上基于软件的无头渲染。

    来自Monocle Documentation

    无头端口什么都不做。它适用于您想在没有图形、输入或平台依赖的情况下运行 JavaFX。渲染仍在进行,只是没有显示在屏幕上。

    无头端口使用 InputDeviceRegistry 的 LinuxInputDeviceRegistry 实现。然而,无头端口根本不访问任何实际的 Linux 设备或任何本机 API;它在设备模拟模式下使用 Linux 输入注册表。这使得即使在非 Linux 平台上也可以模拟 Linux 设备输入。 tests/system/src/test/java/com/sun/glass/ui/monocle/input 中的测试广泛使用了这个特性。

    如果基于 JavaFX Monocle 的方法最终不适合您,您可以考虑使用另一个(与 JavaFX 无关的)无头 HTML 呈现工具包,例如 PhantomJS

    【讨论】:

    • 无头是关键,就我而言。有问题的(linux)机器在完全无头的服务器场中运行。至于要点,我在那里看到了一个 show() ,但我会仔细看看以确保我没有忽略某些东西。谢谢!
    • 这个要点在属性名称中提到了无头,我不确定它是否有效。显然,所需的依赖项在 Maven 中,请务必阅读 cmets...gist.github.com/danialfarid/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-10
    • 1970-01-01
    相关资源
    最近更新 更多