I'm looking for a way to load up a page and save the rendering as an image just as you would do with CutyCapt (QT + webkit EXE to do just that).
At the moment and without JavaFX, I do it by calling an external process from java and rendering to file than loading that file into an ImageBuffer... Neither very optimized nor practical and even less cross platform...
Using JavaFX2+ I tried playing with the WebView & WebEngine:
public class WebComponentTrial extends Application {
private Scene scene;
#Override
public void start(final Stage primaryStage) throws Exception {
primaryStage.setTitle("Web View");
final Browser browser = new Browser();
scene = new Scene(browser, 1180, 800, Color.web("#666970"));
primaryStage.setScene(scene);
scene.getStylesheets().add("webviewsample/BrowserToolbar.css");
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
class Browser extends Region {
static { // use system proxy settings when standalone application
// System.setProperty("java.net.useSystemProxies", "true");
}
final WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
public Browser() {
getStyleClass().add("browser");
webEngine.load("http://www.google.com/");
getChildren().add(browser);
}
#Override
protected void layoutChildren() {
final double w = getWidth();
final double h = getHeight();
layoutInArea(browser, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
}
#Override
protected double computePrefWidth(final double height) {
return 800;
}
#Override
protected double computePrefHeight(final double width) {
return 600;
}
}
There is a deprecated method : renderToImage in Scene (see links below) that would do something that comes close and with which I'd might be able to work but it is deprecated...
It being deprecated in JavaFX seems to mean that there is no javadoc advertising the replacement method and because I don't have access to the code, I cannot see how it was done...
Here are a couple of sites where I found some information but nothing to render a webpage to an image:
http://tornorbye.blogspot.com/2010/02/how-to-render-javafx-node-into-image.html
canvasImage and saveImage(canvasImage, fc.getSelectedFile()) from this one :
http://javafx.com/samples/EffectsPlayground/src/Main.fx.html
Others:
http://download.oracle.com/javafx/2.0/webview/jfxpub-webview.htm
http://download.oracle.com/javafx/2.0/get_started/jfxpub-get_started.htm
http://fxexperience.com/2011/05/maps-in-javafx-2-0/
I have done this by launching JavaFX WebView on a Swing JFrame and JFXPanel. And then I use the paint() method on JFXPanel once the WebEngine status is SUCCEEDED.
You may follow this tutorial to make a WebView: Integrating JavaFX into Swing Applications
The code below demonstrate how I capture the rendered screen from JFXPanel.
public static void main(String args[]) {
jFrame = new JFrame("Demo Browser");
jfxPanel = new JFXPanel();
jFrame.add(jfxPanel);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
browser = new FXBrowser();
jfxPanel.setScene(browser.getScene());
jFrame.setSize((int) browser.getWebView().getWidth(), (int) browser.getWebView().getHeight());
browser.getWebEngine().getLoadWorker().stateProperty().addListener(
new ChangeListener() {
#Override
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
State oldState = (State) oldValue;
State newState = (State) newValue;
if (State.SUCCEEDED == newValue) {
captureView();
}
}
});
}
});
}
});
}
private static void captureView() {
BufferedImage bi = new BufferedImage(jfxPanel.getWidth(), jfxPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bi.createGraphics();
jfxPanel.paint(graphics);
try {
ImageIO.write(bi, "PNG", new File("demo.png"));
} catch (IOException e) {
e.printStackTrace();
}
graphics.dispose();
bi.flush();
}
For JavaFX 2.2 users there is a much more clean and elegant solution based on JavaFX Node snapshot. You can take a look to JavaFX node documentation at:
http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html
Here is an example of taking a capture from a WebView Node (as webView)
File destFile = new File("test.png");
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(snapshot, null);
try {
ImageIO.write(renderedImage, "png", destFile);
} catch (IOException ex) {
Logger.getLogger(GoogleMap.class.getName()).log(Level.SEVERE, null, ex);
}
I had no problems with this solution, and the webview is rendered perfectly in the PNG according to the node size. Any JavaFx node can be rendered and saved to a file with this method.
Hope this help!
Workaround posted by JavaFX engineers: Snapshot does not work with (invisible) WebView nodes.
When taking a snapshot of a scene that contains a WebView node, wait for at least 2 frames before issuing the snapshot command. This can be done by using a counter in an AnimationTimer to skip 2 pulses and take the snapshot on the 3rd pulse.
Once you have got your snapshot, you can convert the image to an awt BufferedImage and encode the image to format like png or jpg, using ImageIO.
Related
I am trying to make a program for visual analyzing Fractal sets. I choose Processing 3 as drawing library and JavaFX for the user interface. There are some screenshots of the current state:
My GUI:
there is Launcher code:
import Graphics.Canvas2D;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import processing.core.PGraphics;
import java.io.IOException;
public class Launcher extends Application {
private static Stage primaryStage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = loadFXML("MainUI.fxml");
Scene scene = new Scene(root, 500, 400);
primaryStage.setTitle("Fractal Analyzer");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setMaximized(true);
Launcher.primaryStage = primaryStage;
}
#Override
public void init() {
}
#Override
public void stop() {
System.exit(0);
}
public static Stage getPrimaryStage() {
return primaryStage;
}
public void setCanvas(Canvas2D canvas){
}
private Parent loadFXML(String path) {
try {
return FXMLLoader.load(getClass().getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
System.exit(1);
return null;
}
}
Testing fractal PAplet:
There is a code of this PAplet:
package Fractal;
import processing.core.PApplet;
public class SirpenskiTriangle extends PApplet {
public static void main(String[] args) {
PApplet.main("Fractal.SirpenskiTriangle");
}
public void settings() {
size(640, 640);
smooth();
if (frame != null) {
frame.setResizable(true);
}
}
public void draw() {
drawTriangle(new Position(300, 20), new Position(620, 620), new Position(20, 620), 0);
noLoop();
scale(10f);
}
public void setup(){}
public void drawTriangle(Position top, Position right, Position left, int depth) {
if (depth > 10) return;
line(top.x, top.y, right.x, right.y);
line(right.x, right.y, left.x, left.y);
line(left.x, left.y, top.x, top.y);
drawTriangle(top, top.middleWith(right), top.middleWith(left), depth + 1);
drawTriangle(top.middleWith(left), left.middleWith(right), left, depth + 1);
drawTriangle(top.middleWith(right), right, left.middleWith(right), depth + 1);
}
class Position {
final float x;
final float y;
Position(float x, float y) {
this.x = x;
this.y = y;
}
Position middleWith(Position other) {
return new Position((x + other.x) / 2, (y + other.y) / 2);
}
}
}
Is there any way to put processing PAplet into JavaFX scene like canvas or something similar?
I hope it can work like this, but this code is invalid:
I have devised two approaches: in the first, we bypass Processing's JavaFX stage creation and point Processing to draw into a JavaFX stage loaded from an FXML file; in the second, we replace Processing's default JavaFX scene with one loaded from an FXML file during runtime.
1. Launching from an FXML
With the first approach we launch the application like we would a JavaFX app (using Application.launch(Launcher.class);), completely bypassing Processing's JavaFX stage creation code.
You'll have to download a slightly modified core.jar for this approach to work, where I've changed the visibility of a few members of the PSurfaceFX and PGraphicsFX2D classes from Protected to Public. The changes allow us to launch JavaFX from our own ... extends Application class, while maintaining access to the members that Processing needs to set during the launch to function.
Processing 3 crashes in FX2D mode when the JDK in use is above Java 8, so I've also made a working version for 8+, since the FXML files usually need at least Java 9 to work.
Download core.jar (Java 8 & below)
Download core.jar (Above Java 8)
This is the FXML file I am working with in this example:
With the modified core.jar added to your project's classpath, override initSurface() in your PApplet class with the following snippet. With this code, we bypass the PApplet's call to initFrame() - this is where processing creates its own JavaFX stage, which we do not want it to do.
#Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
Launcher.surface = fxSurface;
new Thread(new Runnable() {
public void run() {
Application.launch(Launcher.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
Set the PApplet's renderering mode to FX2D like so:
#Override
public void settings() {
size(0, 0, FX2D);
}
Put the following, or similar, in your Launcher class. In this example, I have manually found the Node that I want to add the canvas object into. There are better, more programmatic, ways of doing this (such as .lookup() using the fx:id of the desired node -- this can be defined in the FXML file). I have also bound the dimensions of the canvas to those of its parent, so when the divisor separating the Master and View panes is dragged, the Processing canvas resizes accordingly.
public class Launcher extends Application {
public static PSurfaceFX surface;
#Override
public void start(Stage primaryStage) throws Exception {
Canvas canvas = (Canvas) surface.getNative(); // boilerplate
GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); // boilerplate
surface.fx.context = graphicsContext; // boilerplate
primaryStage.setTitle("FXML/Processing");
VBox root = FXMLLoader.load(new File("c:/Users/Mike/desktop/test.fxml").toURI().toURL());
SplitPane pane = (SplitPane) root.getChildren().get(1); // Manually get the item I want to add canvas to
AnchorPane pane2 = (AnchorPane) pane.getItems().get(0); // Manually get the item I want to add canvas to
pane2.getChildren().add(canvas); // Manually get the item I want to add canvas to
canvas.widthProperty().bind(pane2.widthProperty());
canvas.heightProperty().bind(pane2.heightProperty());
Scene scene = new Scene(root, 800, 800);
primaryStage.setScene(scene);
primaryStage.show();
surface.stage = primaryStage; // boilerplate
}
}
This is the result:
Also see this Github project -- a basic project showing how a Processing sketch and a FXML JavaFX stage may be integrated using this first approach, but includes a JavaFX Controller to populate #FXMLannotated fields (providing an easy way to first get, and then reference, JavaFX objects in code).
2. Launching, then loading a FXML
This approach works with vanilla Processing. Here, we launch Processing like normal and then replace the default scene with new scene loaded from an FXML file during runtime. This is a simpler approach (and doesn't require using a modified .jar!) but will make JavaFX/Processing interoperability more difficult because we can't use a JavaFX Controller to get fields via FXML injection.
Example PDE code:
import java.util.Map;
import java.nio.file.Paths;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import processing.javafx.PSurfaceFX;
public void setup() {
size(800, 800, FX2D);
strokeWeight(3);
}
protected PSurface initSurface() {
surface = (PSurfaceFX) super.initSurface();
final Canvas canvas = (Canvas) surface.getNative();
final Scene oldScene = canvas.getScene();
final Stage stage = (Stage) oldScene.getWindow();
try {
FXMLLoader loader = new FXMLLoader(Paths.get("C:\\path--to--fxml\\stage.fxml").toUri().toURL()); // abs path to fxml file
final Parent sceneFromFXML = loader.load();
final Map<String, Object> namespace = loader.getNamespace();
final Scene newScene = new Scene(sceneFromFXML, stage.getWidth(), stage.getHeight(), false,
SceneAntialiasing.BALANCED);
final AnchorPane pane = (AnchorPane) namespace.get("anchorPane"); // get element by fx:id
pane.getChildren().add(canvas); // processing to stackPane
canvas.widthProperty().bind(pane.widthProperty()); // bind canvas dimensions to pane
canvas.heightProperty().bind(pane.heightProperty()); // bind canvas dimensions to pane
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.setScene(newScene);
}
}
);
}
catch (IOException e) {
e.printStackTrace();
}
return surface;
}
public void draw() {
background(125, 125, 98);
ellipse(200, 200, 200, 200);
line(0, 0, width, height);
line(width, 0, 0, height);
}
Result:
…using this FXML file:
To make it work, you have to launch your Processing sketch, not the JavaFX Application.
Simply do
PApplet.main(Launcher.class.getName());
Also thanks so much for your help! I had NO idea how I should use the JavaFX stuff that comes with Processing!
Okay, this is my code, that works! I copied everything and changed the names.
!!!I have not tested this modified code, so don't copy paste everything!!!
The raw principle should definetly work though.
If you still have issues or questions, just comment.
Main
public class Main {
public static void main(String[] args) {
// Your code starts here, and runs Processing.
// This is also, how you normally start Processing sketches.
PApplet.main(Sketch.class.getName());
}
}
Sketch
public class Sketch extends PApplet{
#Override
public void settings() {
size(200, 200, FX2D); // Size doesn't really matter
}
#Override
public void setup() {
}
#Override
public void draw() {
}
// Processing uses this function to determine,
// how to display everything, how to open the canvas...
// We override the code, that would normally open a window with the normal Processing stuff,
// to open start new JavaFX application in a new Thread.
// micycle's code
#Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
// Because the JavaFX App is being launched by reflection,
// we can't pass variables to it via constructor, so
// we have to access it in static context.
// Here, we give JavaFX the surface.
ExampleApp.surface = fxSurface;
// New thread started, so JavaFX and Processing don't interrupt each other.
new Thread(new Runnable() {
public void run() {
// JavaFX way of launching a new Application
Application.launch(ExampleApp.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
}
ExampleApp
public class ExampleApp extends Application {
public Canvas canvas; // The Canvas you will be drawing to
public static PSurfaceFX surface; // The Processing surface
// JavaFX started, this method is being run to set everything up.
#Override
public void start(Stage primaryStage) {
// This sets up the canvas, and the drawing region.
canvas = (Canvas) surface.getNative();
surface.fx.context = canvas.getGraphicsContext2D();
surface.stage = primaryStage;
// I'm just loading my FXML file. You can do all JavaFX stuff via code, if you want
try {
// !!My root Container is a BorderPane!!
BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("application.fxml"));
} catch(IOException e) {
e.printStackTrace();
}
// Getting the Anchor pane, that is in the center of my BorderPane
AnchorPane pane = (AnchorPane) root.getCenter();
// The Anchor pane is being used, so the canvas can fill the parent (Center)
// Canvases don't have a property to fill it's parent, like most Containers do (Because it isn't a container)
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
// Adding the canvas to your App
root.getChildren().add(canvas);
// Launching the Stage
primaryStage.setTitle("Example App");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Okay, since last time , I changed some elements.
The canvas's parent is now just a Pane instead of an AnchorPane.
The FXML won't help you much... It is just a BorderPane with another Pane in it, but alright...
<center>
<VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<Pane maxHeight="1.7976931348623157E308" VBox.vgrow="ALWAYS" />
</children>
</VBox>
So, what I'm doing is taking the Canvas element, Processing creates and just adding it to the Pane.
We are developing video streaming app using JavaFx and JavaCv.
We Have GridPane of 8X8 and each cell contains Pane layout and each pane
containing ImageView.
Every time i am calling play function whenever user drag url to particular
cell.
we have 64 Urls,So we are calling play function 64 times(all 64 url are playing
in background thread).
for(int i=0;i<8;i++){
for(int j=0;j<8;j++){
//grid08(8X8)
Pane p=new Pane();
ImageView im=new ImageView();
Label l=new Label("");
im.fitWidthProperty().bind(p.widthProperty());
im.fitHeightProperty().bind(p.heightProperty());
l.prefWidthProperty().bind(p.widthProperty());
l.prefHeightProperty().bind(p.heightProperty());
p.getChildren().addAll(im,l);
l.setVisible(false);
l.setStyle("-fx-text-fill: #fff; -fx-font-size: 12px");
l.setAlignment(Pos.CENTER);
p.setStyle("-fx-background-color: black;");
grid.add(p,j,i);//grid->instance of gridPane
p.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
if(!l.isVisible()){
event.acceptTransferModes(TransferMode.ANY);
}
}
});
p.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
play(im,event.getDragboard().getString());
}
});
}
}
play(ImageView im,String Url){
new Thread(new Runnable(){
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
while(true){
Frame frame = frameGrabber.grabImage();
Image image=jconverter.convert(frame);
Platform.runlater(new Runnable(){
im.setImage(image);
});
}
}).start();
}
Problem:
We are facing White Screen Problem,after increasing some threshold Width and
Height.
Here i am attaching Snap of my application with different screen size;
Screen1
Since We are facing this problem from long time.
Any Lead will be highly appreciable.
Thanks in advance.
Just a guess here, but when the image view is a different size to the image, the image view has quite a lot of work to do when you call setImage(...) (it has to resize the image). Since you have a lot of these happening, potentially every pulse, it's possible you are submitting more runnables to Platform.runLater(...) than it can handle (i.e. you submit another Platform.runLater(...) before the previous one submitted from the same thread can complete). This will cause rendering issues, because the rendering system can't keep up.
I can't test this, so I'm not sure if it will fix the issue, but it's probably a good idea to only submit a new call to Platform.runLater(...) when the previous one has completed (i.e. you should do this even if it's not the cause of the problem).
You can do this with
void play(ImageView im,String Url){
new Thread(() -> {
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
e.printStackTrace();
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
AtomicReference<Image> imageHolder = new AtomicReference<>(null);
while(true){
Frame frame = frameGrabber.grabImage();
Image image=jconverter.convert(frame);
if (imageHolder.getAndSet(image) == null) {
Platform.runLater(() -> im.setImage(imageHolder.getAndSet(null)));
}
}
}).start();
}
Note that in this code (and the original), all the images are being resized on the same thread (the FX Application Thread). If possible, it would probably be more efficient to resize each image in its own thread. You can't absolutely guarantee this (because there's no way to guarantee the image view doesn't change size between the image being resized in the background thread and the image view displaying the image), so you still need to bind the fitWidth and fitHeight properties, but this should reduce the amount of image resizing that happens on the UI thread, and consequently should improve performance. This relies on there being some mechanism in your frame grabber API (which I'm not familiar with) to resize images.
for(int i=0;i<8;i++){
for(int j=0;j<8;j++){
//grid08(8X8)
Pane p=new Pane();
AtomicReference<Bounds> bounds = new AtomicReference<>(p.getBoundsInLocal());
p.boundsInLocalProperty().addListener((obs, oldBounds, newBounds) -> bounds.set(newBounds));
ImageView im=new ImageView();
Label l=new Label("");
im.fitWidthProperty().bind(p.widthProperty());
im.fitHeightProperty().bind(p.heightProperty());
l.prefWidthProperty().bind(p.widthProperty());
l.prefHeightProperty().bind(p.heightProperty());
p.getChildren().addAll(im,l);
l.setVisible(false);
l.setStyle("-fx-text-fill: #fff; -fx-font-size: 12px");
l.setAlignment(Pos.CENTER);
p.setStyle("-fx-background-color: black;");
grid.add(p,j,i);//grid->instance of gridPane
p.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
if(!l.isVisible()){
event.acceptTransferModes(TransferMode.ANY);
}
}
});
p.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
play(im,bounds,event.getDragboard().getString());
}
});
}
}
and
void play(ImageView im, AtomicReference<Bounds> bounds, String Url){
new Thread(() -> {
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
e.printStackTrace();
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
AtomicReference<Image> imageHolder = new AtomicReference<>(null);
while(true){
Frame frame = frameGrabber.grabImage();
Bounds imgBounds = bounds.get();
double width = imgBounds.getWidth();
double height = imgBounds.getHeight();
// resize frame to width, height....
Image image=jconverter.convert(frame);
if (imageHolder.getAndSet(image) == null) {
Platform.runLater(() -> im.setImage(imageHolder.getAndSet(null)));
}
}
}).start();
}
I am trying to take input from webcam and draw it in the canvas.
For this I wrote following block of code:
void addImageToCanvas(Image img){
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
}
If I pass only one Image in that function by hand, it works.
If I pass two Images then it only draws the last one.
But I am implementing thread and calling it continuously from the thread. The function is then comes to stand still on the line:
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
The part of code from where the method is invoked:
public void run() {
try {
FrameGrabber grabber = new VideoInputFrameGrabber(cameraId);
CvMemStorage storage = CvMemStorage.create();
grabber.start();
IplImage grabbedImage;
BufferedImage bufImg;
int counter = 0;
while (primaryCameraChosen != null) {
grabbedImage = grabber.grab();
bufImg = grabbedImage.getBufferedImage();
addImageToCanvas(SwingFXUtils.toFXImage(bufImg, null));
try{
Thread.sleep(10000);
} catch(InterruptedException e){}
}
grabber.stop();
grabber.release();
} catch (Exception ex) {
}
}
Could you please tell me what I am doing wrong here? What is the solution? Thanks.
So you have actually two problems
1) If I pass two Images then it only draws the last one.
You are drawing images to the same X, Y coordinates, with the same height and width. The result is (like in real life if your paint something on an existing painting) that only the last drawing is visible as it hides the first one. This is the expected working.
Update after comment from OP:
The problem with this line: gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight()); is, that at the first Image it tries to draw the Image using the actual height and width of the Canvas, but most probably at this moment the Canvas itself was not drawn, so the actual height and width are both zero, therefore the picture gets drawn with zero width and height.
Two possible solutions are:
1) Only start the Thread when the Stage that contains the Canvas is actually showing. This will ensure that the Canvas has the correct height and width even at the first picture.
2) Listen to the width and height change of the canvas and redraw the Image if needed. This will ensure, that whenever the canvas is resized, the Image is redrawn (it has the advantage that the picture fits to the screen all the time so this one is the suggested way)
The example contains both approaches.
2) But I am implementing thread and calling it continuously from the
thread. The function is then comes to stand still on the line
We shall see the full code what can be wrong, but until you update the post to include every part that is needed to detect the problem, here is an example which updates a canvas with an Image from a background thread every 3 seconds:
public class Main extends Application {
Canvas canvas;
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
canvas = new Canvas();
Pane pane = new Pane();
pane.getChildren().add(canvas);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
root.setCenter(pane);
Thread thread = new Thread(new Runnable() {
private ObjectProperty<Image> imageToBeDrawn = new SimpleObjectProperty<Image>(new Image(getClass().getResource("a.jpg").toExternalForm()));
#Override
public void run() {
// Option 2: Listen to the width and height property change of the canvas and redraw the image
canvas.widthProperty().addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
canvas.heightProperty().addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
imageToBeDrawn.addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
while(true){
Random rand = new Random();
if(rand.nextBoolean())
imageToBeDrawn.set(new Image(getClass().getResource("a.jpg").toExternalForm()));
else
imageToBeDrawn.set(new Image(getClass().getResource("b.jpg").toExternalForm()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
primaryStage.setScene(scene);
// Option 2: start the Thread only when the stage is showing
// primaryStage.showingProperty().addListener(new ChangeListener<Boolean>() {
//
// #Override
// public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// if(newValue)
// thread.start();
//
// }
// });
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
void addImageToCanvas(Image img){
Platform.runLater(new Runnable(){
#Override
public void run() {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
}});
}
public static void main(String[] args) {
launch(args);
}
}
Hi i'm using JavaFx WebView to create Screenshot of HTML pages and it works fine but i wanted to know is it possible to do this without launching the application in Graphical Windows!! I mean aren't there any more lightweight method to get the screenshot then this:
public class WebViewSample extends Application {
private Scene scene;
#Override
public void start(Stage stage) {
// create scene
scene = new Scene(new Browser(snapshot), 750, 500, Color.web("#666970"));
stage.setScene(scene);
// show stage
stage.show();
}
WritableImage snapshot;
public static void main(String[] args) {
launch(args);
System.err.println("launched!");
}
}
class Browser extends Region {
final ImageView selectedImage = new ImageView();
final WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
private final WritableImage snapshotImage;
public Browser(WritableImage snapshot) {
this.snapshotImage= snapshot;
// process page loading
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> ov,
State oldState, State newState) {
if (newState == State.SUCCEEDED) {
WritableImage newSnapshot = browser.snapshot(null, snapshotImage);
File file = new File("test2.png");
RenderedImage renderedImage = SwingFXUtils.fromFXImage(newSnapshot, null);
try {
ImageIO.write(renderedImage, "png", file);
} catch (IOException e1) {
e1.printStackTrace();
}
System.exit(0);
}
}
}
);
// load the home page
webEngine.load("http://localhost/");
//add components
getChildren().add(browser);
}
}
For JavaFX 2.2 and below, there's not functionality of the kind.
Headless JavaFX applications are currently not possible, and a main JavaFX thread is mandatory.
The best you can do is read upon several workarounds for achieving this.
Related StackOverflow questions:
JavaFX for server-side image generation
Generating image at server side using Java FX
JavaFx in headless mode
How to test JavaFX 2 in a headless environment?
i am working with javafx 2.0 and netbean 7.1, I am facing an problem when doing a drag and drop on a image over a ImageView, .i kept image as a source(one image) and 2 target point(2 box as target point).when trying to drag an image first time, its working fine and after sources image is entered in to target box.and again trying to drag the image, following error is trown "java.lang.IllegalArgumentException: Wrong byte buffer size 18x15 [1080] != 0"
Once the image is moved to the destination object, i need to set the listener to change it as source, i feel that its throwing error in this place..
code am using
public class DragandDropEx extends Application {
/**
* #param args the command line arguments
*/
GridPane Board;
ImageView deactivateImageView = new ImageView();
ImageView newImageView = new ImageView();
final Rectangle target = new Rectangle(0, 0, 50, 50);
final Rectangle target2 = new Rectangle(0, 0, 50, 50);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
target.setFill(Color.CHOCOLATE);
target2.setFill(Color.BLUE);
Image image = new Image(getClass().getResourceAsStream("triangle.png"));
getDeactivateImageView().setImage(image);
Board = new GridPane();
primaryStage.setTitle("Drag and Drop");
createSource(getDeactivateImageView());
target.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent events) {
events.acceptTransferModes(TransferMode.MOVE);
events.consume();
createTargetDrop(target,0,8);
}
});
target2.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent events) {
events.acceptTransferModes(TransferMode.MOVE);
events.consume();
createTargetDrop(target2,0,9);
}
});
Board.add(getDeactivateImageView(), 0, 1);
Board.add(target, 0, 8);
Board.add(target2, 0, 9);
StackPane root = new StackPane();
root.getChildren().add(Board);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
private void createSource(final ImageView imageView) {
imageView.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent events) {
Dragboard storeImage =imageView.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.putImage(imageView.getImage());
storeImage.setContent(content); **// here i am getting error**
events.consume();
}
});
}
private void createTargetDrop(final Rectangle target,final int xCordination,final int yCordination) {
target.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
Dragboard db = event.getDragboard();
Image dragedImage = db.getImage();
getNewImageView().setImage(dragedImage);
getDeactivateImageView().setVisible(false);
setDeactivateImageView(getNewImageView());
Board.add(getDeactivateImageView(),xCordination,yCordination );
event.consume();
createSource(getDeactivateImageView()); // setting listener to new image
}
});
}
}
I guess its about references. I mean you are using same reference to different place. Maybe you should use clone of object. I did not look to code deeply but it looks like you are trying to add same object to different place.
Which JavaFX version do you use?
You may encounted issue, which were fixed in 2.1: http://javafx-jira.kenai.com/browse/RT-14348
If you were working with 2.0.x version you can try developers version of FX2.1: http://www.oracle.com/technetwork/java/javafx/downloads/devpreview-1429449.html
I would recommend JavaFX because it includes scenebuilder (a GUI) to create your GUI. You can drag and drop GUI elements and size them on a canvas. Then, this creates an FXML file that connects to a Java control file.
Check out scenebuilder here: http://www.oracle.com/technetwork/java/javase/downloads/javafxscenebuilder-info-2157684.html
There is a bit of a learning curve on connecting the FXML to Java code, but it is worth it if you want to graphically design your GUI.