I am using a 64 bit linux machine (8GB of RAM) on KDE with Eclipse as my IDE. I am also using Oracle's JDK. I made a small animation using JavaFX and a few pictures off the web to animate earth rotating around the sun. Whenever I run it, the animation works normally, but it steadily eats all of the RAM on my computer until everything freezes. This usually takes less than 5 minutes.
package Practice;
/**
* For some reason, this code gobbles up memory, and freezes computers
*/
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class BasicAnimation extends Application {
public BasicAnimation() {
// TODO Auto-generated constructor stub
}
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Orbit");
Group root = new Group();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
Canvas canvas = new Canvas(512,512);
root.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
Image earth = new Image(getClass().getResourceAsStream("earth.png"), 25.0, 25.0 ,false, false);
Image sun = new Image(getClass().getResourceAsStream("sun.jpg"), 50.0, 50.0, false, false);
Image space = new Image(getClass().getResourceAsStream("space.jpg"));
final long startNanoTime = System.nanoTime();
new AnimationTimer() {
public void handle(long currentNanoTime) {
double t = (currentNanoTime - startNanoTime) / 1000000000.0 ;
double x = 232 + 128 * Math.cos(t);
double y = 232 + 128 * Math.sin(t);
//background image clears canvas
gc.drawImage(space, 0, 0);
gc.drawImage(earth, x, y);
gc.drawImage(sun, 196, 196);
}
}.start();
primaryStage.show();
}
}
I've set -Xms512m, -Xmx512m, and -Xss512m. Is there something I'm doing wrong that could be causing this, and could you explain why that happens or how to avoid it?
Also if there is something wrong with my question, please let me know.
Edits: Added more information
The Earth image is 2356x2356, and I set it to 25x25px in the program. The Sun image is 750x750, and I set it to 50x50 in the program . The space image is 1920x1080, and it is the background which is 512x512 px.
Links to images
Sun : https://www.thesun.co.uk/wp-content/uploads/2016/06/download.jpg?w=750&strip=all
Earth : https://openclipart.org/image/2400px/svg_to_png/218125/3d-Earth-Globe.png
Space : http://www.gunnars.com/wp-content/uploads/2014/08/Space.jpg
I can't see anything wrong in your code. It may not be the best way to do this in JavaFX but it looks perfectly valid to me and should not eat up any memory. Especially as you say that you have the same problem with the other code from Luke I suspect some Linux bug. Have you tried running your program on another OS? If you'd provide the links to your images someone else could try that too.
This link may be related to your problem:
javafx-unexplainable-leaks-memory-on-linux
Test
I ran your program on a Mac and there was no memory leak and almost no CPU usage, as I'd expected.
gc.drawImage(space, 0, 0); is causing the problem. IMHO, you are not supposed to call it for every frame.
Normally we do animation by rendering frames. We get a Graphics object and for each frame we clear the canvas and redraw everything. But that's not how things work in JavaFX.
In JavaFX, animation is achieved by applying transformation on nodes - shapes, images or groups. You setup the scene, add your "actors", the shapes, images etc. Then you create Animation objects that control those "actors".
I am no expert, so the following example just demonstrate the idea of how to make a circle rotate around another. The motion is not uniform. So you definitely want to experiment different paths/Transitions/Animations.
Update: use Path.setInterpolator(Interpolator.LINEAR) to remove the acceleration
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.effect.BoxBlur;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import static javafx.animation.Animation.INDEFINITE;
public class Animation extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("orbit");
Group root = new Group();
Scene scene = new Scene(root, 800, 800, Color.BLACK);
primaryStage.setScene(scene);
Circle sun = new Circle(50, Color.web("yellow", 1.0));
sun.setCenterX(400);
sun.setCenterY(400);
sun.setEffect(new BoxBlur(10, 10, 3));
Circle earth = new Circle(10, Color.web("blue"));
earth.setEffect(new BoxBlur(4, 4, 3));
root.getChildren().add(sun);
root.getChildren().add(earth);
Path path = new Path();
ArcTo arcTo = new ArcTo();
arcTo.setX(20);
arcTo.setY(401);
arcTo.setSweepFlag(true);
arcTo.setLargeArcFlag(true);
arcTo.setRadiusX(400);
arcTo.setRadiusY(400);
arcTo.setXAxisRotation(0);
path.getElements().add(new MoveTo(20, 400));
path.getElements().add(arcTo);
path.getElements().add(new ClosePath());
path.setVisible(false);
PathTransition pt = new PathTransition(Duration.seconds(10), path, earth);
pt.setInterpolator(Interpolator.LINEAR); // No acceleration/deceleration
pt.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(INDEFINITE);
pt.play();
primaryStage.show();
}
}
tl;dr Use the -D.prism=sw argument for the JVM to hack around the problem.
JavaFX tries to make use of hardware acceleration, and it seems that the new Mesa and Xorg drivers don't entirely solve the issue. I also think that the VDPAU driver, the hardware acceleration driver, is at fault. When I run the program with a -D.prism=sw argument for the JVM, which sets the compiler to use software pipelining rather than hardware acceleration, the problem is greatly reduced. I still see that the program steadily consumes memory, but the process is much slower.
I also found that reducing the number of times the gc.drawimage() is called also increases the amount of time it takes to fill my RAM.
new AnimationTimer() { //This is how I reduced the number of times gc.drawImage is called
long lastupdate = 0 ;
public void handle(long currentNanoTime) {
double t = (currentNanoTime - startNanoTime) / 1000000000.0 ;
double x = 232 + 128 * Math.cos(t);
double y = 232 + 128 * Math.sin(t);
//background image clears canvas
if(currentNanoTime - lastupdate >= 33333333) {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.drawImage(space, 0, 0);
gc.drawImage(sun, 196, 196);
gc.drawImage(earth, x, y);
lastupdate = currentNanoTime;
}
}
}.start();
There may be an issue with garbage collection there, but I'm not sure. I will use VisualVM to check later, then I'll update this answer.
Update
There is no issue with garbage collection. The memory is stable now, and it was stabilized by enabling software pipelining. JavaFX seems to not work well with VDPAU. Thanks for all the help!
Sources:
https://github.com/jfoenixadmin/JFoenix/issues/52
https://bugs.openjdk.java.net/browse/JDK-8161997
Related
I have a weird issue that every other guide and answer seems to contradict, but the issue seems to be deeper. OS-level deep.
System details:
Ubuntu 18.04, Unity window manager, nVidia graphics (proprietary driver)
Tried with the following Java VMs:
-OpenJDK 11 & 17,
-Temurin 11 & 17,
-JBR-17
I have an application where I draw an image on a canvas, zoomed (so it's pixelated), and I can pan around and edit with the mouse (sort of like photoshop). I do this by defining a small rectangle in the image and drawing that to the entire panel (at 4K resolution):
g.drawImage(image,
x, // dst
y,
x + visibleImageWidth * blockSize,
y + visibleImageHeight * blockSize,
ul.x, // src
ul.y,
ul.x + visibleImageWidth,
ul.y + visibleImageHeight,
this);
This works fine statically, but when I start working it with the mouse, it progressively slows down to a crawl.
I analyzed this, and it seems that the mouse fires events at 1000Hz. Then paintComponent() somehow manages to finish within that same 1ms. The OS however chokes on the amount of visual data thrown at it, and every (visual) update takes longer than the last. As long as I keep dragging the mouse, the OS crawls to a complete stop. (It seems everything non-graphical still works at normal speed, e.g. my program keeps processing input) Also visual updates of other programs stop, so it's like the graphics card or driver chokes on the data and can't process/discard it fast enough. When I let go of the mouse it stays frozen until next visual update. Then all programs instantly update their visuals and everything is back to normal.
The (heavyweight) JPanel that I have doesn't collate repaint() calls where I expect it should. Every time I call it, it immediately (from my perspective) calls paintComponent(), which finishes within 1ms, before the next call to repaint().
* Why is Java so insistent on sending graphics data to the OS at such a ridiculous speed? My monitor runs at 60Hz, not 1000Hz.
I found a very dirty workaround that at least lets me use the program in any reasonable way at all: Adding
try {
Thread.sleep(10);
} catch (InterruptedException ignored) {
}
at the end of the paintComponent() method. The program now works super smooth, without visible tearing or microstuttering (even though 10ms != 60Hz).
Why do I need this delay to, of all things, speed up graphics? How can I make Java respect the monitor's refresh rate in a 'neater' way?
[Edit] MSSCC
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class Temp extends JPanel {
public static void main(String... args) {
System.out.println(LocalDate.now());
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setContentPane(new Temp());
f.setExtendedState(Frame.MAXIMIZED_BOTH);
f.setVisible(true);
});
}
private final BufferedImage image = new BufferedImage(10000, 10000, BufferedImage.TYPE_INT_RGB);
public Temp() {
super(null);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int x = e.getX() / 5;
int y = e.getY() / 5;
int rgb = ThreadLocalRandom.current().nextInt(0xFFFFFF);
int[] pixels = new int[100];
Arrays.fill(pixels, rgb);
image.getRaster().setDataElements(x, y, 8, 8, pixels);
repaint();
((Frame)getTopLevelAncestor()).setTitle(" (" + x + ", " + y + ')');
}
});
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(image,
0, 0, getWidth(), getHeight(),
0, 0, getWidth() / 5, getHeight() / 5,
null);
}
}
Back in the day I created a Paint widget to draw like you'd do in MS Paint - and I definitely did not experience mouse move events firing at the rate you are reporting. As #GilbertLeBlanc commented, it seems like you are probably in an infinite paint loop.
Nevertheless, if this isn't the issue, you can throttle event firing like this
public class Throttle extends MouseMotionAdapter {
public static final long THRESHHOLD = 30; //ms
private lastEvTime = 0;
public void mouseMoved(MouseEvent me) {
long time = System.currentTimeMillis();
if (time > (lastEvTime + THRESHHOLD)) {
lastEvTime = time;
//do graphical update
}
// else do nothing
}
}
Is there any way to make fullscreen(and if possible resizing too) to instead of rearranging everything (actually what it does is to rearrange the elements like resizing but to the whole screen) to make an actual fullscreen mode? (like games that what usually do is change screen resolution), so that buttons and text grows accordingly to the size of the screen/window
Also how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?
EDIT: use this way to make resizeable
#Override public void start(Stage stage) throws Exception{
final int initWidth = 720; //initial width
final int initHeight = 1080; //initial height
final Pane root = new Pane(); //necessary evil
Pane controller = new CtrlMainMenu(); //initial view
controller.setPrefWidth(initWidth); //if not initialized
controller.setPrefHeight(initHeight); //if not initialized
root.getChildren().add(controller); //necessary evil
Scale scale = new Scale(1, 1, 0, 0);
scale.xProperty().bind(root.widthProperty().divide(initWidth)); //must match with the one in the controller
scale.yProperty().bind(root.heightProperty().divide(initHeight)); //must match with the one in the controller
root.getTransforms().add(scale);
final Scene scene = new Scene(root, initWidth, initHeight);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
//add listener for the use of scene.setRoot()
scene.rootProperty().addListener(new ChangeListener<Parent>(){
#Override public void changed(ObservableValue<? extends Parent> arg0, Parent oldValue, Parent newValue){
scene.rootProperty().removeListener(this);
scene.setRoot(root);
((Region)newValue).setPrefWidth(initWidth); //make sure is a Region!
((Region)newValue).setPrefHeight(initHeight); //make sure is a Region!
root.getChildren().clear();
root.getChildren().add(newValue);
scene.rootProperty().addListener(this);
}
});
}
There are a couple of ways to resize your UI.
Scale by Font Size
You can scale all controls by setting -fx-font-size in the .root of your scene's style sheet.
For example, if you apply the following stylesheet to your scene, then all controls will be doubled in size (because the default font size is 13px).
.root {
-fx-font-size: 26px;
}
The above will work to scale controls, which is fine for things which are completely control based, but not so good for things which are graphic and shape based.
Scale by Transform
Apply a Scale transform pivoted at (0,0) to your scene's root node.
Scale scale = new Scale(scaleFactor, scaleFactor);
scale.setPivotX(0);
scale.setPivotY(0);
scene.getRoot().getTransforms().setAll(scale);
To scale a game I developed which includes graphics and various shapes, I used a letter boxing technique which sized the game window to a constant aspect ratio, (similar to the letter boxing you see when you watch a 4:3 tv show on a 16:9 screen).
The SceneSizeChangeListener in the code below listens for changes to the scene size and scales the content of the scene appropriate to the available scene size.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
import org.jewelsea.games.supersnake.layout.LayoutController;
import java.io.IOException;
import java.util.ResourceBundle;
/* Main JavaFX application class */
public class SuperSnake extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("layout/layout.fxml"),
ResourceBundle.getBundle("org.jewelsea.games.supersnake.layout.text")
);
Pane root = (Pane) loader.load();
GameManager.instance().setLayoutController(loader.<LayoutController>getController());
Scene scene = new Scene(new Group(root));
stage.setScene(scene);
stage.show();
GameManager.instance().showMenu();
letterbox(scene, root);
stage.setFullScreen(true);
}
private void letterbox(final Scene scene, final Pane contentPane) {
final double initWidth = scene.getWidth();
final double initHeight = scene.getHeight();
final double ratio = initWidth / initHeight;
SceneSizeChangeListener sizeListener = new SceneSizeChangeListener(scene, ratio, initHeight, initWidth, contentPane);
scene.widthProperty().addListener(sizeListener);
scene.heightProperty().addListener(sizeListener);
}
private static class SceneSizeChangeListener implements ChangeListener<Number> {
private final Scene scene;
private final double ratio;
private final double initHeight;
private final double initWidth;
private final Pane contentPane;
public SceneSizeChangeListener(Scene scene, double ratio, double initHeight, double initWidth, Pane contentPane) {
this.scene = scene;
this.ratio = ratio;
this.initHeight = initHeight;
this.initWidth = initWidth;
this.contentPane = contentPane;
}
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
final double newWidth = scene.getWidth();
final double newHeight = scene.getHeight();
double scaleFactor =
newWidth / newHeight > ratio
? newHeight / initHeight
: newWidth / initWidth;
if (scaleFactor >= 1) {
Scale scale = new Scale(scaleFactor, scaleFactor);
scale.setPivotX(0);
scale.setPivotY(0);
scene.getRoot().getTransforms().setAll(scale);
contentPane.setPrefWidth (newWidth / scaleFactor);
contentPane.setPrefHeight(newHeight / scaleFactor);
} else {
contentPane.setPrefWidth (Math.max(initWidth, newWidth));
contentPane.setPrefHeight(Math.max(initHeight, newHeight));
}
}
}
}
Here is a screenshot where you can see the letterboxing and scaling taking effect. The green grass in the middle is the main game content screen and scales up and down to fit the available screen area. The wood texture around the outside provides a flexibly sized border which fills in the area where the black letterbox bars would normally be if you were watching a tv program at a different aspect ratio to your screen. Note that the background in the screenshot below is blurry at the title page because I make it so, when the game starts, the blur effect is removed and the view is crisp regardless of the size.
Windowed version:
Scaled full screen version:
You might think that the scaling method above might make everything go all blocky and pixelated, but it doesn't. All font's and controls scale smoothly. All standard drawing and graphic commands and css based styles scale smoothly as they are all vector based. Even bitmapped images scale well because JavaFX uses fairly high quality filters when scaling the images.
One trick to get good scaling on images is to provide high resolution images, so that when the screen scales up, the JavaFX system has more raw data to work from. For example, if the preferred window size for an app is quarter of the screen size and it contains a 64x64 icon, instead use a 128x128 icon, so that when the app is put in full screen and all elements scaled, the scaler has more raw pixel data samples to use for interpolating values.
The scaling is also fast as it is hardware accelerated.
how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?
It's not possible to remove the full screen exit message in JavaFX 2.2, it will be possible in JavaFX 8:
RT-15314 Allow trusted apps to disable the fullscreen overlay warning and disable the "Exit on ESC" behavior
It will be nice when that is done, because then my games won't have that "look at me - I look like a beta" feel about them.
"Also how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?"
Use this code :
stage.setFullScreenExitHint("");
It will change the string message "Press Esc to quit Fullscreen mode" into empty string so it will not show up.
You may copy this into JavaFXApplication
Dimension resolution = Toolkit.getDefaultToolkit().getScreenSize();
double width = resolution.getWidth();
double height = resolution.getHeight();
double w = width/1280; // your window width
double h = height/720; // your window height
Scale scale = new Scale(w, h, 0, 0);
root.getTransforms().add(scale);
I am coding a program which has an entire JPanel zoomed in with a JScrollPane as a map, but I also want to give users a scaled & minimized view of the whole map in the corner of the JScrollPane. I am using BlueJ, & (in BlueJ, at least) I have basically gotten what I want. To scale down the map, I create a single BufferedImage, bi, that is the size of the mini-map, then take its Graphics object, g, convert it to a Graphics2D object, g2, then give g2 a single set of RenderingHints, through:
g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
From there, I use the Graphics.scale(double sx, double sy) method on g2 to set the scale so that upon calling the paint(Graphics g) method of the large map with g2 as its parameter, it will draw a scaled version of the large map on bi that is the exact size of the mini-map. This happens 20 times per second (somewhat arbitrary, at that speed I think it looks best. The mini-map reflects what the user is doing in real-time & I think it looks laggy if done fewer than 20 times per second.)
On my Macbook Pro, The resulting look of this code is:
There is no noticeable lag with this code when I run the program, which has led me to keep the picture re-sampling at 20 times per second, since there appears to be plenty of CPU for this. However, upon exporting my project to a JAR file, the resulting look is this:
There still is little to no lag with the program running, but what the heck happened? So long as I run the code in BlueJ, it works - but in a JAR file, the mini-map looks horrible.
For clarification, this is the map to be scaled (which, ironically, is being scaled when I'm placing it in this post):
And here are the two BlueJ & JAR file mini-maps, side-by-side:
What could be the cause of this?
When I run "java -version", this is the output:
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
Tl;dr:
When I run my code in BlueJ, the small picture looks good. After exporting to a JAR file, it looks bad. What could be causing this?
EDIT: I am now including an SSCCE to give a better idea of what I mean.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import javax.swing.border.*;
public class ScaleTest
{
int scrollSpeed = 16, imageSize = 200;
int rows = 40, columns = 40;
JFrame f1 = new JFrame("ScaleTest: Full Size");
JScrollPane scrollPane = new JScrollPane();
JPanel f1Panel = new JPanel(new GridLayout(rows,columns,1,1));
JFrame f2 = new JFrame("ScaleTest: Scaled");
JPanel f2Panel = new JPanel()
{
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(bi,0,0,null);
}
};
BufferedImage bi = new BufferedImage(imageSize,imageSize,BufferedImage.TYPE_INT_ARGB);
public ScaleTest()
{
f1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f1.add(scrollPane);
scrollPane.setPreferredSize(new Dimension(600,600));
scrollPane.getHorizontalScrollBar().setUnitIncrement(scrollSpeed);
scrollPane.getVerticalScrollBar().setUnitIncrement(scrollSpeed);
scrollPane.setViewportView(f1Panel);
for (int i = 0; i < rows*columns; i++)
{
JPanel p = new JPanel();
p.setBackground(Color.WHITE);
p.setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
p.setPreferredSize(new Dimension(100,100));
f1Panel.add(p);
}
f1.pack();
f2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f2.setResizable(false);
f2.add(f2Panel);
f2Panel.setPreferredSize(new Dimension(imageSize,imageSize));
f2.pack();
Graphics2D g2 = (Graphics2D)bi.getGraphics();
g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
g2.scale(((double)imageSize)/f1Panel.getWidth(),((double)imageSize)/f1Panel.getHeight());
f1Panel.paint(g2);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
ScaleTest s = new ScaleTest();
s.f1.setVisible(true);
s.f2.setVisible(true);
}
}
In BlueJ the resulting scaled image is this:
Yet, in the JAR file that is produced from BlueJ, running the JAR produces this image:
What could be going on?
I would guess that your BlueJ using JDK1.6.0.
java version "1.6.0_45"
java version "1.8.0_31"
I found this answer on SE: https://stackoverflow.com/a/24746194/1695856 .
The upshot of it is that one way to improve the quality of the scaled down image is to blur it and scale it down in stages. In the following example I scaled the image down by 50% three times, and then by 80%, to get a final scale of 10% of the original.
The file referred to (map.png) was the large image you posted earlier. Setting the Antialiasing rendering hint is of no use when drawing scaled images as it only has an effect on lines, ovals, rectangles etc, but not images, from what I can tell. The Interpolation rendering hint helps a little, but relying on any rendering hint is not always going to work as they may not be implemented the same (or at all!) on different platforms.
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Rescale {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
private ConvolveOp cop;
#Override
public void run() {
JFrame frame = new JFrame("Map");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
BufferedImage im = ImageIO.read(new File("map.png"));
cop = new ConvolveOp(new Kernel(3, 3, new float[] { 0f, 1f / 5f, 0f, 1f / 5f, 1f / 5f, 1f / 5f, 0f, 1f / 5f, 0f }),
ConvolveOp.EDGE_NO_OP, null);
for (int i = 0; i < 3; i++) {
im = getScaled(im, 0.5);
}
im = getScaled(im, 0.8f);
JLabel lab = new JLabel(new ImageIcon(im));
frame.setContentPane(lab);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
private BufferedImage getScaled(BufferedImage im, double scale) {
BufferedImage nim;
Dimension dim = new Dimension((int) (im.getWidth() * scale), (int) (im.getHeight() * scale));
nim = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) nim.getGraphics();
g.scale(scale, scale);
g.drawImage(im, cop, 0, 0);
g.dispose();
return nim;
}
});
}
}
I hope this helps.
Please I want to draw a 3D graph using JavaFX 8 (3D). I already know some basics of 3D like draw sphere, firstly coloring the sphere and add shadow, then some light and initialisation of the sphere. My problem is I want to join the spheres by using a cylinder, but if there are for example 2 cylinders between two spheres it must be an arc or curved cylinder (I don't know if this is possible). I already tried that but nothing appears, even if something appears it's just a cylinder (not like a line just small).
Another problem, I want to know how the rotation can help in such a situation.
And last question, is it possible to make a scrollbar or just using event of Zoom? Thanks.
the picture taken from : Here
This is my code:
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class Graphe3D extends Application {
Group root;
PhongMaterial material;
ArrayList<Sphere> sphere;
#Override
public void start(Stage primaryStage) {
sphere=new ArrayList<>();
root=new Group();
material= new PhongMaterial();
for(int i=0;i<3;i++){
sphere.add(new Sphere(50));
//Sphere Color
material.setDiffuseColor(Color.RED);
//Shadow Color
material.setSpecularColor(Color.rgb(30, 30, 30));
//Init Sphere
sphere.get(i).setMaterial(material);
sphere.get(i).setTranslateX(new Random().nextInt(600));//set location X,Y and Z
sphere.get(i).setTranslateY(new Random().nextInt(600));
sphere.get(i).setTranslateZ(50); // ?
sphere.get(i).setDrawMode(DrawMode.FILL);
sphere.get(i).setCullFace(CullFace.BACK);// ?
//Create Light
PointLight pointLight = new PointLight(Color.ANTIQUEWHITE);
pointLight.setTranslateX(800);
pointLight.setTranslateY(-100);
pointLight.setTranslateZ(-1000);
root.getChildren().add(pointLight); //ajout de lumiere
root.getChildren().add(sphere.get(i)); //ajout des spheres au scene(root)
}
//Display
Scene scene = new Scene(root);
scene.setFill(Color.rgb(10, 10, 40));
scene.setCamera(new PerspectiveCamera(false));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The molecule sample application from Oracle does much of what you are asking. Read the linked tutorial and study the source code provided for the sample application.
Regarding a couple of your questions.
if there is for example 2 cylinder between two sphere it must an arc or curved cylinder
This is possible. You would have to generate a custom TriangleMesh rather than using the pre-built Cylinder class. Essentially, you need to create an elliptical Torus and only display an arc portion of the Torus between your two nodes. I will not provide detailed instructions on how to do this in the context of a StackOverflow answer.
I want to know how the rotation can help in such situation and last question is it possible to make a scrollbar or just using event of Zoom ?
Study the Molecule sample code linked earlier as that has rotation and zoom capability.
You can calculate the cylinder position and rotation using
Shape.getTransforms().add(new Rotate(angle, x, y, z, Rotate.Z_AXIS))
and
Math.toDegrees(Math.atan2(y, x))
Here's a sample working in 2D
Sphere var = null;
for(Sphere sphere: myListSphere {
if(var!=null) {
double x = sphere.getTranslateX()-var.getTranslateX();
double y = sphere.getTranslateY()-var.getTranslateY();
//the distance from each sphere
Cylinder cyl = new Cylinder(5, Math.sqrt((x*x)+(y*y)), 5);
cyl.setMaterial(this.redMaterial);
cyl.setTranslateX(sphere.getTranslateX()-(x/2));
cyl.setTranslateY(sphere.getTranslateY()-(y/2));
//the angle from both dots with Math.atan
cyl.getTransforms().add(new Rotate(90+Math.toDegrees(Math.atan2(y, x)), 0, 0, 0, Rotate.Z_AXIS));
}
var = shape;
}
I'm converting a Swing/Graphics2D app with a lot of custom painting to a JavaFX2 app. Although I absolutely love the new API, I seem to have a performance problem when painting an ellipse that I want to paint below the mouse cursor wherever the mouse is moved. When I move my mouse in a steady way, not ridicously fast, I notice the ellipse is always drawn a few centimeters behind on the mouse trail, and only catches up when I stop moving the cursor. This in a scenegraph with only a handful nodes. In my Swing app I didn't have that problem.
I'm wondering if this is the correct approach for drawing a shape where the mousecursor is?
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Pane p = new Pane();
final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
p.getChildren().add(ellipse);
p.setOnMouseMoved(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
ellipse.setCenterX(event.getX());
ellipse.setCenterY(event.getY());
}
});
Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
primaryStage.setScene(scene);
primaryStage.show();
}
}
Small update: I upgraded to JavaFX 2.2 and Java7u6 (on Windows 7 64bit), doesn't seem to make a difference though.
Here is some code I use to allow a Label to be dragged around in a Pane.
I don't notice any significant lag behind the mouse trail with it.
// allow the label to be dragged around.
final Delta dragDelta = new Delta();
label.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = label.getLayoutX() - mouseEvent.getSceneX();
dragDelta.y = label.getLayoutY() - mouseEvent.getSceneY();
label.setCursor(Cursor.MOVE);
}
});
label.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setCursor(Cursor.HAND);
}
});
label.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
label.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
}
});
label.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setCursor(Cursor.HAND);
}
});
. . .
// records relative x and y co-ordinates.
class Delta { double x, y; }
Here is a small complete example app using the above code.
Update
The above example, will still lag the object being dragged behind the cursor when the objects being dragged are small.
An alternate approach is to use an ImageCursor comprising of a MousePointer superimposed over the an image representation of the node being dragged, then hide and show the actual node at the start and completion of the drag. This means that the node drag rendering will not lag the cursor (as the image representation of the node is now the cursor). However this approach does have drawbacks => there are restrictions on the size and format of ImageCursors, plus you need to convert your Node to an Image to place it in an ImageCursor, for which you may need advanced Node => Image conversion operations only to available in JavaFX 2.2+.
The lag that you're describing (between your mouse and the dragged shape) is a known JavaFX bug:
https://bugs.openjdk.java.net/browse/JDK-8087922
You can work around it (on Windows, at least) by using an undocumented JVM flag:
-Djavafx.animation.fullspeed=true
This flag is normally for internal performance testing, which is why it is undocumented, but we've been using it for months and haven't had any problems with it so far.
EDIT:
There's another, similar way to workaround this bug that might be a little easier on CPU usage. Simply turn off Prism's vertical sync:
-Dprism.vsync=false
In our app, either of these workarounds solves the lag; there's no need to do both.
To me it doesn't look like a question of painting performance, but how the sequence of mouse events is generated. The events are not generated in real time, some are skipped, when the mouse moves fast. For the most applications this will be the sufficent way. The mouse pointer moves in real time without any time lag.
If you don't want this effect you will have to listen to the mouse pointer directly or find a way to get the events in higher density. I don't know how myself.
There's this "cacheHint" property, available on all Nodes and that may help ?
http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#cacheHintProperty
Under certain circumstances, such as animating nodes that are very expensive to render, it is desirable to be able to perform transformations on the node without having to regenerate the cached bitmap. An option in such cases is to perform the transforms on the cached bitmap itself.
This technique can provide a dramatic improvement to animation performance, though may also result in a reduction in visual quality. The cacheHint variable provides a hint to the system about how and when that trade-off (visual quality for animation performance) is acceptable.
If your ellipse remains the same the whole time, but is redrawn every time you move it by one pixel, this seems to be a huge slowdown.
I was having the same problem while trying to make nodes on a chart draggable. I fixed it by calling chart.setAnimated(false); In my case the lag was being caused by JavaFX applying a nice smooth animation to the changes my code was making.
here is the code to drag and drop label using mouse in javafx
#FXML
public void lblDragMousePressed(MouseEvent m)
{
System.out.println("Mouse is pressed");
prevLblCordX= (int) lblDragTest.getLayoutX();
prevLblCordY= (int) lblDragTest.getLayoutY();
prevMouseCordX= (int) m.getX();
prevMouseCordY= (int) m.getY();
}
//set this method on mouse released event for lblDrag
#FXML
public void lblDragMouseReleased(MouseEvent m)
{
System.out.println("Label Dragged");
}
// set this method on Mouse Drag event for lblDrag
#FXML
public void lblDragMouseDragged(MouseEvent m)
{
diffX= (int) (m.getX()- prevMouseCordX);
diffY= (int) (m.getY()-prevMouseCordY );
int x = (int) (diffX+lblDragTest.getLayoutX()-rootAnchorPane.getLayoutX());
int y = (int) (diffY+lblDragTest.getLayoutY()-rootAnchorPane.getLayoutY());
if (y > 0 && x > 0 && y < rootAnchorPane.getHeight() && x < rootAnchorPane.getWidth())
{
lblDragTest.setLayoutX(x);
lblDragTest.setLayoutY(y);
}
}
you can use : Node.setCache(true);
(i use it with a Pane with many childrens like a TextField)
Drag Sphere
#Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
labelTableName.setText("Table Categories");
final PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.BLUE);
blueMaterial.setSpecularColor(Color.LIGHTBLUE);
final Sphere sphere = new Sphere(50);
sphere.setMaterial(blueMaterial);
final Measure dragMeasure = new Measure();
final Measure position = new Measure();
sphere.setOnMousePressed(mouseEvent -> {
dragMeasure.x = mouseEvent.getSceneX() - position.x;
dragMeasure.y = mouseEvent.getSceneY() - position.y;
sphere.setCursor(Cursor.MOVE);
});
sphere.setOnMouseDragged(mouseEvent -> {
position.x = mouseEvent.getSceneX() - dragMeasure.x;
position.y = mouseEvent.getSceneY() - dragMeasure.y;
sphere.setTranslateX(position.x);
sphere.setTranslateY(position.y);
});
sphere.setOnMouseReleased(mouseEvent -> sphere.setCursor(Cursor.HAND));
sphere.setOnMouseEntered(mouseEvent -> sphere.setCursor(Cursor.HAND));
bottomHeader.getChildren().addAll( sphere);
}
class Measure {
double x, y;
public Measure() {
x = 0; y = 0;
}
}
this is modified Kotlin code based on answer from #jewelsea
var dragDelta = Delta()
var releasedDelta = Delta()
scene.setOnMousePressed {
if (releasedDelta.x > 0 && releasedDelta.y > 0) {
val offsetX = it.sceneX - releasedDelta.x
var offsetY = it.sceneY - releasedDelta.y
dragDelta.x = dragDelta.x + offsetX
dragDelta.y = dragDelta.y + offsetY
} else {
dragDelta.x = it.sceneX
dragDelta.y = it.sceneY
}
scene.cursor = Cursor.MOVE;
releasedDelta = Delta()
}
scene.setOnMouseReleased {
releasedDelta.x = it.sceneX
releasedDelta.y = it.sceneY
scene.cursor = Cursor.HAND;
}
scene.setOnMouseDragged {
scene.translateX = it.sceneX - dragDelta.x;
scene.translateY = it.sceneY - dragDelta.y;
}
scene.setOnMouseEntered {
scene.cursor = Cursor.HAND
}