I want to take a snapshot of a website without any video, so it's just plain text with some css and pictures.
I am using a WebView (which is the scene of the JFXPanel) to load the website and then save it via
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
(where "scene" is the scene of the JFXPanel)
but the saved image just displays a part of the website, instead of the complete content (see picture).
How do I ensure/enforce that the dimensions of the image matches the dimensions of the JFXPanel content and everthing is visible?
Complete Code:
package renderer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class HtmlRenderer extends Application {
private JFXPanel jfxPanel;
private WebView webView;
public void start(Stage stage) {
jfxPanel = new JFXPanel();
webView = new WebView();
webView.getEngine().getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
HtmlRenderer.this.toImage(jfxPanel.getScene());
try {
Platform.exit();
HtmlRenderer.this.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
jfxPanel.setScene(new Scene(webView));
this.updateView("http://www.stackoverflow.com/");
}
private void toImage(Scene scene) {
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
// TODO: save in matching dir using proper filename
File file = new File("D:/workspace/SiteChecker/test.png");
try {
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
} catch (IOException e) {
// TODO: exception handling
}
}
public void updateView(String url) {
webView.getEngine().load(url);
}
private void reloadView() {
webView.getEngine().reload();
}
}
So I found a solution, but it's far from perfect and not really perfomant.
However, nothing else works for me.
The trick is to load the website once, determine width and height of the site. The second time I set the preferred size of the Webview to the determined values and load the website again with the new size. I think it's because the first time only the visible part is rendered.
The width and height can be determined with javascript, e.g.:
private int getPageWidth(WebView webView) {
String script = "Math.max(" +
"document.body.scrollWidth, document.body.offsetWidth," +
"document.documentElement.clientWidth, document.documentElement.scrollWidth," +
"document.documentElement.offsetWidth );";
WebEngine engine = webView.getEngine();
int maxWidth = (int) engine.executeScript(script);
return maxWidth;
}
For some reason some websites have a funny end/bottom i.e. there is a lot of empty space.
Related
hope you're doing well.
Actually, i'm facing a problem when trying to rotate an image using Core.rotate Class of OpenCV.
the colors of the image pixels keep changing every time i click the rotation Button.
Can anyone please help me fixing this problem ?
Thanks in advance.
I've tried to change the colors of the image using Imgproc.cvtColor but it didn't work.
This is the code :
`
package application;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class SampleController {
#FXML
private ImageView myimageview;
#FXML
private Button showimageButton, rotateButton;
#FXML
void rotateButtonClicked(ActionEvent event) throws Exception {
BufferedImage image = SwingFXUtils.fromFXImage(myimageview.getImage(), null);
Mat src = BufferedImage2Mat(image);
Core.rotate(src, src, Core.ROTATE_90_CLOCKWISE);
image = Mat2BufferedImage(src);
try {
Image converted_image = SwingFXUtils.toFXImage(image, null);
myimageview.setImage(converted_image);
}
catch (Exception e) {
System.out.println(e);
}
}
public Mat BufferedImage2Mat(BufferedImage image) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", byteArrayOutputStream);
byteArrayOutputStream.flush();
return Imgcodecs.imdecode(new MatOfByte(byteArrayOutputStream.toByteArray()), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
}
static BufferedImage Mat2BufferedImage(Mat matrix)throws Exception {
MatOfByte mob=new MatOfByte();
Imgcodecs.imencode(".jpg", matrix, mob);
byte ba[]=mob.toArray();
BufferedImage bi=ImageIO.read(new ByteArrayInputStream(ba));
return bi;
}
#FXML
void showimageButtonClicked(ActionEvent event) {
Image im = new Image("file:///C:\\Users\\poste\\eclipse-workspace\\dragImage\\Images\\temp.jpg");
myimageview.setImage(im);
}
}
`
I'm working with JavaFX, where I have written a code that contains display Image, through I want to display the image on my scene, but it's not working. The image doesn't display.
When I'm using the getAbsolutePath() it also displays an error. Although 70% of my coding is done, I'm just stuck with displaying images on the scene (without uploading it).
Here is the code I'm working with:
package application;
import java.util.HashMap;
import java.io.File;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.stage.FileChooser;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Circle;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class Main extends Application {
public ImageView iv;
private GridPane gridpane;
//Image
Image img = new Image("application/PA_Image.jpg");
#Override
public void start(Stage primaryStage) {
try {
//Upload Image Button
final Button button = new Button("Display Image");
//FileChooser
final FileChooser fileChooser = new FileChooser();
button.setOnAction(e -> {
File selectedFile = fileChooser.showOpenDialog(primaryStage);
if (selectedFile != null) {
System.out.println(selectedFile.getAbsolutePath());
Image image = new Image(selectedFile.getAbsolutePath());
iv = new ImageView(image);
//Image uploadedImage = new Image(selectedFile.getAbsolutePath());
}
});
final StackPane stac = new StackPane();
stac.getChildren().add(button);
Scene scene = new Scene(stac,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
So, what I need, is to display images on my scene by clicking the button and selecting an image.
The Image() constructor expects a URI and not an Absolute path.
There are two ways to approach this.
Turn your absolutePath in a URI by adding the required prefix file://
Use InputStream which holds the Image data and you don't have to care about the Path after it has been created. I like this way better.
Example:
button.setOnAction(e -> {
File selectedFile = fileChooser.showOpenDialog(primaryStage);
if (selectedFile != null) {
System.out.println(selectedFile.getAbsolutePath());
final InputStream targetStream; // Creating the InputStream
try
{
targetStream = new DataInputStream(new FileInputStream(selectedFile));
Image image = new Image(targetStream);
iv = new ImageView(image);
} catch (FileNotFoundException fileNotFoundException)
{
fileNotFoundException.printStackTrace();
}
}
});
There are also some other problems with your code.
Create your ImageView and initialize it. Not when the button is clicked but right at the beginning
Add the ImageView to your Scene
Do not create new ImageView every time the button is clicked, just change the Image.
This is my code based on yours:
public ImageView iv = new ImageView();
private GridPane gridpane;
#Override
public void start(Stage primaryStage) {
try {
//Upload Image Button
final Button button = new Button("Display Image");
//FileChooser
final FileChooser fileChooser = new FileChooser();
button.setOnAction(e -> {
File selectedFile = fileChooser.showOpenDialog(primaryStage);
if (selectedFile != null) {
final InputStream targetStream;
try {
targetStream = new DataInputStream(new FileInputStream(selectedFile));
Image image = new Image(targetStream);
iv.setImage(image); // Set Image
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
}
}
});
final StackPane stac = new StackPane();
stac.getChildren().add(button);
stac.getChildren().add(iv); // Add ImageView
Scene scene = new Scene(stac, 1600, 800);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
I have a file browser component in my app (TableView), and I load the icons of files via FileSystemView.getFileSystemView().getSystemIcon(). Now, this is quite slow as I also need to convert the icon to BufferedImage, and that to JavaFX Image. So I needed to push this all to background thread(s). I created an IconLoadingTask, that loads a single icon - and during updateItem() of TableView I push these IconLoadingTasks to ExecutorService. It works, but could be improved.
The problem I have is that if the folder has lots of icons, and the user quickly drags the scrollbar, lots of "redundant" icons are loaded, stalling the thread(s) and resulting in often not seeing the icons that matter at the time: the ones that are currently displayed in the TableView.
Anyone have ideas how to solve this issue? I was thinking of accessing the vertical scrollbar of TableView and listening to it (and perhaps only update after scroll has been released), but I'm not sure how to even access the scrollbar... and maybe there's a simpler solution that escapes me.
EDIT:
Well, well. I created a minimal, complete and verifiable example out of my Kotlin code into Java code, and I cannot reproduce the "effect" anymore. It appears to work now as I intended it to in the first place. So it seems I just have to figure what "extra" I am doing in my main application's code. Anyways, here's the apparently working sample code.
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.filechooser.FileSystemView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class IconLoadTest extends Application {
public static class FileEntry {
public Path path;
private Image icon;
public FileEntry(Path path) {
this.path = path;
}
synchronized void setIcon(Image icon) {
this.icon = icon;
}
synchronized Image getIcon() {
return icon;
}
public void setPath(Path path) {
this.path = path;
}
public Path getPath() {
return path;
}
}
class MyTableCell extends TableCell<FileEntry, Path> {
#Override
protected void updateItem(Path item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
setGraphic(null);
return;
}
setText(item.getFileName().toString());
Image icon = dataMap.get(item).getIcon();
if (icon == null) {
setGraphic(null);
executor.execute(new IconLoadingTask(item));
} else {
setGraphic(new ImageView(icon));
}
}
}
class IconLoadingTask extends Task<Void> {
private Path path;
IconLoadingTask(Path path) {
this.path = path;
}
#Override
protected Void call() {
FileEntry entry = dataMap.get(path);
if (entry.icon != null) {
return null;
}
entry.setIcon(getIcon(path.toFile()));
// Refresh currently visible items
Platform.runLater(() -> table.refresh());
return null;
}
}
private TableView<FileEntry> table = new TableView<>();
// Table data
private HashMap<Path, FileEntry> dataMap = new HashMap<>();
private List<FileEntry> data = FXCollections.observableArrayList();
private ExecutorService executor = Executors.newFixedThreadPool(1);
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add(table);
TableColumn<FileEntry, Path> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("path"));
table.getColumns().add(nameCol);
nameCol.setCellFactory(tableColumn -> new MyTableCell());
// Sort so that dirs come first
nameCol.setComparator((o1, o2) -> {
if (Files.isDirectory(o1) && !Files.isDirectory(o2)) {
return -1;
} else if (!Files.isDirectory(o1) && Files.isDirectory(o2)) {
return 1;
} else {
return o1.toString().toLowerCase().compareTo(o2.toString().toLowerCase());
}
});
// Set to a directory with lots of files (e.g. System32 on Windows)
String directory = "C:\\Windows\\System32\\";
// Load files from directory, and create entries for table
Path dir = Paths.get(directory);
List<Path> files = listContents(dir);
for (Path p : files) {
FileEntry entry = new FileEntry(p);
data.add(entry);
dataMap.put(p, entry);
}
table.setItems((ObservableList<FileEntry>) data);
// Sort
table.getSortOrder().add(table.getColumns().get(0));
table.sort();
// Display the app
Scene scene = new Scene(root, 600, 480);
primaryStage.setTitle("Icon Background Loading");
primaryStage.setScene(scene);
primaryStage.show();
}
// Gets directory contents
private static List<Path> listContents(Path directory) {
ArrayList<Path> paths = new ArrayList<>();
try {
Files.newDirectoryStream(directory).forEach(paths::add);
} catch (IOException ex) {
ex.printStackTrace();
}
return paths;
}
// Gets a system icon for a file
private static Image getIcon(File file) {
Icon ico = FileSystemView.getFileSystemView().getSystemIcon(file);
java.awt.Image awtImage = ((ImageIcon) ico).getImage();
BufferedImage bImg;
if (awtImage instanceof BufferedImage) {
bImg = (BufferedImage) awtImage;
} else {
bImg = new BufferedImage(
awtImage.getWidth(null),
awtImage.getHeight(null),
BufferedImage.TYPE_INT_ARGB
);
Graphics graphics = bImg.createGraphics();
graphics.drawImage(awtImage, 0, 0, null);
graphics.dispose();
}
return SwingFXUtils.toFXImage(bImg, null);
}
public static void main(String[] args) {
Application.launch(IconLoadTest.class, args);
}
}
I have object of javafx.scene.image.Image class. How can I print it on printer using javafx8? Please, note, that I don't want to print some node, for example ImageView. I need to print image. Although it's very simple question I can't find answer in internet.
The only code I found is:
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
boolean success = job.printPage(node);
if (success) {
job.endJob();
}
}
However it is about printing the node.
Problem
javafx.print.PrinterJob only prints Node and it's subclasses. Image isn't a subclass of Node. So you have to wrap it in a Node (ImageView) or print from plain Java.
Difference of JavaFX-PrinterJob and AWT-PrinterJob
The main difference is, that the JavaFX PrinterJob was introduced for usage with Node objects. It has set some usefull things as a JavaFX Property like the Jobstatus or the Printer itself. And it is more thread safe as the older AWT PrinterJob. The AWT PrinterJob can print mostly anything you want like Strings, Images, Arc's, etc., because it takes an AWT Graphics Object to draw things on the page.
Solution
Before you can use the plain Java solution, you have to convert your FX-Image to a BufferedImage with SwingFXUtils.fromFXImage(). But there is a bug with *.jpg Files, as described here: https://stackoverflow.com/a/30995307/4170073
The Minimal, Complete, and Verifiable example down below shows a working solution:
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ImagePrinter extends Application {
#Override
public void start(Stage primaryStage) {
Image image = new Image("http://www.gnu.org/graphics/gnu-head.png");
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
Button btn = new Button();
btn.setText("Print Image");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
printImage(bufferedImage);
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Image Printer");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void printImage(BufferedImage image) {
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(new Printable() {
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
// Get the upper left corner that it printable
int x = (int) Math.ceil(pageFormat.getImageableX());
int y = (int) Math.ceil(pageFormat.getImageableY());
if (pageIndex != 0) {
return NO_SUCH_PAGE;
}
graphics.drawImage(image, x, y, image.getWidth(), image.getHeight(), null);
return PAGE_EXISTS;
}
});
try {
printJob.print();
} catch (PrinterException e1) {
e1.printStackTrace();
}
}
}
I want to snap a picture of a webpage using the JavaFX "snapshot" method. My problem is that the WebView doesn't load the webpage before I snap the snapshot. Putting the call to snapshot inside a listener that tests when the webview's getLoadWorker succeeds doesn't work. I get inside the "if" of the listener (see below) before the page actually loads.
To demonstrate this, the following standalone code both attempts to snap the snapshot upon "success" and allows you to click a button to do the same. The button click (marked "BUTTON") works, because you click it after the webpage loads. However the automatic snapshot (marked "AUTOMATIC") produces a blank image, because for some reason the worker state is reaching "SUCCEEDED" before the page is actually loaded.
Can someone tell me what I'm doing wrong with the getLoadWorker listener? Thank you for any help!
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import javax.imageio.ImageIO;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class OpenHTML extends Application {
#Override
public void start(Stage primaryStage) {
final VBox vbox = new VBox(2);
final Button btn = new Button();
WebView webView = new WebView();
webView.setMaxWidth(600);
final WebEngine webEngine = webView.getEngine();
File myHTMLFile = new File("C:/Temp/test.html");
try {
webEngine.load(myHTMLFile.toURI().toURL().toString());
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
webEngine.getLoadWorker().stateProperty()
.addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> ov, State oldState,
State newState) {
if (newState == Worker.State.SUCCEEDED) {
System.out.println("succeeded");
// AUTOMATIC
snapit(vbox, "snapshot_auto.png");
}
}
});
vbox.getChildren().add(btn);
vbox.getChildren().add(webView);
btn.setText("Snap a picture");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// BUTTON
snapit(vbox, "snapshot_by_btn.png");
}
});
Scene scene = new Scene(new Group(vbox), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void snapit(VBox vbox, String filename) {
WritableImage snapshot = vbox.snapshot(new SnapshotParameters(), null);
File file = new File("C:/Temp/" + filename);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(snapshot, null);
try {
ImageIO.write(renderedImage, "png", file);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
OpenHTML.launch(args);
}
}