Recently I was investigating how to capture web page as image.
I recently realized that there is a nicer nice component for navigating web page in Java FX.
Please note that you should include jfxrt.jar, which was in JDK1.7.0_XX/jre/lib, should be added to your classpath.
I am very happy if you give me some feedback for this code.
Here is some trivial utility methods used in above web page capture class.
I recently realized that there is a nicer nice component for navigating web page in Java FX.
- javafx.scene.web.WebView
- javafx.embed.swing.JFXPanel
Please note that you should include jfxrt.jar, which was in JDK1.7.0_XX/jre/lib, should be added to your classpath.
Code
Here is an actual code. Please feel free to use it...I am very happy if you give me some feedback for this code.
package com.dukesoftware.javafx; import static com.dukesoftware.utils.io.IOUtils.getExtension; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.concurrent.Worker.State; import javafx.embed.swing.JFXPanel; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javax.imageio.ImageIO; import javax.swing.JFrame; import com.dukesoftware.utils.awt.AwtUtils; import com.dukesoftware.utils.io.IOUtils; import com.dukesoftware.utils.swing.jframe.JFrameFactory; import com.google.common.collect.ImmutableMap; public class WebCapture extends JFXPanel{ private static final String PATH = "path"; private static final String URL = "url"; private static final String COMMAND = "command"; private static final String SHUTDOWN = "shutdown"; public static void main(String[] args) throws InterruptedException { WebCapture capture = new WebCapture(1024, 768, 1000); // you can see the WebView behavior if you want // JFrame frame = JFrameFactory.createExitJFrame(capture, "", 1024, 768); // frame.pack(); // frame.setVisible(true); capture .load("http://www.google.com", "c:/temp/google.png") .load("http://www.yahoo.com", "c:/temp/yahoo.png") .load("http://www.bing.com", "c:/temp/bing.png") .shutdownAfterAllRequestsProcessed(); } private WebView webView; private final long waitMills; private final LinkedBlockingQueue<Map<String, String>> queue = new LinkedBlockingQueue<>(); private volatile Map<String, String> current = null; public WebCapture(int width, int height, long waitMills){ setSize(width, height); this.waitMills = waitMills; Platform.runLater(new Runnable() { @Override public void run() { initWebViewFX(); } }); } // This method is invoked on JavaFX thread private void initWebViewFX() { if(webView != null) { throw new IllegalStateException(); } webView = new WebView(); webView.getStyleClass().add(".scroll-bar{ -fx-scale-x: 0;-fx-scale-y: 0;-fx-scale-z: 0; }"); System.out.println(webView.getStylesheets()); // hide webview scrollbars whenever they appear. // hideScrollBar(); // get web engine final WebEngine engine = webView.getEngine(); // add listener for capture image when loading page is completed engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() { @Override public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) { if (State.SUCCEEDED == newValue) { Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { try { // need this sleep because sometimes the capturing is started before the rendering is done // if anyone knows better solution, please tell me!! Thread.sleep(waitMills); BufferedImage image = AwtUtils.captureView(WebCapture.this); String path = current.get(PATH); ImageIO.write(image, IOUtils.getExtension(path), new File(path)); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } fireNextOnJavaFXThread(); } }); } else if(State.FAILED == newValue){ fireNextOnJavaFXThread(); } } }); setScene(new Scene(webView, getWidth(), getHeight())); } private void fireNext() { if(queue.isEmpty()){ current = null; return; } try { current = queue.take(); if(current.get(COMMAND).equals(SHUTDOWN)){ System.out.println("all done"); System.exit(0); } else{ System.out.println("loading " + current.get(URL)); webView.getEngine().load(current.get(URL)); } } catch (InterruptedException e) { e.printStackTrace(); } } private void hideScrollBar() { webView.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() { @Override public void onChanged(Change<? extends Node> change) { Set<Node> deadSeaScrolls = webView.lookupAll(".scroll-bar"); for (Node scroll : deadSeaScrolls) { scroll.setVisible(false); } } }); } public WebCapture load(final String url, final String path) throws InterruptedException { queue.put(ImmutableMap.of(COMMAND, "load", URL, url, PATH, path)); Platform.runLater(new Runnable() { @Override public void run() { if(current == null){ fireNext(); } } }); return this; } private void shutdownAfterAllRequestsProcessed() throws InterruptedException { queue.put(ImmutableMap.of(COMMAND, SHUTDOWN)); if(queue.isEmpty()){ Platform.exit(); // sorry sorry anti-pattern but for standalone program this should be fine... System.exit(0); } } private void fireNextOnJavaFXThread() { Platform.runLater(new Runnable() { @Override public void run() { fireNext(); } }); } public final static String getExtension(final String fname) { int dotNum = fname.lastIndexOf('.'); if (dotNum < 0) { return ""; } else { return fname.substring(dotNum + 1); } } }
Here is some trivial utility methods used in above web page capture class.
// AwtUtils public static BufferedImage captureView(Component component) throws IOException { BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics graphics = image.createGraphics(); component.paint(graphics); graphics.dispose(); image.flush(); return image; } // JFrameFactory public static JFrame createExitJFrame(Component component, String title, int width, int height){ JFrame frame = new JFrame(title); frame.getContentPane().add(component, BorderLayout.CENTER); frame.setPreferredSize(new Dimension(width, height)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); return frame; } // IOUtils public final static String getExtension(final String fname) { int dotNum = fname.lastIndexOf('.'); if (dotNum < 0) { return ""; } else { return fname.substring(dotNum + 1); } }
Pending Task
- Hide scroll bar I read this stackoverflow post and calling setVisible(false) method.
- Is there any better way to trigger capturing page? Sometimes capturing is started before rendering is fully done.
it works very well but only one thing I would not prefer is white space corresponding to an original scroll bar place rendered.
Looks the best way to avoid this is defining custom css for the scroll bar.
So I added silly sleep call before capturing the web page.
コメント
Thank's
Naima
do you get solution for Thread.sleep(waitMills); ??
Thank's