I have a set of Nodes, Circles, on the stage.
I want to be able to click on one of them and 'select it' (just get a reference to it so I can move it around, change color etc.)
Pane root = new Pane();
root.getChildren().addAll( /* an array of Circle objects */ );
Scene scene = new Scene(root, 500, 500, BACKGROUND_COLOR);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// how do I get which Circle I clicked on?
}
});
stage.setTitle(TITLE);
stage.setScene(scene);
stage.show();
I would simply register a listener with each circle itself. Then you already have the reference to the circle with which the listener was registered.
This example pushes the limit a little as to usability, because it has 10,000 circles shown all at once, but it demonstrates the technique:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GridOfCircles extends Application {
private static final PseudoClass SELECTED_P_C = PseudoClass.getPseudoClass("selected");
private final int numColumns = 100 ;
private final int numRows = 100 ;
private final double radius = 4 ;
private final double spacing = 2 ;
private final ObjectProperty<Circle> selectedCircle = new SimpleObjectProperty<>();
private final ObjectProperty<Point2D> selectedLocation = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
selectedCircle.addListener((obs, oldSelection, newSelection) -> {
if (oldSelection != null) {
oldSelection.pseudoClassStateChanged(SELECTED_P_C, false);
}
if (newSelection != null) {
newSelection.pseudoClassStateChanged(SELECTED_P_C, true);
}
});
Pane grid = new Pane();
for (int x = 0 ; x < numColumns; x++) {
double gridX = x*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(gridX, 0, gridX, numRows*(spacing + radius + radius)));
}
for (int y = 0; y < numRows ; y++) {
double gridY = y*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(0, gridY, numColumns*(spacing + radius + radius), gridY));
}
for (int x = 0 ; x < numColumns; x++) {
for (int y = 0 ;y < numRows ; y++) {
grid.getChildren().add(createCircle(x, y));
}
}
Label label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
Point2D loc = selectedLocation.get();
if (loc == null) {
return "" ;
}
return String.format("Location: [%.0f, %.0f]", loc.getX(), loc.getY());
}, selectedLocation));
BorderPane root = new BorderPane(new ScrollPane(grid));
root.setTop(label);
Scene scene = new Scene(root);
scene.getStylesheets().add("grid.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createCircle(int x, int y) {
Circle circle = new Circle();
circle.getStyleClass().add("intersection");
circle.setCenterX(x * (spacing + radius + radius) + spacing);
circle.setCenterY(y * (spacing + radius + radius) + spacing);
circle.setRadius(radius);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
selectedCircle.set(circle);
selectedLocation.set(new Point2D(x, y));
});
return circle ;
}
public static void main(String[] args) {
launch(args);
}
}
with the file grid.css:
.intersection {
-fx-fill: blue ;
}
.intersection:selected {
-fx-fill: gold ;
}
You can get the reference by using getSource of the MouseEvent.
Example in which you can drag a Circle and any other Node:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Circle circle = new Circle( 100,100,50);
circle.setStroke(Color.BLUE);
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
Rectangle rectangle = new Rectangle( 0,0,100,100);
rectangle.relocate(200, 200);
rectangle.setStroke(Color.GREEN);
rectangle.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.3));
Text text = new Text( "Example Text");
text.relocate(300, 300);
Pane root = new Pane();
root.getChildren().addAll(circle, rectangle, text);
MouseGestures mouseGestures = new MouseGestures();
mouseGestures.makeDraggable(circle);
mouseGestures.makeDraggable(rectangle);
mouseGestures.makeDraggable(text);
Scene scene = new Scene(root, 1024, 768);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = (Node) (event.getSource());
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = (Node) (event.getSource());
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = event -> {
};
}
public static void main(String[] args) {
launch(args);
}
}
Related
I am trying to create a first person camera in JavaFX based on bindings. The camera and the actual position both work perfectly. The only problem is that they don’t match! As you can see in the picture, the actual position (red box) is in the middle of the circle, but the camera is outside. How can I change that? What did I do wrong?
The Player class handles the PerspectiveCamera.
package game;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Player extends Character {
private static final Robot ROBOT = new Robot();
private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
private DoubleProperty relativeCenterY = new SimpleDoubleProperty();
protected PerspectiveCamera camera = new PerspectiveCamera();
protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);
protected Translate translate = new Translate();
protected DoubleProperty centerX = new SimpleDoubleProperty();
protected DoubleProperty centerY = new SimpleDoubleProperty();
#SuppressWarnings("exports")
public Player(Stage stage) {
camera.getTransforms().addAll(xAxis, yAxis);
centerX.bind(stage.widthProperty().divide(2));
centerY.bind(stage.heightProperty().divide(2));
relativeCenterX.bind(stage.xProperty().add(centerX));
relativeCenterY.bind(stage.yProperty().add(centerY));
camera.translateXProperty().bind(posX.subtract(centerX));
camera.translateYProperty().bind(posZ);
camera.translateZProperty().bind(posY.subtract(centerY));
xAxis.angleProperty().bind(viewX.subtract(90));
yAxis.angleProperty().bind(viewY);
translate.xProperty().bind(posX);
translate.zProperty().bind(posY);
translate.yProperty().bind(posZ);
}
#SuppressWarnings("exports")
public EventHandler<KeyEvent> getKeyHandle() {
return e -> {
switch (e.getCode()) {
case A:
view(-1, 0);
break;
case D:
view(1, 0);
break;
case W:
move(1, 1, 0);
break;
case S:
move(-1, -1, 0);
break;
case SPACE:
move(0, 0, 10);
break;
case F:
move(0, 0, -10);
break;
default:
break;
}
};
}
#SuppressWarnings("exports")
public EventHandler<MouseEvent> getMouseHandle() {
return e -> {
view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
Platform.runLater(() -> {
ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
});
};
}
#SuppressWarnings("exports")
public PerspectiveCamera getPespectiveCamera() {
return camera;
}
}
The Character class calculates position and view.
package game;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public abstract class Character {
protected DoubleProperty posX = new SimpleDoubleProperty();
protected DoubleProperty posY = new SimpleDoubleProperty();
protected DoubleProperty posZ = new SimpleDoubleProperty();
protected DoubleProperty viewX = new SimpleDoubleProperty();
protected DoubleProperty viewY = new SimpleDoubleProperty();
protected DoubleProperty speed = new SimpleDoubleProperty(10);
public void move(double x, double y, double z) {
double fX = Math.cos(Math.toRadians(viewX.get()));
double fY = -Math.sin(Math.toRadians(viewX.get()));
double fZ = 1;
posX.set(posX.get() + fX * x * speed.get());
posY.set(posY.get() + fY * y * speed.get());
posZ.set(posZ.get() + fZ * z);
}
public void view(double x, double y) {
viewX.set(viewX.get() + x);
viewY.set(viewY.get() + y);
}
#SuppressWarnings("exports")
public DoubleProperty posXPorperty() {
return posX;
}
#SuppressWarnings("exports")
public DoubleProperty posYPorperty() {
return posY;
}
#SuppressWarnings("exports")
public DoubleProperty posZPorperty() {
return posZ;
}
#SuppressWarnings("exports")
public DoubleProperty viewXPorperty() {
return viewX;
}
#SuppressWarnings("exports")
public DoubleProperty viewYPorperty() {
return viewY;
}
}
My Application, which shows the total graphical content.
package graphics;
import game.Player;
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GameStage extends Application implements Runnable {
#Override
public void run() {
launch();
}
#SuppressWarnings("exports")
#Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, 500, 500);
Group content = new Group(), map = new Group();
ContentScene subscene = new ContentScene(content, map, 500, 500);
subscene.widthProperty().bind(scene.widthProperty());
subscene.heightProperty().bind(scene.heightProperty());
pane.getChildren().add(subscene);
pane.setBottom(map);
Player player = new Player(stage);
Box box = new Box(50, 50, 50);
box.translateXProperty().bind(player.posXPorperty());
box.translateYProperty().bind(player.posZPorperty());
box.translateZProperty().bind(player.posYPorperty());
box.rotateProperty().bind(player.viewXPorperty());
box.setMaterial(new PhongMaterial(Color.RED));
content.getChildren().add(box);
Rectangle rectangle = new Rectangle(5, 5);
rectangle.translateXProperty().bind(player.posXPorperty().divide(10));
rectangle.translateYProperty().bind(player.posYPorperty().divide(10));
rectangle.setFill(Color.RED);
map.getChildren().add(rectangle);
subscene.setCamera(player.getPespectiveCamera());
scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
scene.setFill(Color.BLACK);
Cursor cursor = Cursor.CROSSHAIR;
scene.setCursor(cursor);
stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() != KeyCode.F11) {
return;
}
if (stage.isFullScreen()) {
stage.setFullScreen(false);
} else {
stage.setFullScreen(true);
}
});
stage.setAlwaysOnTop(true);
stage.setScene(scene);
stage.show();
}
private class ContentScene extends SubScene {
public ContentScene(Group content, Group map, double width, double height) {
super(content, width, height, true, SceneAntialiasing.BALANCED);
PhongMaterial material = new PhongMaterial(Color.AQUA);
for (int v = 0; v < 3_600; v += 180) {
for (int y = 0; y < 500; y += 100) {
Box box = new Box(50, 50, 50);
box.setTranslateX(Math.sin(v / 10) * 1_000);
box.setTranslateY(y);
box.setTranslateZ(Math.cos(v / 10) * 1_000);
box.setMaterial(material);
content.getChildren().add(box);
Rectangle rectangle = new Rectangle(5, 5);
rectangle.translateXProperty().bind(box.translateXProperty().divide(10));
rectangle.translateYProperty().bind(box.translateZProperty().divide(10));
rectangle.setFill(Color.AQUA);
map.getChildren().add(rectangle);
}
}
}
}
}
Thanks to Thomas, I was able to solve the problem. The code now loks like this:
package graphics;
import game.Player;
import javafx.application.Application;
import javafx.scene.AmbientLight;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GameStage extends Application implements Runnable {
#Override
public void run() {
launch();
}
#SuppressWarnings("exports")
#Override
public void start(Stage stage) throws Exception {
// Parents
BorderPane pane = new BorderPane();
Group content = new Group(new AmbientLight()), map = new Group();
Pane stackpane = new Pane(map);
// Scenes
Scene scene = new Scene(pane, 500, 500);
SubScene contentSubscene = new SubScene(content, 500, 500, true, SceneAntialiasing.BALANCED);
contentSubscene.widthProperty().bind(scene.widthProperty());
contentSubscene.heightProperty().bind(scene.heightProperty());
SubScene minimapSubscene = new SubScene(stackpane, 256, 256);
minimapSubscene.setFill(Color.DARKGREY);
pane.getChildren().add(contentSubscene);
pane.setBottom(minimapSubscene);
// Create Player
Player player = new Player(stage);
Rectangle currentPosition = new Rectangle(5, 5);
currentPosition.layoutXProperty().bind(minimapSubscene.widthProperty().divide(2));
currentPosition.layoutYProperty().bind(minimapSubscene.heightProperty().divide(2));
currentPosition.setFill(Color.RED);
stackpane.getChildren().add(currentPosition);
map.layoutXProperty().bind(player.posXPorperty().divide(-10).add(minimapSubscene.widthProperty().divide(2)));
map.layoutYProperty().bind(player.posYPorperty().divide(-10).add(minimapSubscene.heightProperty().divide(2)));
// Create Box in
PhongMaterial material = new PhongMaterial(Color.AQUA);
for (int v = 0; v < 3_600; v += 180) {
for (int y = 0; y < 500; y += 100) {
Box box = new Box(50, 50, 50);
box.setTranslateX(Math.sin(v / 10) * 1_000);
box.setTranslateY(y);
box.setTranslateZ(Math.cos(v / 10) * 1_000);
box.setMaterial(material);
content.getChildren().add(box);
Rectangle boxPosition = new Rectangle(5, 5);
boxPosition.translateXProperty().bind(box.translateXProperty().divide(10));
boxPosition.translateYProperty().bind(box.translateZProperty().divide(10));
boxPosition.setFill(Color.AQUA);
map.getChildren().add(boxPosition);
}
}
contentSubscene.setCamera(player.getPespectiveCamera());
scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
scene.setCursor(Cursor.CROSSHAIR);
scene.setFill(Color.WHITE);
stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() != KeyCode.F11) {
return;
}
if (stage.isFullScreen()) {
stage.setFullScreen(false);
} else {
stage.setFullScreen(true);
}
});
stage.setAlwaysOnTop(true);
stage.setScene(scene);
stage.show();
}
}
package game;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public abstract class Character {
protected DoubleProperty posX = new SimpleDoubleProperty();
protected DoubleProperty posY = new SimpleDoubleProperty();
protected DoubleProperty posZ = new SimpleDoubleProperty();
protected DoubleProperty viewX = new SimpleDoubleProperty();
protected DoubleProperty viewY = new SimpleDoubleProperty();
protected DoubleProperty speed = new SimpleDoubleProperty(10);
public void move(double x, double y, double z) {
double fX = Math.cos(Math.toRadians(viewX.get()));
double fY = -Math.sin(Math.toRadians(viewX.get()));
double fZ = 1;
posX.set(posX.get() + fX * x * speed.get());
posY.set(posY.get() + fY * y * speed.get());
posZ.set(posZ.get() + fZ * z);
}
public void view(double x, double y) {
viewX.set(viewX.get() + x);
viewY.set(viewY.get() + y);
}
#SuppressWarnings("exports")
public DoubleProperty posXPorperty() {
return posX;
}
#SuppressWarnings("exports")
public DoubleProperty posYPorperty() {
return posY;
}
#SuppressWarnings("exports")
public DoubleProperty posZPorperty() {
return posZ;
}
#SuppressWarnings("exports")
public DoubleProperty viewXPorperty() {
return viewX;
}
#SuppressWarnings("exports")
public DoubleProperty viewYPorperty() {
return viewY;
}
}
package game;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class Player extends Character {
private static final Robot ROBOT = new Robot();
private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
private DoubleProperty relativeCenterY = new SimpleDoubleProperty();
protected PerspectiveCamera camera = new PerspectiveCamera(true);
protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);
protected DoubleProperty centerX = new SimpleDoubleProperty();
protected DoubleProperty centerY = new SimpleDoubleProperty();
#SuppressWarnings("exports")
public Player(Stage stage) {
camera.getTransforms().addAll(xAxis, yAxis);
camera.setFieldOfView((40 + 62) / 2);
camera.setNearClip(0.1);
camera.setFarClip(100000);
camera.setVerticalFieldOfView(true);
centerX.bind(stage.widthProperty().divide(2));
centerY.bind(stage.heightProperty().divide(2));
relativeCenterX.bind(stage.xProperty().add(centerX));
relativeCenterY.bind(stage.yProperty().add(centerY));
xAxis.angleProperty().bind(viewX.subtract(90));
yAxis.angleProperty().bind(viewY);
camera.translateXProperty().bind(posX);
camera.translateZProperty().bind(posY);
camera.translateYProperty().bind(posZ);
}
#SuppressWarnings("exports")
public EventHandler<KeyEvent> getKeyHandle() {
return e -> {
switch (e.getCode()) {
case A:
view(-1, 0);
break;
case D:
view(1, 0);
break;
case W:
move(1, 1, 0);
break;
case S:
move(-1, -1, 0);
break;
case SPACE:
move(0, 0, 10);
break;
case F:
move(0, 0, -10);
break;
default:
break;
}
};
}
#SuppressWarnings("exports")
public EventHandler<MouseEvent> getMouseHandle() {
return e -> {
view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
Platform.runLater(() -> {
ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
});
};
}
#SuppressWarnings("exports")
public PerspectiveCamera getPespectiveCamera() {
return camera;
}
}
I have a problem in Grouping, javafx. My program is to draw a Rectangle on ImageView for cropping the Image. The co-ordinates are obtained from mouse press and mouse release. When i put the group in GridPane layout the rectangle is not drawn at right position.
My Program is
package application;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
double x1,y1,x2,y2;
int ulx,uly,w,h;
String filename;
#Override
public void start(Stage primaryStage) {
try {
GridPane grid = new GridPane();
//grid.setAlignment(Pos.TOP_LEFT);
grid.setHgap(20);
grid.setVgap(10);
grid.setPadding(new Insets(0, 10, 10, 10));
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth() - 100;
double height = screenSize.getHeight()- 100;
double panelWidth = (width)/2 - 50;
double panelHeight = (9*panelWidth)/16;
double panelX = width-panelWidth-50-20;
double panelY = 0.0;
Rectangle rectangle = new Rectangle();
BorderPane root = new BorderPane();
Scene scene = new Scene(grid,width,height);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
File imagePath1 = new File("plugins\\image\\car.jpg");
Image image1 = new Image(imagePath1.toURI().toString());
ImageView imageView1 = new ImageView();
imageView1.setId("imageView");
imageView1.setImage(image1);
imageView1.setFitWidth(panelWidth);
imageView1.setFitHeight(panelHeight);
imageView1.setPreserveRatio(false);
imageView1.setSmooth(true);
imageView1.setCache(true);
File imagePath = new File("plugins\\image\\car.jpg");
Image image = new Image(imagePath.toURI().toString());
ImageView imageView = new ImageView();
imageView.setId("imageView");
imageView.setImage(image);
imageView.setFitWidth(panelWidth);
imageView.setFitHeight(panelHeight);
imageView.setPreserveRatio(false);
imageView.setSmooth(true);
imageView.setCache(true);
imageView.setOnMousePressed(new EventHandler <MouseEvent>()
{
public void handle(MouseEvent event)
{
imageView.setMouseTransparent(true);
x1 = event.getSceneX() ;
y1 = event.getSceneY();
System.out.println(x1+" "+y1);
event.setDragDetect(true);
}
});
imageView.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// TODO Auto-generated method stub
double tx1=x1,ty1=y1,tx2=0,ty2=0;
double tempX=0,tempY=0;
if(event.getSceneX()<panelX || event.getSceneX()>(panelX+panelWidth) || event.getSceneY()<0 || event.getSceneY()>(panelY+panelHeight)){
if(event.getSceneX()<panelX){
tempX = 0;
tempY = event.getSceneY();
}
else if(event.getSceneX()>(panelX+panelWidth)){
tempX = panelX+panelWidth;
tempY = event.getSceneY();
}
if(event.getSceneY()<panelY){
tempX = event.getSceneX();
tempY = 0;
}
else if(event.getSceneY()>(panelY+panelHeight)){
tempX = event.getSceneX();
tempY = panelY+panelHeight;
}
}
else{
tempX = event.getSceneX();
tempY = event.getSceneY();
}
if(tempX<tx1 && tempY<ty1){
tx2 = tx1;
tx1 = tempX;
ty2 = ty1;
ty1 = tempY;
}
else if(tempX<tx1 && tempY>ty1){
tx2 = tx1;
tx1 = tempX;
ty2 = tempY;
}
else if(tempX>tx1 && tempY<ty1){
tx2 = tempX;
ty2 = ty1;
ty1 = tempY;
}
else if(tempX>tx1 && tempY>ty1){
tx2 = tempX;
ty2 = tempY;
}
ulx = (int)tx1;
uly = (int)ty1;
w =(int) (tx2-tx1);
h = (int)(ty2-ty1);
rectangle.setX(ulx);
rectangle.setY(uly);
rectangle.setWidth(w);
rectangle.setHeight(h);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setStroke(Color.WHITE);
rectangle.setStrokeWidth(2);
rectangle.getStrokeDashArray().addAll(7d, 5d);
}
});
imageView.setOnMouseReleased(new EventHandler <MouseEvent>()
{
public void handle(MouseEvent event)
{
imageView.setMouseTransparent(false);
if(event.getSceneX()<x1){
x2 = x1;
x1 =event.getSceneX();
}
else{
x2 = event.getSceneX();
}
if(event.getSceneY()<y1){
y2 = y1;
y1 =event.getSceneY();
}
else{
y2 = event.getSceneY();
}
ulx = (int)x1;
uly = (int)y1;
w =(int) (x2-x1);
h = (int)(y2-y1);
System.out.println(x1+" "+y1+" "+x2+" "+y2+" "+w+" "+h);
filename = String.valueOf(x1)+"_"+String.valueOf(y1)+"_"+String.valueOf(w)+"_"+String.valueOf(h);
rectangle.setX(ulx);
rectangle.setY(uly);
rectangle.setWidth(w);
rectangle.setHeight(h);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setStroke(Color.WHITE);
rectangle.setStrokeWidth(2);
rectangle.getStrokeDashArray().addAll(7d, 5d);
}
});
Group group = new Group(imageView, rectangle);
HBox option = new HBox();
Button save = new Button("SAVE");
save.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e){
double x = x1-panelX;
double y = y1-panelY;
WritableImage image = imageView.snapshot(new SnapshotParameters(), null);
PixelReader reader = image.getPixelReader();
WritableImage newImage = new WritableImage(reader, (int)(x), (int)(y), w, h);
// TODO: probably use a file chooser here
filename = String.valueOf(x)+"_"+String.valueOf(y)+"_"+String.valueOf(w)+"_"+String.valueOf(h);
File file = new File(filename+".png");
try {
ImageIO.write(SwingFXUtils.fromFXImage(newImage, null), "png", file);
} catch (IOException ec) {
// TODO: handle exception here
}
}
});
option.getChildren().add(save);
root.setLeft(group);
grid.add(imageView1, 0, 0);
grid.add(group, 1, 0);
grid.add(option, 1, 1);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
My output Screenshot is
But the rectangle should be in the imageView
USAGE
right click to delete the crop lines
double click to cut the image
you see the screen shots?
some of your calculations are unnecessary, run everything in MouseEvent it does the hard work for you.
forget GridPane and use absolute co-ordinates as you are cropping, GridPane will alter the positions because it is forced to obey layout rules in the onLayout() use somebody like Pane and wrap in in HBox then put them in ScrollPane, that's it, easy and nice like her...
The images are online, my sister is very popular :)
code
public class Main extends Application {
final Rectangle rectBound = new Rectangle(0, 0);
Random ran = new Random();
#Override
public void start(Stage primaryStage) {
try {
rectBound.setFill(Color.TRANSPARENT);
rectBound.setStroke(Color.GOLD);
ScrollPane scp = new ScrollPane();
HBox root = new HBox(15);
scp.setContent(root);
//root.setOrientation(Orientation.HORIZONTAL);
Pane imageViewParent = new Pane();
imageViewParent.setStyle("-fx-border-color: black; -fx-border-width: 2;");
ImageView imageView1 = new ImageView(new Image(getUrl(),true));
imageView1.setLayoutX(0.0);imageView1.setLayoutY(0.0);
imageViewParent.getChildren().add(imageView1);
///////////////////////////////////////////////
imageViewParent.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
if (rectBound.getParent() == null) {
rectBound.setWidth(0.0); rectBound.setHeight(0.0);
rectBound.setLayoutX(event.getX()); rectBound.setLayoutY(event.getY()); // setX or setY
imageViewParent.getChildren().add(rectBound);
}
} else if (event.getEventType() == MouseEvent.MOUSE_RELEASED) {
} else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
rectBound.setWidth(event.getX() - rectBound.getLayoutX());
rectBound.setHeight(event.getY() - rectBound.getLayoutY());
} else if (event.getEventType() == MouseEvent.MOUSE_CLICKED
&& event.getButton() == MouseButton.SECONDARY) {
if (rectBound.getParent() != null) {
imageViewParent.getChildren().remove(rectBound);
}
} else if (event.getEventType() == MouseEvent.MOUSE_CLICKED
&& event.getButton() == MouseButton.PRIMARY && event.getClickCount() > 1) {
//////////////// i crop here //////////////
PixelReader reader = imageView1.getImage().getPixelReader();
WritableImage newImage = new WritableImage(reader, (int) rectBound.getLayoutX(),
(int) rectBound.getLayoutY(),
(int) rectBound.getWidth(),
(int) rectBound.getHeight());
root.getChildren().add(new ImageView(newImage));
}
}
});
//////////////////////////////////////////////
root.getChildren().add(imageViewParent);
Scene scene = new Scene(scp,900,650);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private String getUrl() {
switch (ran.nextInt(4)) {
case 1:
return "http://6544-presscdn-0-22.pagely.netdna-cdn.com/wp-content/uploads/2013/06/Juliet-Ibrahim2.jpg";
case 4:
return "http://6544-presscdn-0-22.pagely.netdna-cdn.com/wp-content/uploads/2016/02/Nadia-Buari.jpg";
case 2:
return "http://www.eonlineghana.com/wp-content/uploads/2016/03/1.jpg";
case 3:
return "http://fashionpoliceng.com/wp-content/uploads/2015/08/Juliet-Ibrahim-527x600.jpg";
default:
return "http://6544-presscdn-0-22.pagely.netdna-cdn.com/wp-content/uploads/2014/04/Juliet-Ibrahim-Foundation-1.jpg";
}
}
public static void main(String[] args) {
launch(args);
}
}
hope it helps
I have a set of Nodes, Circles, on the stage.
I want to be able to click on one of them and 'select it' (just get a reference to it so I can move it around, change color etc.)
Pane root = new Pane();
root.getChildren().addAll( /* an array of Circle objects */ );
Scene scene = new Scene(root, 500, 500, BACKGROUND_COLOR);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// how do I get which Circle I clicked on?
}
});
stage.setTitle(TITLE);
stage.setScene(scene);
stage.show();
I would simply register a listener with each circle itself. Then you already have the reference to the circle with which the listener was registered.
This example pushes the limit a little as to usability, because it has 10,000 circles shown all at once, but it demonstrates the technique:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GridOfCircles extends Application {
private static final PseudoClass SELECTED_P_C = PseudoClass.getPseudoClass("selected");
private final int numColumns = 100 ;
private final int numRows = 100 ;
private final double radius = 4 ;
private final double spacing = 2 ;
private final ObjectProperty<Circle> selectedCircle = new SimpleObjectProperty<>();
private final ObjectProperty<Point2D> selectedLocation = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
selectedCircle.addListener((obs, oldSelection, newSelection) -> {
if (oldSelection != null) {
oldSelection.pseudoClassStateChanged(SELECTED_P_C, false);
}
if (newSelection != null) {
newSelection.pseudoClassStateChanged(SELECTED_P_C, true);
}
});
Pane grid = new Pane();
for (int x = 0 ; x < numColumns; x++) {
double gridX = x*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(gridX, 0, gridX, numRows*(spacing + radius + radius)));
}
for (int y = 0; y < numRows ; y++) {
double gridY = y*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(0, gridY, numColumns*(spacing + radius + radius), gridY));
}
for (int x = 0 ; x < numColumns; x++) {
for (int y = 0 ;y < numRows ; y++) {
grid.getChildren().add(createCircle(x, y));
}
}
Label label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
Point2D loc = selectedLocation.get();
if (loc == null) {
return "" ;
}
return String.format("Location: [%.0f, %.0f]", loc.getX(), loc.getY());
}, selectedLocation));
BorderPane root = new BorderPane(new ScrollPane(grid));
root.setTop(label);
Scene scene = new Scene(root);
scene.getStylesheets().add("grid.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createCircle(int x, int y) {
Circle circle = new Circle();
circle.getStyleClass().add("intersection");
circle.setCenterX(x * (spacing + radius + radius) + spacing);
circle.setCenterY(y * (spacing + radius + radius) + spacing);
circle.setRadius(radius);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
selectedCircle.set(circle);
selectedLocation.set(new Point2D(x, y));
});
return circle ;
}
public static void main(String[] args) {
launch(args);
}
}
with the file grid.css:
.intersection {
-fx-fill: blue ;
}
.intersection:selected {
-fx-fill: gold ;
}
You can get the reference by using getSource of the MouseEvent.
Example in which you can drag a Circle and any other Node:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Circle circle = new Circle( 100,100,50);
circle.setStroke(Color.BLUE);
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
Rectangle rectangle = new Rectangle( 0,0,100,100);
rectangle.relocate(200, 200);
rectangle.setStroke(Color.GREEN);
rectangle.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.3));
Text text = new Text( "Example Text");
text.relocate(300, 300);
Pane root = new Pane();
root.getChildren().addAll(circle, rectangle, text);
MouseGestures mouseGestures = new MouseGestures();
mouseGestures.makeDraggable(circle);
mouseGestures.makeDraggable(rectangle);
mouseGestures.makeDraggable(text);
Scene scene = new Scene(root, 1024, 768);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = (Node) (event.getSource());
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = (Node) (event.getSource());
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = event -> {
};
}
public static void main(String[] args) {
launch(args);
}
}
when I run this code this, the action will stop when the mouse click.
The ball will stop action when I click mouse. how do I make the ball do the action continuously although I click the mouse to add other balls.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class GamePractice extends Application {
public static Circle circle;
public static Pane canvas;
#Override
public void start(final Stage primaryStage) {
canvas = new Pane();
final Scene scene = new Scene(canvas, 800, 600);
primaryStage.setTitle("Game");
primaryStage.setScene(scene);
primaryStage.show();
circle = new Circle(15, Color.BLUE);
circle.relocate(100, 100);
canvas.getChildren().addAll(circle);
final Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>() {
double deltaX = (double)(Math.random()*10) + 3;
double deltaY = (double)(Math.random()*10) + 3;
#Override
public void handle(final ActionEvent t) {
circle.setLayoutX(circle.getLayoutX() + deltaX);
circle.setLayoutY(circle.getLayoutY() + deltaY);
final Bounds bounds = canvas.getBoundsInLocal();
final boolean atRightBorder = circle.getLayoutX() >= (bounds.getMaxX() - circle.getRadius());
final boolean atLeftBorder = circle.getLayoutX() <= (bounds.getMinX() + circle.getRadius());
final boolean atBottomBorder = circle.getLayoutY() >= (bounds.getMaxY() - circle.getRadius());
final boolean atTopBorder = circle.getLayoutY() <= (bounds.getMinY() + circle.getRadius());
if (atRightBorder || atLeftBorder) {
deltaX *= -1;
}
if (atBottomBorder || atTopBorder) {
deltaY *= -1;
}
}
}));
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
if (!(canvas.getChildren().isEmpty())) {
canvas.getChildren().remove(0);
}
}
else {
int red = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int x = (int)(Math.random()*801);
int y = (int)(Math.random()*601);
circle = new Circle(15, Color.rgb(red, green, blue));
circle.relocate(x, y);
canvas.getChildren().addAll(circle);
}
}
});
loop.setCycleCount(Timeline.INDEFINITE);
loop.play();
}
public static void main(final String[] args) {
launch(args);
}
}
Just create an animation for each circle:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class GamePractice extends Application {
private Pane canvas;
#Override
public void start(final Stage primaryStage) {
canvas = new Pane();
final Scene scene = new Scene(canvas, 800, 600);
primaryStage.setTitle("Game");
primaryStage.setScene(scene);
primaryStage.show();
addCircle(100, 100, Color.BLUE);
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
if (!(canvas.getChildren().isEmpty())) {
canvas.getChildren().remove(0);
}
}
else {
int red = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int x = (int)(Math.random()*801);
int y = (int)(Math.random()*601);
addCircle(x, y, Color.rgb(red, green, blue));
}
}
});
}
private void addCircle(double x, double y, Color color) {
Circle circle = new Circle(15, color);
circle.relocate(x, y);
canvas.getChildren().addAll(circle);
final Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>() {
double deltaX = (double)(Math.random()*10) + 3;
double deltaY = (double)(Math.random()*10) + 3;
#Override
public void handle(final ActionEvent t) {
circle.setLayoutX(circle.getLayoutX() + deltaX);
circle.setLayoutY(circle.getLayoutY() + deltaY);
final Bounds bounds = canvas.getBoundsInLocal();
final boolean atRightBorder = circle.getLayoutX() >= (bounds.getMaxX() - circle.getRadius());
final boolean atLeftBorder = circle.getLayoutX() <= (bounds.getMinX() + circle.getRadius());
final boolean atBottomBorder = circle.getLayoutY() >= (bounds.getMaxY() - circle.getRadius());
final boolean atTopBorder = circle.getLayoutY() <= (bounds.getMinY() + circle.getRadius());
if (atRightBorder || atLeftBorder) {
deltaX *= -1;
}
if (atBottomBorder || atTopBorder) {
deltaY *= -1;
}
}
}));
loop.setCycleCount(Timeline.INDEFINITE);
loop.play();
}
public static void main(final String[] args) {
launch(args);
}
}
I'm trying to write a painting application in JavaFX. I want a brush resembling a real paintbrush, but I'm not sure how to start the algorithm. The code below shows my current paintbrush stroke, although it's a useful stroke, it's not really a paintbrush:
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.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.stage.Stage;
import static javafx.scene.input.MouseEvent.*;
public class BrushTester extends Application {
private static final Color color = Color.CHOCOLATE;
private static final double START_OPACITY = 0.3;
private static final double OPACITY_MODIFIER = 0.002;
private double currentOpacity = START_OPACITY;
private double strokeWidth = 15;
public static void main(String[] args) {
Application.launch(BrushTester.class);
}
#Override
public void start(Stage primaryStage) throws Exception {
Canvas canvas = new Canvas(600d, 600d);
GraphicsContext gc = canvas.getGraphicsContext2D();
canvas.addEventHandler(MOUSE_DRAGGED, e -> BrushTester.this.handleMouseDragged(gc, e));
canvas.addEventHandler(MOUSE_PRESSED, e -> handleMousePressed(gc, e));
canvas.addEventHandler(MOUSE_RELEASED, e -> handleMouseReleased(gc, e));
Group root = new Group();
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root, Color.DARKGRAY));
primaryStage.show();
}
private void configureGraphicsContext(GraphicsContext gc) {
gc.setStroke(new Color(color.getRed(), color.getGreen(), color.getBlue(), currentOpacity));
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(strokeWidth);
}
public void handleMousePressed(GraphicsContext gc, MouseEvent e) {
configureGraphicsContext(gc);
gc.beginPath();
gc.moveTo(e.getX(), e.getY());
gc.stroke();
}
public void handleMouseReleased(GraphicsContext gc, MouseEvent e) {
currentOpacity = START_OPACITY;
gc.closePath();
}
public void handleMouseDragged(GraphicsContext gc, MouseEvent e) {
currentOpacity = Math.max(0, currentOpacity - OPACITY_MODIFIER);
configureGraphicsContext(gc);
gc.lineTo(e.getX(), e.getY());
gc.stroke();
}
}
Anyone with some tips on how to get closer to the real thing?
It all depends on what you're trying to achieve. Personally I would use
an AnimationTimer
a customizable Brush (i. e. an Image) instead of a stroke, so you can specify size and hardness
a line drawing algorithm (like Bresenham) to connect the previous mouse location with the current one to get a full line between points
A quick example with a simple drawing algorithm:
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
private static double SCENE_WIDTH = 1280;
private static double SCENE_HEIGHT = 720;
static Random random = new Random();
Canvas canvas;
GraphicsContext graphicsContext;
AnimationTimer loop;
Point2D mouseLocation = new Point2D( 0, 0);
boolean mousePressed = false;
Point2D prevMouseLocation = new Point2D( 0, 0);
Scene scene;
Image brush = createBrush( 30.0, Color.CHOCOLATE);
double brushWidthHalf = brush.getWidth() / 2.0;
double brushHeightHalf = brush.getHeight() / 2.0;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
canvas = new Canvas( SCENE_WIDTH, SCENE_HEIGHT);
graphicsContext = canvas.getGraphicsContext2D();
Pane layerPane = new Pane();
layerPane.getChildren().addAll(canvas);
root.setCenter(layerPane);
scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
addListeners();
startAnimation();
}
private void startAnimation() {
loop = new AnimationTimer() {
#Override
public void handle(long now) {
if( mousePressed) {
// try this
// graphicsContext.drawImage( brush, mouseLocation.getX() - brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
// then this
bresenhamLine( prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
}
prevMouseLocation = new Point2D( mouseLocation.getX(), mouseLocation.getY());
}
};
loop.start();
}
// https://de.wikipedia.org/wiki/Bresenham-Algorithmus
private void bresenhamLine(double x0, double y0, double x1, double y1)
{
double dx = Math.abs(x1-x0), sx = x0<x1 ? 1. : -1.;
double dy = -Math.abs(y1-y0), sy = y0<y1 ? 1. : -1.;
double err = dx+dy, e2; /* error value e_xy */
while( true){
graphicsContext.drawImage( brush, x0 - brushWidthHalf, y0 - brushHeightHalf);
if (x0==x1 && y0==y1) break;
e2 = 2.*err;
if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
}
private void addListeners() {
scene.addEventFilter(MouseEvent.ANY, e -> {
mouseLocation = new Point2D(e.getX(), e.getY());
mousePressed = e.isPrimaryButtonDown();
});
}
public static Image createImage(Node node) {
WritableImage wi;
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
int imageWidth = (int) node.getBoundsInLocal().getWidth();
int imageHeight = (int) node.getBoundsInLocal().getHeight();
wi = new WritableImage(imageWidth, imageHeight);
node.snapshot(parameters, wi);
return wi;
}
public static Image createBrush( double radius, Color color) {
// create gradient image with given color
Circle brush = new Circle(radius);
RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
brush.setFill(gradient1);
// create image
return createImage(brush);
}
public static void main(String[] args) {
launch(args);
}
}
Of course you can extend this with e. g.
multiple layers
JavaFX's blend modes on layer and graphicscontext level
to simulate force I'd use a paint delay (eg 200 ms) and a buffer for the mouse locations and let the opacity depend on whether the mouse is still pressed or not
smooth the lines by using bezier curves
...
Example with Brush variations when you start painting:
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
private static double SCENE_WIDTH = 1280;
private static double SCENE_HEIGHT = 720;
static Random random = new Random();
Canvas canvas;
GraphicsContext graphicsContext;
AnimationTimer loop;
Point2D mouseLocation = new Point2D( 0, 0);
boolean mousePressed = false;
Point2D prevMouseLocation = new Point2D( 0, 0);
Scene scene;
double brushMaxSize = 30;
Image brush = createBrush( brushMaxSize, Color.CHOCOLATE);
double brushWidthHalf = brush.getWidth() / 2.0;
double brushHeightHalf = brush.getHeight() / 2.0;
double pressure = 0;
double pressureDelay = 0.04;
private Image[] brushVariations = new Image[256];
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
canvas = new Canvas( SCENE_WIDTH, SCENE_HEIGHT);
for( int i=0; i < brushVariations.length; i++) {
double size = (brushMaxSize - 1) / (double) brushVariations.length * (double) i + 1;
brushVariations[i] = createBrush( size, Color.CHOCOLATE);
}
graphicsContext = canvas.getGraphicsContext2D();
Pane layerPane = new Pane();
layerPane.getChildren().addAll(canvas);
root.setCenter(layerPane);
scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
addListeners();
startAnimation();
}
private void startAnimation() {
loop = new AnimationTimer() {
#Override
public void handle(long now) {
if( mousePressed) {
// try this
// graphicsContext.drawImage( brush, mouseLocation.getX() - brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
// then this
bresenhamLine( prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
pressure += pressureDelay;
if( pressure > 1) {
pressure = 1;
}
} else {
pressure = 0;
}
prevMouseLocation = new Point2D( mouseLocation.getX(), mouseLocation.getY());
}
};
loop.start();
}
// https://de.wikipedia.org/wiki/Bresenham-Algorithmus
private void bresenhamLine(double x0, double y0, double x1, double y1)
{
double dx = Math.abs(x1-x0), sx = x0<x1 ? 1. : -1.;
double dy = -Math.abs(y1-y0), sy = y0<y1 ? 1. : -1.;
double err = dx+dy, e2; /* error value e_xy */
while( true){
int variation = (int) (pressure * (brushVariations.length - 1));
Image brushVariation = brushVariations[ variation ];
graphicsContext.setGlobalAlpha(pressure);
graphicsContext.drawImage( brushVariation, x0 - brushWidthHalf, y0 - brushHeightHalf);
if (x0==x1 && y0==y1) break;
e2 = 2.*err;
if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
}
private void addListeners() {
scene.addEventFilter(MouseEvent.ANY, e -> {
mouseLocation = new Point2D(e.getX(), e.getY());
mousePressed = e.isPrimaryButtonDown();
});
}
public static Image createImage(Node node) {
WritableImage wi;
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
int imageWidth = (int) node.getBoundsInLocal().getWidth();
int imageHeight = (int) node.getBoundsInLocal().getHeight();
wi = new WritableImage(imageWidth, imageHeight);
node.snapshot(parameters, wi);
return wi;
}
public static Image createBrush( double radius, Color color) {
// create gradient image with given color
Circle brush = new Circle(radius);
RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
brush.setFill(gradient1);
// create image
return createImage(brush);
}
public static void main(String[] args) {
launch(args);
}
}
Example with variation for limiting the brush length
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ColorPicker;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
private static double SCENE_WIDTH = 1280;
private static double SCENE_HEIGHT = 720;
Canvas canvas;
GraphicsContext graphicsContext;
AnimationTimer loop;
Point2D mouseLocation = new Point2D(0, 0);
boolean mousePressed = false;
Point2D prevMouseLocation = new Point2D(0, 0);
Scene scene;
double brushMaxSize = 30;
double pressure = 0;
double pressureDelay = 0.04;
double pressureDirection = 1;
double strokeTimeMax = 1;
double strokeTime = 0;
double strokeTimeDelay = 0.07;
private Image[] brushVariations = new Image[256];
ColorPicker colorPicker = new ColorPicker();
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
canvas = new Canvas(SCENE_WIDTH, SCENE_HEIGHT);
graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.setFill(Color.WHITE);
graphicsContext.fillRect(0, 0, SCENE_WIDTH, SCENE_HEIGHT);
Pane layerPane = new Pane();
layerPane.getChildren().addAll(canvas);
colorPicker.setValue(Color.CHOCOLATE);
colorPicker.setOnAction(e -> {
createBrushVariations();
});
root.setCenter(layerPane);
root.setTop(colorPicker);
scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT, Color.WHITE);
primaryStage.setScene(scene);
primaryStage.show();
createBrushVariations();
addListeners();
startAnimation();
}
private void createBrushVariations() {
for (int i = 0; i < brushVariations.length; i++) {
double size = (brushMaxSize - 1) / (double) brushVariations.length * (double) i + 1;
brushVariations[i] = createBrush(size, colorPicker.getValue());
}
}
private void startAnimation() {
loop = new AnimationTimer() {
#Override
public void handle(long now) {
if (mousePressed) {
// try this
// graphicsContext.drawImage( brush, mouseLocation.getX() -
// brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
// then this
bresenhamLine(prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
// increasing or decreasing
strokeTime += strokeTimeDelay * pressureDirection;
// invert direction
if (strokeTime > strokeTimeMax) {
pressureDirection = -1;
}
// while still
if (strokeTime > 0) {
pressure += pressureDelay * pressureDirection;
// clamp value of pressure to be [0,1]
if (pressure > 1) {
pressure = 1;
} else if (pressure < 0) {
pressure = 0;
}
} else {
pressure = 0;
}
} else {
pressure = 0;
pressureDirection = 1;
strokeTime = 0;
}
prevMouseLocation = new Point2D(mouseLocation.getX(), mouseLocation.getY());
}
};
loop.start();
}
// https://de.wikipedia.org/wiki/Bresenham-Algorithmus
private void bresenhamLine(double x0, double y0, double x1, double y1) {
double dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1. : -1.;
double dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1. : -1.;
double err = dx + dy, e2; /* error value e_xy */
while (true) {
int variation = (int) (pressure * (brushVariations.length - 1));
Image brushVariation = brushVariations[variation];
graphicsContext.setGlobalAlpha(pressure);
graphicsContext.drawImage(brushVariation, x0 - brushVariation.getWidth() / 2.0, y0 - brushVariation.getHeight() / 2.0);
if (x0 == x1 && y0 == y1)
break;
e2 = 2. * err;
if (e2 > dy) {
err += dy;
x0 += sx;
} /* e_xy+e_x > 0 */
if (e2 < dx) {
err += dx;
y0 += sy;
} /* e_xy+e_y < 0 */
}
}
private void addListeners() {
canvas.addEventFilter(MouseEvent.ANY, e -> {
mouseLocation = new Point2D(e.getX(), e.getY());
mousePressed = e.isPrimaryButtonDown();
});
}
public static Image createImage(Node node) {
WritableImage wi;
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
int imageWidth = (int) node.getBoundsInLocal().getWidth();
int imageHeight = (int) node.getBoundsInLocal().getHeight();
wi = new WritableImage(imageWidth, imageHeight);
node.snapshot(parameters, wi);
return wi;
}
public static Image createBrush(double radius, Color color) {
// create gradient image with given color
Circle brush = new Circle(radius);
RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
brush.setFill(gradient1);
// create image
return createImage(brush);
}
public static void main(String[] args) {
launch(args);
}
}
This is how it looks like:
or using different colors, I added a color picker in the last example: