I have a javaFX 8 application that works perfectly well in jre 1.8.0_45 but today a user came to me with a problem. After some investigation i realised that it was related to him having a more recent release of the jre, specifically 1.8.0_60.
Im reading a GIS shapefile and drawing several Paths to a Group (like 30.000 or more) in my version it was a bit slow but it worked fine. In the latest version the image appeared distorted. The paths where drawn out of place and out of scale in chunks.
correct image generated under jre 1.8.0_45
distorted image generated under jre 1.8.0_60
So i decided to make a little test application to separate the problem from anything else i might be doing. In doing so i found out that the problem wasn't only when drawing Paths on Group but also in drawing to a canvas. Also if somehow i managed to redraw the screen the image would appear fine. For example i have a checkbox binded with the visible property of the Group containing the paths so if i set it to false and then true it takes some time drawing the scene but then it appears fine. The test app is very simple if you press a button you generate a canvas with some squares 10px10p if you press the other you generate more squares and thus the rendering glitch appears.
package gisUI;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Path2DTestApplication extends Application {
private static final int WIDTH = 10;
Group content = new Group();
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("JavaFX 1.8.0_60 rendering test");
Button button = new Button("Canvas 100 x 30");
button.setOnAction(a->doGenerateCanvas(100,30));
Button button2 = new Button("Canvas 100 x 400");
button2.setOnAction(a->doGenerateCanvas(100,400));
Button button3 = new Button("Paths 100 x 30");
button3.setOnAction(a->doGeneratePaths(100,30));
VBox vBox = new VBox();
vBox.getChildren().addAll(new HBox(button,button2,button3),content);
Group root = new Group();
root.getChildren().add(vBox);
Scene scene = new Scene(root,80*WIDTH,60*WIDTH);//, 1500, 800);//, Color.White);
stage.setScene(scene);
stage.show();
}
private void doGeneratePaths(int maxX,int maxY) {
Pane paths = new Pane();
content.getChildren().clear();
Platform.runLater(()->{
for(int i = 0;i<maxX;i++){
for(int j=0;j<maxY;j++){
paths.getChildren().add(getPath(i,j));
}
}
content.getChildren().add(paths);
});
}
private void doGenerateCanvas(int maxX,int maxY) {
content.getChildren().clear();
Platform.runLater(()->{
Canvas canvas = new Canvas(maxX*WIDTH, maxY*WIDTH);
GraphicsContext gc = canvas.getGraphicsContext2D();
int counter =0;
for(int i = 0;i<maxX;i++){
for(int j=0;j<maxY;j++){
gc.setFill(Color. rgb(255,(int) (Math.random()*255),191));
double[] xCoords = new double[]{i*WIDTH, (i+1)*WIDTH, (i+1)*WIDTH, i*WIDTH};
double[] yCoords = new double[]{j*WIDTH,(j)*WIDTH,(j+1)*WIDTH,(j+1)*WIDTH};
gc.fillPolygon(xCoords,yCoords,xCoords.length);
counter++;
}
}
System.out.println(counter +" polygons added");
content.getChildren().add(canvas);
});
}
protected Node getPath(int i,int j) {
Path path = new Path();
path.getElements().add(new MoveTo(i*WIDTH, j*WIDTH));
path.getElements().add(new LineTo((i+1)*WIDTH, j*WIDTH));
path.getElements().add(new LineTo((i+1)*WIDTH, (j+1)*WIDTH));
path.getElements().add(new LineTo(i*WIDTH, (j+1)*WIDTH));
path.getElements().add(new LineTo(i*WIDTH, j*WIDTH));
Paint currentColor =Color. rgb(255,(int) (Math.random()*255),191);
path.setFill(currentColor);
path.setStrokeWidth(0.1);
return path;
}
public static void main(String[] args) {
Application.launch(Path2DTestApplication.class, args);
}
}
Test 1: press button "Canvas 100 x 30", 3000 squares are drawn
correctly and fast
Test 2: press button "Canvas 100 x 400", 40000
squares are drawn showing the glitch.
Test 3: press button "Canvas
100 x 400" again, 40000 squares are drawn correctly and fast.
Test 4: press button "Paths 100 x 30", 3000 squares are drawn showing the
glitch.
Test 5: press button "Paths 100 x 30" again, 3000 squares are drawn correctly.
This is my first question to stakoverflow so apologies if i wasn't clear enough.
If anyone knows if this is a jre error or there is some problem with my code i would be grateful. Also any workarounds would be helpful.
Tks!!
I played around with this on my MacBook Pro (OS X 10.9.5). This has a native Retina LCD display at 2880x1800, with an attached Thunderbolt LCD display at 2560x1440. Note that the native pixel resolution is different between these two displays.
When I run the code posted, I had no issues with any of the canvas rendering. When rendering the "Paths" option for the first time, or switching from "canvas" to "paths", I saw rendering issues similar to those you describe but only if the application was displayed on the thunderbolt display. When moving to the Retina display, everything worked fine.
So the problem appears to be hardware related. This is clearly a bug, and you should report it as mentioned in a comment, but as a workaround you can switch to software rendering using the system property -Dprism.order=sw from the command line:
java -Dprism.order=sw gisUI.Path2DTestApplication
This removed all rendering errors on my system. You should be aware that this may impact performance.
Related
I have a JavaFX application with a ScrollPane that handles resizing of nodes upon scrollEvents. However when the JavaFX stage (or Window) is not focused, I get an odd behaviour that I think might be a JFX bug, though wondering if anyone has encountered or managed to resolve it.
To replicate the issue, if you lose focus on the JavaFX window and perform some scrolling using the mouse-wheel on another window (eg your browser), and then relatively quickly move your mouse back to re-enter the JavaFX window (without clicking, scrolling or focusing upon the JavaFX window) the JavaFX window receives a bunch of scrollEvents despite no mouse-wheel action being performed.
I'm wondering if anyone has encountered this and worked out a way to somehow filter these odd scrollEvents out as it results in some strange zooming action that is unexpected given the lack of mouse-wheel scrolling!
I'm using Java & JavaFX 17 (OpenJFX), see below sample application that demonstrates, thanks!
public class ScrollEventIssueApplication extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane ();
borderPane .setPrefWidth(600);
borderPane .setPrefHeight(600);
Pane content = new Pane();
content.setPrefWidth(1000);
content.setPrefHeight(1800);
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefWidth(700);
scrollPane.setPrefHeight(700);
content.setOnScroll(event -> {
System.out.println("Scroll event received: " + event.getDeltaY());
});
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 1800, 900);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It appears that this may be a platform-dependent feature of the installed pointing device and driver. I could occasionally reproduce the effect on Mac OS X 12 with JavaFX 17, but only when I also accidentally raked an errant finger or two across the mouse's multi-touch surface.
For reference, I tested the following simpler variation. Note code to enumerate the OS and Java version numbers, as well as changes to the viewport dimensions in order to show the scroll bars when set to AS_NEEDED by default.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/** #see https://stackoverflow.com/q/72485336/230513 */
public class ScrollTest extends Application {
private static final double W = 640;
private static final double H = 480;
#Override
public void start(Stage stage) {
Pane content = new StackPane();
content.getChildren().addAll(new Rectangle(W, H, Color.BLUE),
new Circle(Math.min(W, H) / 2, Color.LIGHTBLUE));
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefViewportWidth(2 * W / 3);
scrollPane.setPrefViewportHeight(2 * H / 3);
content.setOnScroll(event -> System.out.println(
"dx dy: " + event.getDeltaX() + " " + event.getDeltaY()));
stage.setScene(new Scene(scrollPane));
stage.show();
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name")
+ " v" + System.getProperty("os.version")
+ "; Java v" + System.getProperty("java.version")
+ "; JavaFX v" + System.getProperty("javafx.runtime.version"));
launch(args);
}
}
I realised the issue was the mouse I was using (a Logitech MX Vertical Advanced Ergonomic) had a "smooth scrolling" option on the mouse wheel that meant to it would "continue" some small scroll events after the mouse wheel had stopped scrolling which were causing this issue. Turning off that option in the Logitech Options application resolved the issue.
I have an application that displays a fullscreen AnchorPane containing an ImageView with its fit height/width set to the size of the pane to make the image it contains fullscreen iself.
I've just upgraded my setup to a 3840x2160 display, which has me using Windows 10 built-in scaling factor for comfort. However, this scaling now messes with the application.
I'm setting the pane dimension using this code, which can also cycle through multiple monitors.
GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] monitors = e.getScreenDevices();
if ((full.isShowing() && monitors.length > 1) || monitor < 0) {
monitor = (monitor + 1) % monitors.length;
GraphicsDevice device = monitors[monitor];
Rectangle bounds = device.getDefaultConfiguration().getBounds();
full.setX(bounds.x);
full.setY(bounds.y);
full.setWidth(bounds.width);
full.setHeight(bounds.height);
}
The pane dimension reports naturally at 3840x2160. However, Windows apply its scaling to the pane and it ends up at an incorrect dimension. My possible fix for this problem are as follow:
Find a way to retrieve the scaling factor and divide the resolution to fit the screen correctly. This is not ideal: the display resolution will be lower than the resolution of the screen.
Find a way to bypass the scaling, effectively telling Windows to not scale the pane, although scaling the rest of the UI is fine.
Anything else?
EDIT: After running a test with setFullScreen on the stage. I've found out this solution does not completely solve the issue. What happens is exactly what I describe in fix #1: The application does appear fullscreen, however this image is displayed at an inferior resolution, only to be upscaled by windows scaling.
EDIT(2): Here's a code sample that highlights the issue
package com.mycompany.mavenproject1;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = new AnchorPane();
Scene scene = new Scene(root);
Rectangle2D bounds = Screen.getPrimary().getBounds();
System.out.println("Bounds:" + bounds);
stage.setX(bounds.getMinX());
stage.setY(bounds.getMinY());
stage.setWidth(bounds.getWidth());
stage.setHeight(bounds.getHeight());
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
On my 3840x2160 screen, the bounds report Bounds:Rectangle2D [minX = 0.0, minY=0.0, maxX=2560.0, maxY=1400.0, width=2560.0, height=1440.0] The pane is visually of the right dimension, but the pane's resolution is lower (windows upscaling at work). Worse the pane does not even show at the expected position of (0, 0) but at roughly (970, -340)... I suppose the position is also affected by the scaling in a very weird way.
Calling stage.setFullScreen(true) moves the pane at the correct position but the pane resolution is still lower than desired. (Also, there is the problem of the full screen exit hint that cannot be disabled, but that's a different matter).
So I noticed something about the framerate / smoothness of my animation. The Animation is somewhat choppy. However, after doing soing testing I noticed that it becomes smooth again as soon as I trigger a resize event, be it only 0.1 px. I have the newest Java installed.
I cant work with vsync unless its opengl and it seems javafx is using a triple bugger or something. Either way the performance is really bad. My windows machine is pretty good and on the newest version.
So after my the call of the show() function I added:
Window.setWidth(Window.getWidth() + 0.1)
Problem was solved but of course I want to know what is going on under the hood and how to I truly solve this without resorting to such primitive hacks?
JavaFX Canvas Double Buffering
My code is below:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.util.Duration;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.control.Button;
public class Gui extends Application{
Stage Window;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Window = primaryStage;
Window.setTitle("Gui Tester");
Group root = new Group();
Rectangle box = new Rectangle(0, 0, 50,50);
box.setFill(Color.GREEN);
KeyValue x = new KeyValue(box.xProperty(), 900);
KeyFrame keyFrame = new KeyFrame(Duration.millis(3000), x);
Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
root.getChildren().add(box);
GridPane grid = new GridPane();
grid.setVgap(8);
root.getChildren().add(grid);
Button newGame = new Button("START NEW GAME");
GridPane.setConstraints(newGame, 1, 1);
Button continueGame = new Button("CONTINUE");
GridPane.setConstraints(continueGame, 1, 2);
grid.getChildren().addAll(newGame, continueGame);
Scene scene = new Scene(root, 1000, 1000);
scene.getStylesheets().add("tester.css");
Window.setScene(scene);
Window.show();
Window.setWidth(Window.getWidth() + 0.1)
}
}
Tested on Mac OS X 10.11.4, Java 1.8.0_92, NVIDIA GeForce 9400, no energy saving.
Both the example shown, using Timeline, and this example, using PathTransition, start out choppy. Subsequently, they both smooth out after about one auto-reverse cycle. Using the -Xint option, which "Runs the application in interpreted-only mode" as shown here, significantly diminishes the initial stuttering, especially after a second run. Delays due to just-in-time compiler overhead may be exaggerated if the application remains confined to a single core or the animation creates a busy loop that fails to reach a safepoint, as illustrated here.
I have a JavaFX application (Swing users input might also help) which is Image gallery basically. It watches a folder, and as soon as any Image is added, the Image is displayed on the screen.
Sooner as more Images got added, the application memory consumption only grew bigger. On profiling, I have observed that on adding an Image (size 1.3 MB), memory consumption increased by about 50 MB. The class that holds Image is a simple ImageView that holds an Image.
Does any one have similar experiences? The behavior is same on Windows & Mac
PS: I know any code here will help, but there is nothing much to be shown. As I said there is a List of ImageView which holds Image. List of ImageView is binded to another List say l1. On detecting an Image, image is added to l1 and so is added to the actual list which is displayed on screen
EDIT:
I just tried a sample code. I have observed that, on every Iamge it loads (2.3 MB in this case), there is a memory increase of 12 MB each:
package side;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test extends Application {
public static void main(String... args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
ScrollBar bar = new ScrollBar();
bar.setOrientation(Orientation.VERTICAL);
final VBox box = new VBox();
Group root = new Group();
root.getChildren().addAll(box, bar);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Layout Sample");
primaryStage.show();
for (int ik = 0; ik < 6; ik++) {
System.out.println("1");
ImageView i = new ImageView();
InputStream is = new FileInputStream(new File("C:\\Users\\Jatin\\Documents\\BarcodeNew\\w.png"));
Image im = new Image(is);
i.setImage(im);
box.getChildren().add(i);
is.close();
}
//r.close();
}
}
Found two issues:
I wasn't closing my stream.
From this
Remember that the file size of a PNG image and the memory consumption of an uncompressed image in memory are very different.
On specifying the size of the Image it all works well.
I know how to draw HTML to a Graphics2D object using Swing's limited built-in HTML support (see http://www.java.net/node/680674), but I need better rendering. Most importantly for this particular chemistry diagram drawing application, Swing's HTML support does not extend to nested sub/superscripts. Better CSS support would be nice too. I don't need image embedding or interactive features such as Javascript or hotlinks.
The HTML text is scaled and rotated and then drawn into a diagram that presumably contains additional text and graphics. The Graphics2D target may be the screen, a printer, or (via iText) a PDF file. I doubt that any solution involving conversion via a BufferedImage or the like can be adequately compact when producing PDF files of publication quality.
My (possibly incorrect) impression is that JavaFX 2.0 does not yet have a solution to this, though it might eventually. (If an earlier version can do this, that might be a solution.) Rewriting the entire application from Swing to JavaFX is not realistic.
This application is free and open source, so any tool it uses probably needs to be freely distributable also. Otherwise, I believe JWebEngine might have fit the bill.
Any help would be appreciated.
You could use a JavaFX WebView node - it has very good HTML tag and css support. You can rotate and scale the WebView node using JavaFX primitives. MathJax can be used within WebView to get high quality equation rendering (if just plain html and css alone doesn't do the job for you). Using JavaFX 2.2, you can take a snapshot of the WebView node and render it to a JavaFX image. You can convert that JavaFX image to an awt BufferedImage using JavaFX 2.2 SwingFXUtils and write it out to a file in many formats using ImageIO.
Here is an example of rendering a piechart node to a png. Depending on the complexity of your html, sometimes an high quality image will compress well to (for example) a png file. In the pichart sample, the 2000x2000 pixel piechart with text and colored gradients saved to a png file of 168kb.
Rewriting the entire application from Swing to JavaFX is not necessary as JavaFX includes the JFXPanel for embedding JavaFX applications inside existing Swing applications. The node snapshot step does not even require the node to be rendered to a screen (it can all be done through memory buffers) - though the JavaFX system would probably need to have been initiated and launched in a JavaFX application or a JFXPanel first.
All of the above may or may not end up giving you the result you want, but it seems a promising avenue to examine.
Update
I ran a couple of tests on this and though I can snapshot a WebView displayed on the screen as explained in this post, due to some limitation of JavaFX 2.2, I was unable to snapshot a WebView displayed as part of an offscreen scene. This means that the information in this answer is accurate, but only applies to the portion of the HTML which can be displayed in the WebView on a screen; e.g. the technique will not currently work for large documents whose pixel size exceeds the screen pixel size. For some sample code, see https://forums.oracle.com/forums/thread.jspa?threadID=2456191.
After a lot of searching and scraping several pieces together I found that the only problem I had with the example in the Update oracle forum link above was that the size of the webview was fixed and that my css used in the html (not in JavaFX) needed.
overflow-x: hidden;
overflow-y: hidden;
to hide the last scrollbar.
So I come up with the following snapshot method (application with animation just as example of your application):
package application;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.web.WebView;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class WebViewSnapshot extends Application {
BorderPane rootPane;
TranslateTransition animation;
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 50, 50, 50);
rect.setFill(Color.CORAL);
animation = createAnimation(rect);
Button snapshotButton = new Button("Take snapshot");
Pane pane = new Pane(rect);
pane.setMinSize(600, 150);
rootPane = new BorderPane(pane, null, null, snapshotButton, new Label("This is the main scene"));
snapshotButton.setOnAction(e -> {
// html file being shown in webview
File htmlFile = new File ("generated/template.html");
// the resulting snapshot png file
File aboutFile = new File ("generated/about.png");
generate(htmlFile, aboutFile, 1280, 720);
});
BorderPane.setAlignment(snapshotButton, Pos.CENTER);
BorderPane.setMargin(snapshotButton, new Insets(5));
Scene scene = new Scene(rootPane);
primaryStage.setScene(scene);
primaryStage.show();
}
private TranslateTransition createAnimation(Rectangle rect) {
TranslateTransition animation = new TranslateTransition(Duration.seconds(1), rect);
animation.setByX(400);
animation.setCycleCount(Animation.INDEFINITE);
animation.setAutoReverse(true);
animation.play();
return animation;
}
public void generate(File htmlFile, File outputFile, double width, double height) {
animation.pause();
// rootPane is the root of original scene in an FXML controller you get this when you assign it an id
rootPane.setEffect(new GaussianBlur());
Stage primaryStage = (Stage)rootPane.getScene().getWindow();
// creating separate webview holding same html content as in original scene
WebView webView = new WebView();
// with the size I want the snapshot
webView.setPrefSize(width, height);
AnchorPane snapshotRoot = new AnchorPane(webView);
webView.getEngine().load(htmlFile.toURI().toString());
Stage popupStage = new Stage(StageStyle.TRANSPARENT);
popupStage.initOwner(primaryStage);
popupStage.initModality(Modality.APPLICATION_MODAL);
// this popup doesn't really show anything size = 1x1, it just holds the snapshot-webview
popupStage.setScene(new Scene(snapshotRoot, 1, 1));
// pausing to make sure the webview/picture is completely rendered
PauseTransition pt = new PauseTransition(Duration.seconds(2));
pt.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
WritableImage image = webView.snapshot(null, null);
// writing a png to outputFile
// writing a JPG like this will result in a pink JPG, see other posts
// if somebody can scrape me simple code to convert it ARGB to RGB or something
String format = "png";
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), format, outputFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
rootPane.setEffect(null);
popupStage.hide();
animation.play();
}
}
});
// pausing, after pause onFinished event will take + write snapshot
pt.play();
// GO!
popupStage.show();
}
public static void main(String[] args) {
launch(args);
}
}