I was trying out the code here for making a JavaFX app which allows my stylus pen to draw on a canvas: Canvas does not draw smooth lines
The performance is extremely painful when trying to draw on a canvas. It will freeze for half a second, and then start drawing. Only after it starts drawing is it fine. Beforehand though when you first press down with the mouse/pen, the delay is pretty brutal and makes it unusable.
Here is the code I used:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.BoxBlur;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.stage.Stage;
public class Test extends Application {
private GraphicsContext gc;
#Override
public void start(Stage stage) {
Canvas canvas = new Canvas(500, 500);
canvas.setOnMouseDragged(e -> {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
});
canvas.setOnMousePressed(e -> gc.moveTo(e.getX(), e.getY()));
gc = canvas.getGraphicsContext2D();
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(1);
BoxBlur blur = new BoxBlur();
blur.setWidth(1);
blur.setHeight(1);
blur.setIterations(1);
gc.setEffect(blur);
Group root = new Group(canvas);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
stage.setFullScreen(true);
}
public static void main(String[] args) {
launch(args);
}
}
Is there any way to fix the performance issue? My desktop computer is pretty solid and can run high-end games, so its not the performance on my computer.
NOTE: I should also say that the delay that occurs when you first press the mouse on the canvas is bad enough to cause mouse clicks to drop.
EDIT: To confirm it wasnt the OnMousePressed, I commented it out and it did not help.
Apparently my driver was conflicting somehow with Java, which means JavaFX is fine. After getting help from the tablet company and some fixes, this doesn't happen with the latest drivers.
Related
I'm trying to have two boxes with one of them half transparent and the other in orange. Somehow it always just fully replaces the pixels but still kinda applies the transparency to the color. What am I missing? Same happens with loaded Obj files which have d/Tr set to 0.5 for example.
import javafx.application.Application;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class HelloFX extends Application {
#Override
public void start(Stage primaryStage) {
boolean is3DSupported = Platform.isSupported(ConditionalFeature.SCENE3D);
if (!is3DSupported) {
System.out.println("Sorry, 3D is not supported in JavaFX on this platform.");
return;
}
Box boxForeground = new Box(100, 500, 100);
boxForeground.setTranslateX(250);
boxForeground.setTranslateY(100);
boxForeground.setTranslateZ(400);
boxForeground.setMaterial(new PhongMaterial(new Color(0, 0, 0, 0.3)));
Box boxBackground = new Box(100, 100, 100);
boxBackground.setMaterial(new PhongMaterial(Color.ORANGE));
boxBackground.setTranslateX(250);
boxBackground.setTranslateY(200);
boxBackground.setTranslateZ(800);
boolean fixedEyeAtCameraZero = false;
PerspectiveCamera camera = new PerspectiveCamera(fixedEyeAtCameraZero);
camera.setTranslateX(150);
camera.setTranslateY(-100);
camera.setTranslateZ(250);
Group root = new Group(boxForeground, boxBackground);
// root.setDepthTest(DepthTest.ENABLE); // no effect
root.setRotationAxis(Rotate.X_AXIS);
root.setRotate(30);
Scene scene = new Scene(root, 500, 300, true);
scene.setCamera(camera);
primaryStage.setScene(scene);
primaryStage.setTitle("3D Example");
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
I've played around with many options of depth buffer, depth test settings and colors but it seemed to have no good effect.
My expectation is that its possible to slightly see the orange box through the black box with strong transparency.
Actual result:
So based on the comments and my own investigation it seems to be a known bug thats hasn't been fixed in over 5 years. Basically transparency works like in the 2D space of JavaFx. The one last added to the graph paints over the already painted ones - potentially using transparency blending when your colors/pixels contain alpha information.
Bugtracker: Order-independent transparency for 3D objects
Workaround:
The current workaround is to reorder the graph so it matches the desired z-order. Depending on the use case you can group objects on a root level to get close to an ideal transparency handling. But for transparent objects that 'interact' with each other on the same level of the graph you gonna need constant reordering when the objects or the camera move.
When done right you get the expected result:
Group root = new Group(boxForeground, boxBackground); // bug shows
Group root = new Group(boxBackground, boxForeground); // workaround
Apparently JavaFx has a long history of transparency issues. Which are also discussed here: JavaFX 3D Transparency
My setup:
JavaFX SDK 18.0.1
Windows 11
JDK 18.0.1.1
A screen with the touch support
When I try a simple program, none of the touch events are triggered upon touch gestures. My test app follows.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.input.TouchEvent;
import javafx.stage.Stage;
public class JavaFXSandbox extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Group root = new Group();
Scene scene = new Scene(root, 600, 600);
Canvas canvas = new Canvas(600, 600);
root.getChildren().add(canvas);
stage.addEventFilter(TouchEvent.ANY, e -> System.out.println("touch event: " + e));
stage.setScene(scene);
stage.show();
}
}
Is there any way of circumventing the issue, or am I misusing the JavaFX API?
After playing a bit with JavaFX, I found out that touch events are converted to mouse events such, that it's possible to simulate touch events in mouse listeners.
I am trying to build a JavaFX application, where I have a button named "Start" and an ImageView. With the robot class of JavaFX-12, I am trying to take a screenshot of the laptop screen when the button is clicked and show the images one by one during the runtime in the ImageView. My problem is that the JavaFX window does not respond and the program crashes (probably). Even putting the thread into sleep does not seem to work. I assume that it isn't working as I have not set any fps rule, but how can I do that? At the moment, I am creating writable images, converting them into a separate image with a number, saving them, and again reusing them. My goal is to create a screen sharing of the same laptop in the image view. I know that's difficult. I'm new to the JavaFx robot class (not he awt one). Any help is appreciated.
P.S.: The images are properly formed in the directory.
package sample;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
ImageView iv = new ImageView();
iv.setFitWidth(100);
iv.setFitHeight(100);
Button b = new Button("Start");
VBox v = new VBox(10);
v.getChildren().addAll(b,iv);
b.setOnAction(event -> {
Robot r = new Robot();
WritableImage wi = new WritableImage(300,300);
WritableImage i;
Rectangle2D rect = Screen.getPrimary().getVisualBounds();
while(true){
i = r.getScreenCapture(wi,rect);
try {
ImageIO.write(SwingFXUtils.fromFXImage(i,null),"png",new File("F:/Pic/pic" + x + ".png"));
iv.setImage(new Image(new FileInputStream("F:/Pic/pic" + x + ".png")));
//Thread.sleep(500);
//iv.setImage(null);
} catch (Exception e) {
System.out.println(e);
}
}
});
primaryStage.setScene(new Scene(v, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
JavaFX is, like most UI frameworks, single-threaded. You must never block (e.g. sleep) or otherwise monopolize (e.g. while (true)) the JavaFX Application Thread. That thread is responsible for everything related to the UI and if it's not free to do its job then the UI will become unresponsive. Note that a render pass cannot happen until the FX thread returns from whatever it's doing, so setting the image of an ImageView in a loop will have no visible effect until some time after the loop terminates.
Also, a full-throttle while loop will attempt to get a screen capture as fast as the CPU can execute said loop. That is likely to be much faster than the rate at which your UI refreshes and is thus a waste of resources. The rate of screen captures should not exceed the frame rate.
If you need to loop on the FX thread and/or be constrained by the (JavaFX's) frame rate then use the javafx.animation API. In your case, an AnimationTimer seems apt. Here's an example which continuously screenshots the primary screen:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
ImageView view = new ImageView();
view.setFitWidth(720);
view.setFitHeight(480);
// Keep a reference to the AnimationTimer instance if
// you want to be able to start and stop it at will
new AnimationTimer() {
final Robot robot = new Robot();
final Rectangle2D bounds = Screen.getPrimary().getBounds();
#Override
public void handle(long now) {
WritableImage oldImage = (WritableImage) view.getImage();
WritableImage newImage = robot.getScreenCapture(oldImage, bounds);
if (oldImage != newImage) {
view.setImage(newImage);
}
}
}.start();
primaryStage.setScene(new Scene(new StackPane(view)));
primaryStage.show();
}
}
Some notes:
The AnimationTimer#handle(long) method is invoked once per frame. JavaFX tries to target 60 frames-per-second. If that's too fast (the above lags somewhat on my computer) then you can use the method's argument, which is the timestamp of the current frame in nanoseconds, to throttle the rate of screen captures. You could also look into using a Timeline or PauseTransition instead of an AnimationTimer. See JavaFX periodic background task for more information.
The above gives a Droste Effect (I think that's the term?) since the screen capture is displayed on the screen which is being captured.
My example does not include saving each image to a file. You seem to already understand how to do that so you should be able to easily adapt the code. It'd probably be a good idea, however, to move the I/O to a background thread. Unfortunately, that will likely require using different WritableImage instances for each capture to avoid the image being mutated by the FX thread while the background thread reads it. It may also require some tuning or dropped images; I'm not sure how well the I/O will keep up with the influx of screen captures (i.e. standard producer-consumer problems).
As an aside, your question explains you're attempting to share the entire screen. If that's the case then continue using Robot. However, if you only need to share something from the JavaFX application itself then consider using Node#snapshot(SnapshotParameters,WritableImage) or Scene#snapshot(WritableImage) (assuming you can't just send model data instead of images).
If I am drawing in a canvas, I can rotate the coordinate system of the graphicscontext without rotating anything that is already drawn in the corresponding canvas, same with translations.
Is there anything similar possible in a group? If I just rotate and translate it's children I don't get the right effect because translations orient themselves on the unrotated system of the group.
If not, is there anything like a 3d canvas with that functionality?
As Slaw pointed out, what i was looking for were the classes in the package javafx.scene.transform.
Here is an example: Let's say I want a Line starting from (200, 200) with a length of 200 in direction 60 degrees from the x-axis. This would be not so hard to do without the transform package aswell but it shall only serve as an easy example.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.transform.Affine;
import javafx.stage.Stage;
public class Example extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) throws Exception {
Group group = new Group();
Scene scene = new Scene(group, 400, 400);
stage.setScene(scene);
// Does not work as intended
Line line1 = new Line(0,0, 200, 0);
line1.setTranslateX(200);
line1.setTranslateY(200);
line1.setRotate(60);
// Does work as intended.
Line line2 = new Line(0, 0, 200, 0);
Affine affine = new Affine();
affine.appendTranslation(200, 200);
affine.appendRotation(60);
line2.getTransforms().add(affine);
group.getChildren().addAll(line1, line2);
stage.show();
}
}
I'm currently working on a project which relies on the representation of file sizes through rectangles. What I have been stuck on for quite a while however, was scaling of the height to a size that could fit inside of my Stage. This is a crucial part of the project as it allows the user to visually compare the sizes of the files they are viewing from any directory on their machine.
EDIT: I tried adding an image but I haven't got enough reputation yet :(.
http://tinypic.com/r/2e56pol/8
Why don't you just bind the height of the rectangle to the scene's using:
rectangle.heightProperty().bind(scene.heightProperty());
A complete example :
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DemoTabPane extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rectangle = new Rectangle(20,20,200,200);
rectangle.setStroke(Color.BLACK);
Scene scene = new Scene(new HBox(rectangle), 100, 100);
rectangle.heightProperty().bind(scene.heightProperty());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}