尝试将 javafx WebView 渲染到屏幕外缓冲区或 FBO
Trying to render javafx WebView to offscreen buffer or FBO
最终目标是能够以 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 中捕获动画的示例。
从网络视图中捕获的图像被放置在一个分页器中以供查看,以便于查看它们。如果您愿意,可以使用 SwingFXUtils
和 ImageIO
将它们写入文件。如果你想将生成的图像放入缓冲区,你可以使用它们的 PixelReader
.
它并没有按照我想要的方式工作。我想在不将 WebView 放在可见阶段的情况下对其进行快照。拍摄不在 Stage 中的节点的快照对于 JavaFX 中的所有其他节点类型都可以正常工作(据我所知),但是,由于某些奇怪的原因,它不适用于 WebView。因此,该示例实际上在显示器 window 后面创建了一个新舞台,用于显示动画捕获结果的图像序列。我知道这不完全是你想要的,但它就是这样......
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。此工具包支持在任何系统上基于软件的无头渲染。
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.
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 无关的)无头 HTML 渲染套件,例如 PhantomJS.
最终目标是能够以 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 中捕获动画的示例。
从网络视图中捕获的图像被放置在一个分页器中以供查看,以便于查看它们。如果您愿意,可以使用 SwingFXUtils
和 ImageIO
将它们写入文件。如果你想将生成的图像放入缓冲区,你可以使用它们的 PixelReader
.
它并没有按照我想要的方式工作。我想在不将 WebView 放在可见阶段的情况下对其进行快照。拍摄不在 Stage 中的节点的快照对于 JavaFX 中的所有其他节点类型都可以正常工作(据我所知),但是,由于某些奇怪的原因,它不适用于 WebView。因此,该示例实际上在显示器 window 后面创建了一个新舞台,用于显示动画捕获结果的图像序列。我知道这不完全是你想要的,但它就是这样......
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。此工具包支持在任何系统上基于软件的无头渲染。
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.
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 无关的)无头 HTML 渲染套件,例如 PhantomJS.