javafx - 尝试将 javafx WebView 渲染到离屏缓冲区或 FBO

标签 javafx jogl

最终目标是能够以 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);
    }
}

最佳答案

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

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

first second

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

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 .

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


Headless is key, in my case. The (linux) machines in question run in a server farm totally headless. As for the gist, I see a show() in there but I'll take a closer look to make sure that I didn't overlook something.

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

来自Monocle Documentation :

The headless port does nothing. It is for when you want to run JavaFX with no graphics, input or platform dependencies. Rendering still happens, it just doesn’t show up on the screen.

headless operation

The headless port uses the LinuxInputDeviceRegistry implementation of InputDeviceRegistry. However the headless port does not access any actual Linux devices or any native APIs at all; it uses the Linux input registry in device simulation mode. This allows Linux device input to be simulated even on non-Linux platforms. The tests in tests/system/src/test/java/com/sun/glass/ui/monocle/input make extensive use of this feature.

如果基于 JavaFX Monocle 的方法最终不适合您,您可以考虑另一个(与 JavaFX 无关) headless HTML 渲染套件,例如 PhantomJS .

关于javafx - 尝试将 javafx WebView 渲染到离屏缓冲区或 FBO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40642573/

相关文章:

java - 如何在 JOGL 中独立于彼此轴旋转两条线?

java - 当尝试通过 ObservableList 将项目添加到 JavaFX TableView 时,TableView 项目将不会显示

java - 如何优化我的 JOGL 应用程序?

java - 将图表保存为 pdf 时消除模糊图表

javafx - CSS 解析错误

java - revalidate()/repaint() 在 ActionListener 中不起作用

java - 如何正确从视频内存加载/卸载纹理?

java - 从JOGL最新master解析NewtCanvasJFX

JavaFX 在控件中显示属性

java - 调整 TableView 大小以适合 ScrollPane