Created
May 23, 2013 05:38
-
-
Save jewelsea/5632958 to your computer and use it in GitHub Desktop.
Captures web pages to image files using JavaFX WebView and ImageIO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javafx.animation.PauseTransition; | |
import javafx.application.Application; | |
import javafx.beans.value.*; | |
import javafx.embed.swing.SwingFXUtils; | |
import javafx.event.*; | |
import javafx.geometry.Bounds; | |
import javafx.scene.Scene; | |
import javafx.scene.control.*; | |
import javafx.scene.image.*; | |
import javafx.scene.layout.*; | |
import javafx.scene.web.WebView; | |
import javafx.stage.Stage; | |
import javafx.util.Duration; | |
import javafx.util.converter.NumberStringConverter; | |
import javax.imageio.ImageIO; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
public class WebViewCaptureDemo extends Application { | |
private static final String HOME_LOC = "http://docs.oracle.com/javafx/2/get_started/animation.htm"; | |
private WebView webView; | |
private File captureFile = new File("cap.png"); | |
public static void main(String[] args) { Application.launch(WebViewCaptureDemo.class); } | |
@Override public void start(Stage stage) throws Exception { | |
webView = new WebView(); | |
webView.setPrefSize(1000, 8000); | |
final TextField location = new TextField(); | |
location.setOnAction(new EventHandler<ActionEvent>() { | |
@Override public void handle(ActionEvent event) { | |
if (!location.getText().startsWith("http")) { | |
location.setText("http://" + location.getText()); | |
} | |
webView.getEngine().load(location.getText()); | |
} | |
}); | |
webView.getEngine().locationProperty().addListener(new ChangeListener<String>() { | |
@Override | |
public void changed(ObservableValue<? extends String> observable, String oldLocation, String newLocation) { | |
location.setText(newLocation); | |
} | |
}); | |
ScrollPane webViewScroll = new ScrollPane(); | |
webViewScroll.setContent(webView); | |
webViewScroll.setPrefSize(800, 300); | |
final Button capture = new Button("Capture"); | |
final ProgressIndicator progress = new ProgressIndicator(); | |
progress.setVisible(false); | |
final TextField prefWidth = new TextField("1000"); | |
final TextField prefHeight = new TextField("8000"); | |
HBox controls = new HBox(10); | |
controls.getChildren().addAll(capture, progress, prefWidth, prefHeight); | |
final ImageView imageView = new ImageView(); | |
ScrollPane imageViewScroll = makeScrollable(imageView); | |
imageViewScroll.setPrefSize(800, 300); | |
final PauseTransition pt = new PauseTransition(); | |
pt.setDuration(Duration.millis(500)); | |
pt.setOnFinished(new EventHandler<ActionEvent>() { | |
@Override | |
public void handle(ActionEvent actionEvent) { | |
WritableImage image = webView.snapshot(null, null); | |
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null); | |
try { | |
ImageIO.write(bufferedImage, "png", captureFile); | |
imageView.setImage(new Image(captureFile.toURI().toURL().toExternalForm())); | |
System.out.println("Captured WebView to: " + captureFile.getAbsoluteFile()); | |
progress.setVisible(false); | |
capture.setDisable(false); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
}); | |
capture.setOnAction(new EventHandler<ActionEvent>() { | |
@Override | |
public void handle(ActionEvent actionEvent) { | |
NumberStringConverter converter = new NumberStringConverter(); | |
double W = converter.fromString(prefWidth.getText()).doubleValue(); | |
double H = converter.fromString(prefHeight.getText()).doubleValue(); | |
// ensure that the capture size has a reasonable min size and is within the limits of what JavaFX is capable of processing. | |
if (W < 100) { | |
W = 100; | |
prefWidth.setText("100"); | |
} | |
if (W > 2000) { | |
W = 2000; | |
prefWidth.setText("2000"); | |
} | |
if (H < 100) { | |
H = 100; | |
prefHeight.setText("100"); | |
} | |
if (H > 16000) { | |
H = 16000; | |
prefHeight.setText("16000"); | |
} | |
webView.setPrefWidth(W); | |
webView.setPrefHeight(H); | |
pt.play(); | |
capture.setDisable(true); | |
progress.setVisible(true); | |
} | |
}); | |
webView.getEngine().load(HOME_LOC); | |
VBox layout = new VBox(10); | |
layout.setStyle("-fx-padding: 10; -fx-background-color: cornsilk;"); | |
layout.getChildren().setAll( | |
location, | |
webViewScroll, | |
controls, | |
imageViewScroll, | |
new Label("Capture File: " + captureFile.getAbsolutePath()) | |
); | |
VBox.setVgrow(imageViewScroll, Priority.ALWAYS); | |
stage.setScene(new Scene(layout)); | |
stage.show(); | |
} | |
private ScrollPane makeScrollable(final ImageView imageView) { | |
final ScrollPane scroll = new ScrollPane(); | |
final StackPane centeredImageView = new StackPane(); | |
centeredImageView.getChildren().add(imageView); | |
scroll.viewportBoundsProperty().addListener(new ChangeListener<Bounds>() { | |
@Override public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) { | |
centeredImageView.setPrefSize( | |
Math.max(imageView.prefWidth(bounds.getHeight()), bounds.getWidth()), | |
Math.max(imageView.prefHeight(bounds.getWidth()), bounds.getHeight()) | |
); | |
} | |
}); | |
scroll.setContent(centeredImageView); | |
return scroll; | |
} | |
} | |
What about "headless" snapshoting? Like PhantomJS does?
JavaFX can render in a headless mode using the Monocle glass windowing component. I do not know if this headless rendering using Monocle is compatible with WebView. You could ask further questions on the openjfx-dev mailing list for more information.
It doesn't work. I've tried it just now but got NPE:
java.lang.NullPointerException
at com.sun.javafx.webkit.prism.WCPageBackBufferImpl.validate(WCPageBackBufferImpl.java:97)
at com.sun.webkit.WebPage.paint(WebPage.java:656)
at com.sun.javafx.sg.prism.web.NGWebView.renderContent(NGWebView.java:96)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:576)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:576)
at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2294)
at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2188)
at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2214)
at com.sun.javafx.sg.prism.CacheFilter.impl_renderNodeToCache(CacheFilter.java:671)
at com.sun.javafx.sg.prism.CacheFilter.render(CacheFilter.java:575)
at com.sun.javafx.sg.prism.NGNode.renderCached(NGNode.java:2358)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2044)
at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:576)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:576)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:477)
at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:323)
at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
at java.lang.Thread.run(Thread.java:745)
This worked good for me, just copy the whole file into your project and try it out.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Primary feature is ability to capture a web page rendering up to 2000 pixels by 16000 pixels (i.e. larger than a screen resolution).