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.
Related
I am trying to display a gif but if the gif is "too long" it for some reason just starts over instead of displaying the whole animation.
I am currently just using this plain code (for testing without any other code interfering) and it won't work:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Group popup = new Group();
Image image = new Image("https://image.ibb.co/hUMzWU/1.gif");
ImageView view = new ImageView(image);
popup.getChildren().add(view);
Scene dialogScene = new Scene(popup);
primaryStage.setScene(dialogScene);
primaryStage.setTitle("Testing Gif Stuff");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
An example for such an image would be: https://img2.picload.org/image/dlldgogw/7.gif
For me it keeps "resetting" right when his arms enter the picture. Any help is appreciated. Using Java 10. Loading from disk or from internet makes no difference.
Some other gifs that won't work either:https://image.ibb.co/jhhsJ9/ae221412fcd5235a.gif (broken as hell)
https://image.ibb.co/fyhL5p/1664d3a95ec06cfd.gif
https://image.ibb.co/hH4NJ9/0beec1ba838fabd2.gif
File size does not seem to be the main issue because the last gif is relatively small (900kb).
I have a scene with a choice box. the aim is to get all available system fonts to display in the choice box, I kinda feel I'm on the right path as so far I have managed to get 1 to display in the choice box, but why just the 1?
here is the code -
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
public class ChoiceBoxFonts extends Application
{
ObservableList<String> fontType;
ChoiceBox<String> fonts;
public static void main(String [] args)
{
Application.launch(args);
}
public void start(Stage primaryStage)
{
Pane root = new Pane();
Font.getFamilies().stream().forEach(i ->{
fontType =
FXCollections.observableArrayList(i
);
});
// New choicebox with observable arraylist fontType
fonts = new ChoiceBox<String> (fontType);
//SingleSelectionModel<String> selMod = fonts.getSelectionModel();
root.getChildren().add(fonts);
Scene scene = new Scene(root,200,200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The goal of the experiment is to be able to select a font from the choice box and change the font of a text object with that selection.
Also, is there a better UI to be able to do such a thing? If there are a bucket load of fonts, that choice box is going to be very long!
You just need
fontType = FXCollections.observableArrayList(Font.getFamilies());
instead of the iteration you have.
If there are a bucket load of fonts, that choice box is going to be very long!
I would probably consider a ListView.
I have a MacBook Pro with a touch enabled trackpad. When I open a large image with the Mac preview app I can let the image slide softly over the screen with a two finger swipe gesture. Now I would like to do the same inside a JavaFX app (see code below). In principle this seems to work but the movement is not smooth. The image jumps in roughly 10 pixel increments which just looks ugly. I experimented with the JavaFX gestures but I did not get it to move smoothly. Can anybody tell me whether this is possible with JavaFX at all and if yes how to do that?
package jfxfeatures.control.scrollpane;
import java.io.File;
import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ScrollCanvas1 extends Application {
// Some large image in the order of 4000x3000 pixels.
private final static File file = new File("some large image file here");
ScrollPane scrollPane;
#Override
public void start(Stage primaryStage) {
try {
Image image = new Image(file.toURI().toURL().toExternalForm());
Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
canvas.getGraphicsContext2D().drawImage(image, 0, 0);
scrollPane = new ScrollPane();
scrollPane.setPannable(true);
scrollPane.setContent(canvas);
BorderPane root = new BorderPane();
root.setPrefSize(1200, 800);
root.setCenter(scrollPane);
primaryStage.setScene(new Scene(root));
primaryStage.show();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
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.
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);
}
}