How can I create a feathered brush with JavaFX? - java

So I'm trying to create a very basic photo editor program in Java, using JavaFX. I got a brush and eraser working pretty well so far the following way:
package application;
import java.io.File;
import javax.imageio.ImageIO;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
public class EditorController {
private boolean eraser = false;
#FXML
private Canvas canvas;
#FXML
private ColorPicker colorPicker;
#FXML
private TextField brushSize;
#FXML
private TextField selectedTool;
private Point2D last = null;
public void initialize() {
GraphicsContext gc = canvas.getGraphicsContext2D();
canvas.setOnMouseReleased(e -> {last = null;});
canvas.setOnMouseClicked(e -> {
if (!eraser) {
double size = Double.parseDouble(brushSize.getText());
float mouseX = (float) e.getX();
float mouseY = (float) e.getY();
gc.fillOval(mouseX-(size/2), mouseY-(size/2), size, size);
}
});
canvas.setOnMouseDragged(e -> {
System.out.println(eraser);
double size = Double.parseDouble(brushSize.getText());
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineWidth(size);
float mouseX = (float) e.getX();
float mouseY = (float) e.getY();
if (last != null && !eraser) {
gc.strokeLine(last.getX(), last.getY(), mouseX, mouseY);
} else if (eraser) {
gc.clearRect(mouseX, mouseY, size, size);
}
last = new Point2D(mouseX, mouseY);
});
}
public void onSave() {
try {
Image snapshot = canvas.snapshot(null, null);
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File("paint.png"));
} catch (Exception e) {
System.out.println("Failed to save image: " + e);
}
}
public void onLoad() {
// not implemented yet
}
// not implemented yet
public void onUndo() { }
public void onRedo() { }
public void onSmaller() { }
public void onBigger() { }
public void onResetView() { }
public void onFitView() { }
public void onFillView() { }
public void onNewLayer() { }
public void onDeleteLayer() { }
public void onDuplicateLayer() { }
public void onGroupLayers() { }
public void onMergeLayers() { }
public void onAddMask() { }
public void onBrush() { eraser = false; selectedTool.setText("Brush"); }
public void onEraser() { eraser = true; selectedTool.setText("Eraser"); }
public void onExit() {
Platform.exit();
}
}
Now I want to have a feather/hardness value for the brush (like in photoshop) where I can draw a softer-looking line, but I'm not sure how to achieve it with JavaFX? Are there any tools within it for things like this?
So with a visual example: the brush on the left would be a feathered brush, the one on the right isn't (and that's what I have currently)

Simple drawing app.
It uses a radial gradient rendered to an image that is drawn on the canvas, but you could just draw the gradient directly onto the canvas. A gradient is a paint so you can set it directly as an argument to setFill on a graphics context.
The solution in my example probably won't exactly give you the solution you are looking for, but perhaps you could tweak it for what you need.
It was a quick app I put together for demo purposes, it could be structured better if a more functional drawing app was required.
The code which creates the "feathered brush" is based on a gradient:
private RadialGradient createSoftBrushGradient(Color primaryColor) {
return new RadialGradient(
0, 0,
.5, .5,
.5,
true,
CycleMethod.NO_CYCLE,
new Stop(0, primaryColor),
new Stop(1, Color.TRANSPARENT)
);
}
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class SprayPaint extends Application {
private final IntegerProperty brushDiameter = new SimpleIntegerProperty();
private final ObjectProperty<Image> brushImage = new SimpleObjectProperty<>();
private final ToggleGroup brushHardnessSelection = new ToggleGroup();
private final RadioButton hardBrushSelection = new RadioButton();
private final RadioButton softBrushSelection = new RadioButton();
private final Circle hardBrush = new Circle();
private final Circle softBrush = new Circle();
private final SnapshotParameters snapshotParams = new SnapshotParameters();
private final Canvas canvas = new Canvas(600, 450);
#Override
public void start(Stage stage) {
snapshotParams.setFill(Color.TRANSPARENT);
Pane controls = createControls();
StackPane canvasHolder = new StackPane(canvas);
canvasHolder.setStyle("-fx-border-color: gray;");
canvasHolder.setMaxSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
VBox layout = new VBox(
10,
controls,
canvasHolder
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
enableDrawing(canvas);
}
private void enableDrawing(Canvas canvas) {
EventHandler<MouseEvent> drawHandler = event -> {
Image brush = snapshotBrushImage();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(
brush,
event.getX() - brushDiameter.doubleValue() / 2,
event.getY() - brushDiameter.doubleValue() / 2
);
};
canvas.setOnMousePressed(drawHandler);
canvas.setOnMouseDragged(drawHandler);
}
private Image snapshotBrushImage() {
if (brushImage.get() == null) {
if (hardBrushSelection == brushHardnessSelection.getSelectedToggle()) {
brushImage.set(snapshot(hardBrush));
} else { // soft brush selected
brushImage.set(snapshot(softBrush));
}
}
return brushImage.get();
}
private Image snapshot(Circle brushNode) {
return brushNode.snapshot(snapshotParams, null);
}
private Pane createControls() {
hardBrush.radiusProperty().bind(
brushDiameter.divide(2.0)
);
softBrush.radiusProperty().bind(
brushDiameter.divide(2.0)
);
hardBrushSelection.getStyleClass().addAll("toggle-button", "left-pill");
hardBrushSelection.getStyleClass().remove( "radio-button");
StackPane hardBrushGraphic = new StackPane(hardBrush);
hardBrushGraphic.setMinSize(40, 40);
hardBrushSelection.setGraphic(hardBrushGraphic);
hardBrushSelection.setToggleGroup(brushHardnessSelection);
softBrushSelection.getStyleClass().addAll( "toggle-button", "right-pill");
softBrushSelection.getStyleClass().remove( "radio-button");
StackPane softBrushGraphic = new StackPane(softBrush);
softBrushGraphic.setMinSize(40, 40);
softBrushSelection.setGraphic(softBrushGraphic);
softBrushSelection.setToggleGroup(brushHardnessSelection);
hardBrushSelection.setSelected(true);
HBox brushSelectionPanel = new HBox(hardBrushSelection, softBrushSelection);
Slider brushDiameterSlider = new Slider(8, 40, 20);
brushDiameterSlider.setMajorTickUnit(4);
brushDiameterSlider.setMinorTickCount(0);
brushDiameterSlider.setShowTickMarks(true);
brushDiameter.setValue((int) Math.round(brushDiameterSlider.getValue()));
brushDiameterSlider.valueProperty().addListener((observable, oldValue, newValue) ->
brushDiameter.setValue((int) Math.round(newValue.doubleValue()))
);
Label diameterLabel = new Label();
diameterLabel.textProperty().bind(
brushDiameter.asString()
);
ColorPicker colorPicker = new ColorPicker();
hardBrush.fillProperty().bind(
colorPicker.valueProperty()
);
colorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
softBrush.setFill(
createSoftBrushGradient(newColor)
)
);
colorPicker.setValue(Color.NAVY);
brushDiameter.addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
colorPicker.valueProperty().addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
brushHardnessSelection.selectedToggleProperty().addListener((observable, oldValue, newValue) ->
brushImage.set(null)
);
Button clear = new Button("Clear");
clear.setOnAction(e ->
canvas.getGraphicsContext2D().clearRect(
0, 0, canvas.getWidth(), canvas.getHeight()
)
);
HBox controlPanel = new HBox(
10,
colorPicker,
brushSelectionPanel,
new Label("Diameter: "),
brushDiameterSlider,
diameterLabel,
clear
);
controlPanel.setMinWidth(450);
controlPanel.setMinHeight(Pane.USE_PREF_SIZE);
return controlPanel;
}
private RadialGradient createSoftBrushGradient(Color primaryColor) {
return new RadialGradient(
0, 0,
.5, .5,
.5,
true,
CycleMethod.NO_CYCLE,
new Stop(0, primaryColor),
new Stop(1, Color.TRANSPARENT)
);
}
}

Related

Why wont my ball object continously move up when i press space?

I am trying to recreate the mini game shooting gallery from Zelda: A link to the past with a triangle polygon that acts as the player and a circle that acts as the projectile and when the player presses space the projectile moves up at a quick speed, but my setOnKeyPressed method does not work, what am i missing?
Method spoken about
scene.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands.get(event.getCode());
if (movement != null){
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null){
shoot.execute(ball);
}
}
});
This is my main method
package videogameclass;
import java.util.HashMap;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import static javafx.scene.paint.Color.color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author
*/
public class VideoGameClass extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
launch(args);
}
#Override
public void start(Stage stage) throws Exception
{
stage.setTitle("Target practice");
Group root = new Group();
Scene scene = new Scene(root, 320, 240);
stage.setScene(scene);
Circle circle1 = new Circle(20,20,10);
Sprite c = new Sprite(circle1);
circle1.setFill(Color.RED);
root.getChildren().add(c.getNode());
Circle circle2 = new Circle(80, 20, 10);
Sprite c2 = new Sprite(circle2);
circle2.setFill(Color.RED);
root.getChildren().add(c2.getNode());
Circle circle3 = new Circle(140, 20, 10);
Sprite c3 = new Sprite(circle3);
circle3.setFill(Color.RED);
root.getChildren().add(c3.getNode());
Rectangle rectangle = new Rectangle(15,40,25,10);
Sprite rec = new Sprite(rectangle);
rectangle.setFill(Color.BLACK);
root.getChildren().add(rec.getNode());
Rectangle rectangle2 = new Rectangle(75,40,25,10);
Sprite rec2 = new Sprite(rectangle2);
rectangle2.setFill(Color.BLACK);
root.getChildren().add(rec2.getNode());
Rectangle rectangle3 = new Rectangle(135,40,25,10);
Sprite rec3 = new Sprite(rectangle3);
rectangle3.setFill(Color.BLACK);
root.getChildren().add(rec3.getNode());
Rectangle rectangle4 = new Rectangle(195,40,25,10);
Sprite rec4 = new Sprite(rectangle4);
rectangle4.setFill(Color.BLACK);
root.getChildren().add(rec4.getNode());
Polygon t = new Polygon();
Sprite player = new Sprite(t);
root.getChildren().add(player.getNode());
t.getPoints().addAll(new Double[]{
25.0, 20.0,
45.0, 20.0,
35.0, 5.0,
25.0,20.0,
});
t.setTranslateX(125);
t.setTranslateY(200);
Circle circle4 = new Circle(t.getTranslateX()+35,t.getTranslateY(),5);
Sprite ball = new Sprite(circle4);
root.getChildren().add(ball.getNode());
Command moveLeft = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(-2, 0);
//sprite.update();
}
};
Command moveRight = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(2, 0);
//sprite.update();
}
};
Command moveUp = new Command(){
#Override
public void execute(Sprite sprite){
sprite.setVelocity(0,-2);
}
};
Command stopMoving = new Command()
{
#Override
public void execute(Sprite sprite)
{
sprite.setVelocity(0,0);
}
};
Map<KeyCode,Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode,Command> keyCommandsBall = new HashMap<>();
keyCommands.put(KeyCode.SPACE, moveUp);
Map<KeyCode,Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F19, moveLeft);
keyCommands.put(KeyCode.F18, moveRight);
AnimationTimer animation = new AnimationTimer()
{
#Override
public void handle(long now){
c.setVelocity(1.0, 0);
c2.setVelocity(1.0,0);
c3.setVelocity(1.0,0);
ball.setVelocity(0, 0);
rec.setVelocity(-2.0,0);
rec2.setVelocity(-2.0,0);
rec3.setVelocity(-2.0,0);
rec4.setVelocity(-2.0,0);
c.update();
c2.update();
c3.update();
ball.update();//projectile
rec.update();
rec2.update();
rec3.update();
rec4.update();
player.update();
//move sprites
//check and handle collisions
if(c3.getNode().getTranslateX() > scene.getWidth() - 120){
c3.getNode().setTranslateX(-140);
}
if(c2.getNode().getTranslateX() > scene.getWidth() - 75){
c2.getNode().setTranslateX(c3.getNode().getTranslateX()-20);
}
if(c.getNode().getTranslateX() > scene.getWidth() - 10){
c.getNode().setTranslateX(c2.getNode().getTranslateX()-20);
}
if (rec.getNode().getTranslateX() < -40){
rec.getNode().setTranslateX(300);
}
if(rec2.getNode().getTranslateX() < -100){
rec2.getNode().setTranslateX(rec.getNode().getTranslateX()+30);
}
if(rec3.getNode().getTranslateX() < -135){
rec3.getNode().setTranslateX(rec2.getNode().getTranslateX()+30);
}
if(rec4.getNode().getTranslateX() < -225){
rec4.getNode().setTranslateX(rec3.getNode().getTranslateX()+30);
}
if(ball.getNode().getBoundsInParent().intersects(rec.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec2.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec3.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(rec4.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c2.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
if(ball.getNode().getBoundsInParent().intersects(c3.getNode().getBoundsInParent())){
ball.setVelocity(0, 0);
}
}//end of handle
};
scene.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands.get(event.getCode());
if (movement != null){
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null){
shoot.execute(ball);
}
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent event)
{
Command movement = keyCommands2.get(event.getCode());
Command stop = stopMoving;
if (movement == null){
stop.execute(player);
}
}
});
animation.start();
stage.show();
}
}
This is my Sprite class
package videogameclass;
import javafx.scene.Node;
/**
*
* #author
*/
public class Sprite {
private final Node node;
private double dx, dy;
public Sprite(Node node){
this.node = node;
}
public Node getNode(){
return node;
}
public void setVelocity(double dx, double dy){
this.dx = dx;
this.dy = dy;
}
public double getVelocityX(){
return dx;
}
public double getVelocityY(){
return dy;
}
public void update(){
this.node.setTranslateX(this.node.getTranslateX()+dx);
this.node.setTranslateY(this.node.getTranslateY()+dy);
}
}
This is my Command method
package videogameclass;
public interface Command
{
public void execute(Sprite sprite);
}
Because there is nothing added to keyCommandsBall map.
I am not sure if this is a typo mistake, but I can sense this part of the code is wrong. All the commands added to keyCommands map only.
Map<KeyCode,Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode,Command> keyCommandsBall = new HashMap<>();
keyCommands.put(KeyCode.SPACE, moveUp);
Map<KeyCode,Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F19, moveLeft);
keyCommands.put(KeyCode.F18, moveRight);
Update:
I felt a bit interesting in this game so I tried to put my hands in this. Indeed this was quite fun when playing ;)
But few suggestions for you from your code:
Try to reuse to code by either keeping things in loop or by calling a common method. That way you can reduce the code drastically.
Try to create constants for the fixed sizes and positions. That way you accidentally don't get messed up with some wrong values. (Though I have not included that in my example)
When providing a minimal example, try to keep all the code in a single class with all imports (even if it has multiple classes or interfaces). The main aim should be that others should directly copy the class and should be able to run the program.
Below is the output of the code that I worked on. (There is still so much scope to optimize this)
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class VideoGameClass extends Application {
private List<Sprite> circles = new ArrayList<>();
private List<Sprite> rectangles = new ArrayList<>();
private List<Sprite> all = new ArrayList<>();
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, 320, 240, Color.BLACK);
stage.setTitle("Target practice");
stage.setScene(scene);
Sprite c1 = createCircle(20, 20);
Sprite c2 = createCircle(80, 20);
Sprite c3 = createCircle(140, 20);
Sprite rec1 = createRectangle(15);
Sprite rec2 = createRectangle(75);
Sprite rec3 = createRectangle(135);
Sprite rec4 = createRectangle(195);
Polygon t = new Polygon();
t.getPoints().addAll(new Double[]{
25.0, 20.0,
45.0, 20.0,
35.0, 5.0,
25.0, 20.0,
});
t.setFill(Color.WHITE);
t.setTranslateX(125);
t.setTranslateY(200);
Sprite player = new Sprite(t);
all.add(player);
Sprite ball = new Sprite(new Circle(t.getTranslateX() + 35, t.getTranslateY(), 5, Color.WHITE));
all.add(ball);
all.stream().map(Sprite::getNode).forEach(n -> root.getChildren().add(n));
Command moveLeft = sprite -> sprite.setVelocity(-2, 0);
Command moveRight = sprite -> sprite.setVelocity(2, 0);
Command moveUp = sprite -> sprite.setVelocity(0, -2);
Command stopMoving = sprite -> sprite.setVelocity(0, 0);
Map<KeyCode, Command> keyCommands = new HashMap<>();
keyCommands.put(KeyCode.LEFT, moveLeft);
keyCommands.put(KeyCode.RIGHT, moveRight);
Map<KeyCode, Command> keyCommandsBall = new HashMap<>();
keyCommandsBall.put(KeyCode.SPACE, moveUp);
Map<KeyCode, Command> keyCommands2 = new HashMap<>();
keyCommands.put(KeyCode.F11, moveLeft);
keyCommands.put(KeyCode.F12, moveRight);
AnimationTimer animation = new AnimationTimer() {
#Override
public void handle(long now) {
circles.forEach(c -> c.setVelocity(1.0, 0));
rectangles.forEach(r -> r.setVelocity(-2.0, 0));
all.forEach(Sprite::update);
//check and handle collisions
if (c3.getNode().getTranslateX() > scene.getWidth() - 120) {
c3.getNode().setTranslateX(-140);
}
if (c2.getNode().getTranslateX() > scene.getWidth() - 75) {
c2.getNode().setTranslateX(c3.getNode().getTranslateX() - 20);
}
if (c1.getNode().getTranslateX() > scene.getWidth() - 10) {
c1.getNode().setTranslateX(c2.getNode().getTranslateX() - 20);
}
if (rec1.getNode().getTranslateX() < -40) {
rec1.getNode().setTranslateX(300);
}
if (rec2.getNode().getTranslateX() < -100) {
rec2.getNode().setTranslateX(rec1.getNode().getTranslateX() + 30);
}
if (rec3.getNode().getTranslateX() < -135) {
rec3.getNode().setTranslateX(rec2.getNode().getTranslateX() + 30);
}
if (rec4.getNode().getTranslateX() < -225) {
rec4.getNode().setTranslateX(rec3.getNode().getTranslateX() + 30);
}
// Check for collision and stop the ball
Stream.of(circles, rectangles).flatMap(List::stream).forEach(sprite -> {
if (ball.getNode().getBoundsInParent().intersects(sprite.getNode().getBoundsInParent())) {
ball.setVelocity(0, 0);
}
});
}//end of handle
};
scene.setOnKeyPressed(event -> {
Command movement = keyCommands.get(event.getCode());
if (movement != null) {
movement.execute(player);
}
Command shoot = keyCommandsBall.get(event.getCode());
if (shoot != null) {
shoot.execute(ball);
}
});
scene.setOnKeyReleased(event -> {
Command movement = keyCommands2.get(event.getCode());
Command stop = stopMoving;
if (movement == null) {
stop.execute(player);
}
// Bring back the ball to the player
if(event.getCode() == KeyCode.Z){
ball.setVelocity(0, 0);
Circle ballNode = (Circle) ball.getNode();
ballNode.setTranslateX(0);
ballNode.setTranslateY(0);
ballNode.setCenterX(player.getNode().getTranslateX() + 35);
ballNode.setCenterY(player.getNode().getTranslateY());
}
});
animation.start();
stage.show();
}
private Sprite createCircle(double centerX, double centerY) {
Sprite circle = new Sprite(new Circle(centerX, centerY, 10, Color.RED));
circles.add(circle);
all.add(circle);
return circle;
}
private Sprite createRectangle(double x) {
Rectangle r = new Rectangle(x, 40, 25, 10);
r.setFill(Color.YELLOW);
Sprite rectangle = new Sprite(r);
rectangles.add(rectangle);
all.add(rectangle);
return rectangle;
}
class Sprite {
private final Node node;
private double dx, dy;
public Sprite(Node node) {
this.node = node;
}
public Node getNode() {
return node;
}
public void setVelocity(double dx, double dy) {
this.dx = dx;
this.dy = dy;
}
public void update() {
this.node.setTranslateX(this.node.getTranslateX() + dx);
this.node.setTranslateY(this.node.getTranslateY() + dy);
}
}
interface Command {
void execute(Sprite sprite);
}
}

How to update part of BufferedImage and display it with javafx

I have an image. At the beginning I want the image to be displayed with only the first frame. On the keyboard with 'u' key pressed, drink water and load the second frame. If I understand correctly the whole image should be loaded at the beginning. And then I need to set new y.
How to complete this task? What is the right way to display this image?
My code in:
package testDesktopUi;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class DesktopUi extends Application {
private static int y = 0;
private static BufferedImage bufferedImage;
private static final ImageView imageView = new ImageView();
private static final HBox root = new HBox();
#Override
public void start(Stage primaryStage) throws IOException {
String path = "Bottle.png";
bufferedImage = ImageIO.read(new File(path)).getSubimage(0, 0, 32, 32);
Image image = SwingFXUtils.toFXImage(bufferedImage, null);
imageView.setImage(image);
root.getChildren().add(imageView);
Scene scene = new Scene(root);
scene.setOnKeyPressed(key -> {
if (key.getCode() == KeyCode.U) {
System.out.println("u pressed");
updateImage(bufferedImage);
System.out.println(y);
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
private static void updateImage(BufferedImage bufferedImage) {
int maxHeight = 352;
if (y + 32 >= maxHeight) {
y = 0;
} else {
y += 32;
}
//?? bufferedImage.getSubimage(0, y, 32, 32);
}
}
If you're using JavaFX, stick just to the JavaFX image API: there is no need to first load an AWT BufferedImage and convert it to a JavaFX image.
To display portions of an image, you can create an ImageView from the image and set the ImageView's viewport.
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class DesktopUi extends Application {
private int y = 0;
private final ImageView imageView = new ImageView();
private final HBox root = new HBox();
#Override
public void start(Stage primaryStage) {
String path = "Bottle.png";
// Assumes Bottle.png is in the same package as the current class:
Image image = new Image(getClass().getResource("Bottle.png").toExternalForm());
imageView.setImage(image);
// display only a portion of the image:
imageView.setViewport(new Rectangle2D(0, y, 32, 32));
root.getChildren().add(imageView);
Scene scene = new Scene(root);
scene.setOnKeyPressed(key -> {
if (key.getCode() == KeyCode.U) {
System.out.println("u pressed");
System.out.println(y);
updateImage();
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateImage() {
// update y
int maxHeight = 352;
if (y + 32 >= maxHeight) {
y = 0;
} else {
y += 32;
}
// update portion of image displayed
imageView.setViewport(new Rectangle2D(0, y, 32, 32));
}
public static void main(String[] args) {
Application.launch(args);
}
}
Note this also lends itself nicely to animations:
#Override
public void start(Stage primaryStage) {
int numSprites = 11 ;
Image image = new Image(getClass().getResource("Bottle.png").toExternalForm());
imageView.setImage(image);
IntegerProperty spriteIndex = new SimpleIntegerProperty();
spriteIndex.addListener((obs, oldIndex, newIndex) -> System.out.println(newIndex));
imageView.viewportProperty().bind(Bindings.createObjectBinding(
() -> new Rectangle2D(0, spriteIndex.get() * 32 , 32, 32),
spriteIndex
));
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(5),
new KeyValue(spriteIndex, numSprites - 1)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
root.getChildren().add(imageView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Javafx Timeline stops playing upon passing through multiple listeners

I'm a beginner who recently picked up JavaFX, I've been trying to make a program that executes an animation on a node upon clicking on it. I've tried to see if anyone else has tried something similar but I have not been able to find a similar case to what I've tried to do.
The Timeline animates Panes, which contain a Rectangle and Text, horizontally across the screen, upon finishing the execution the stage will change scenes. When the user clicks a button to go back to the original scene the buttons should perform another animation.
I've verified that it isn't the scene transition that is causing it to stop and I have also verified that the program is indeed executing .play() on the timeline. The timelines are executed within the custom listeners so the listeners are not an issue as far as I know.
Here is the code sample to recreate the issue I have, run the script and just press on. Here you can see that done is being printed to the console which is right underneath Main_Button_Animation.play(); however, nothing is being animated.
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class main extends Application{
Scene scene1;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("test");
Main_Screen MainScreen = new Main_Screen(800, 480);
scene1 = MainScreen.MainScreen(primaryStage);
MainScreen.getInitiater().Calibration_Listener(() -> {
MainScreen.getInitiater().Back_Button_Pressed();
});
primaryStage.setScene(scene1);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
//custom listeners
interface Calibration {
void menu();
}
interface Back {
void back();
}
class Initiater {
private List<Calibration> calilist = new ArrayList<Calibration>();;
private List<Back> Go_Back = new ArrayList<Back>();
public void Calibration_Listener(Calibration toAdd) {
calilist.add(toAdd);
}
public void Back_Listener(Back toAdd) {
Go_Back.add(toAdd);
}
public void Calibration_Pressed() {
System.out.println("Calibration Pressed");
for (Calibration hl : calilist)
hl.menu();
}
public void Back_Button_Pressed() {
System.out.println("Back Pressed");
for (Back hl : Go_Back)
hl.back();
}
}
//animations and setup
class Main_Screen {
Initiater initiater;
private int X;
private int Y;
public Main_Screen(int X, int Y) {
this.initiater = new Initiater();
this.X = X;
this.Y = Y;
}
private Pane UI_Button(String name, int[] pos, int[] size, Color color) {
final Text text = new Text(0, 0, name);
text.setFont(new Font(20));
final Rectangle outline = new Rectangle(0, 0, size[0], size[1]);
outline.setFill(color);
final StackPane stack = new StackPane();
stack.getChildren().addAll(outline, text);
stack.setLayoutX(pos[0]);
stack.setLayoutY(pos[1]);
return stack;
}
public int getX() {
return this.X;
}
public int getY() {
return this.Y;
}
public Initiater getInitiater() {
return this.initiater;
}
public Scene MainScreen(Stage stage) {
Scene scene = new Scene(new Group(), getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() - getX() / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.rgb(255, 0, 0));
Pane Calibration = UI_Button("Callibration", new int[] { 0, ((getY() + (getY() / 8)) / 4) * 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
((Group) scene.getRoot()).getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline();
final KeyFrame Cali_kf = new KeyFrame(Duration.millis(500), new KeyValue(Calibration.translateXProperty(), getX() / 2));
final KeyFrame Start_kf = new KeyFrame(Duration.millis(500), new KeyValue(Start.translateXProperty(), -getX() / 4));
Main_Button_Animation.getKeyFrames().addAll(Cali_kf, Start_kf);
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.setRate(1);
Main_Button_Animation.jumpTo(Duration.millis(0));
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(event -> {
Main_Button_Animation.play();
System.out.println("done");
});
});
return scene;
}
}
Edit1 : For clarity, The example above doesn't use any scene transition. Here is a example with a scene transition.
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class main extends Application{
Scene scene1, scene2;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("test");
Main_Screen MainScreen = new Main_Screen(800, 480);
scene1 = MainScreen.MainScreen(primaryStage);
MainScreen.getInitiater().Calibration_Listener(() -> {
primaryStage.setScene(scene2);
primaryStage.show();
});
Label label2 = new Label("This is the second scene");
Button button2 = new Button("Go to scene 1");
button2.setOnAction(e -> {
MainScreen.getInitiater().Back_Button_Pressed();
primaryStage.setScene(scene1);
primaryStage.show();
});
VBox layout2 = new VBox(20);
layout2.getChildren().addAll(label2, button2);
scene2 = new Scene(layout2, 800, 480);
primaryStage.setScene(scene1);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
//custom listeners
interface Calibration {
void menu();
}
interface Back {
void back();
}
class Initiater {
private List<Calibration> calilist = new ArrayList<Calibration>();;
private List<Back> Go_Back = new ArrayList<Back>();
public void Calibration_Listener(Calibration toAdd) {
calilist.add(toAdd);
}
public void Back_Listener(Back toAdd) {
Go_Back.add(toAdd);
}
public void Calibration_Pressed() {
System.out.println("Calibration Pressed");
for (Calibration hl : calilist)
hl.menu();
}
public void Back_Button_Pressed() {
System.out.println("Back Pressed");
for (Back hl : Go_Back)
hl.back();
}
}
//animations and setup
class Main_Screen {
Initiater initiater;
private int X;
private int Y;
public Main_Screen(int X, int Y) {
this.initiater = new Initiater();
this.X = X;
this.Y = Y;
}
private Pane UI_Button(String name, int[] pos, int[] size, Color color) {
final Text text = new Text(0, 0, name);
text.setFont(new Font(20));
final Rectangle outline = new Rectangle(0, 0, size[0], size[1]);
outline.setFill(color);
final StackPane stack = new StackPane();
stack.getChildren().addAll(outline, text);
stack.setLayoutX(pos[0]);
stack.setLayoutY(pos[1]);
return stack;
}
public int getX() {
return this.X;
}
public int getY() {
return this.Y;
}
public Initiater getInitiater() {
return this.initiater;
}
public Scene MainScreen(Stage stage) {
Scene scene = new Scene(new Group(), getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() - getX() / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.rgb(255, 0, 0));
Pane Calibration = UI_Button("Callibration", new int[] { 0, ((getY() + (getY() / 8)) / 4) * 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
((Group) scene.getRoot()).getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline();
final KeyFrame Cali_kf = new KeyFrame(Duration.millis(500), new KeyValue(Calibration.translateXProperty(), getX() / 2));
final KeyFrame Start_kf = new KeyFrame(Duration.millis(500), new KeyValue(Start.translateXProperty(), -getX() / 4));
Main_Button_Animation.getKeyFrames().addAll(Cali_kf, Start_kf);
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.setRate(1);
Main_Button_Animation.jumpTo(Duration.millis(0));
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(event -> {
this.initiater.Calibration_Pressed();
System.out.println("done");
});
});
this.initiater.Back_Listener(() -> {
Main_Button_Animation.setRate(-1);
Main_Button_Animation.jumpTo(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.play();
Main_Button_Animation.setOnFinished(e -> {
Calibration.setDisable(false);
Start.setDisable(false);
});
});
return scene;
}
}
You only define KeyValues for the frame at the end of the Timeline animation. This only allows the Timeline to interpolate between the value at the start of the animation and the target value. Inserting another KeyFrame at the beginning of the animation should fix this issue:
public Scene MainScreen(Stage stage) {
final Group root = new Group();
Scene scene = new Scene(root, getX(), getY());
scene.setFill(Color.WHITE);
Pane Start = UI_Button("Start", new int[] { getX() * 3 / 4, 0 }, new int[] { getX() / 4, getY() / 4 }, Color.RED);
Pane Calibration = UI_Button("Callibration", new int[] { 0, 0 }, new int[] { getX() / 2, getY() / 4 }, Color.rgb(60, 208, 230));
System.out.println(Calibration.boundsInLocalProperty());
root.getChildren().addAll(Calibration, Start);
final Timeline Main_Button_Animation = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(Calibration.translateXProperty(), 0),
new KeyValue(Start.translateXProperty(), 0)),
new KeyFrame(Duration.millis(500),
new KeyValue(Calibration.translateXProperty(), getX() / 2),
new KeyValue(Start.translateXProperty(), -getX() / 4)));
Main_Button_Animation.setRate(-1);
Main_Button_Animation.playFrom(Main_Button_Animation.getTotalDuration());
Calibration.setOnMouseClicked(MouseEvent -> {
Calibration.setDisable(true);
Start.setDisable(true);
System.out.println(Calibration.boundsInLocalProperty());
Main_Button_Animation.playFromStart();
Main_Button_Animation.setOnFinished(event -> {
this.initiater.Calibration_Pressed();
System.out.println("done");
});
});
this.initiater.Back_Listener(() -> {
Main_Button_Animation.setRate(-1);
Main_Button_Animation.playFrom(Main_Button_Animation.getTotalDuration());
Main_Button_Animation.setOnFinished(e -> {
Calibration.setDisable(false);
Start.setDisable(false);
});
});
return scene;
}
Note that there have been a few additional changes here:
Combining KeyFrames at the same time to a single one with multiple KeyValues
Keep a reference to the scene root to avoid the use of the getter/cast
Using playFrom and playFromStart instead of jumpTo/play
Simplifications of some of the parameters passed to the UI_Button method (rounding could make the result differ by 1 though)
...

Implementing drawing areas in tabs in JavaFX

I would want to add a drawing area to my tabs in JavaFX. I am new to the field, and tried adding a Canvas to each tab I create. However, this does not seem to be working. Relevant code included.
#FXML
void fileNewTabHandler(ActionEvent event) {
++indexTab;
Tab tab = new Tab("Untitled " + indexTab);
Canvas canvas = new Canvas(500, 285);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.BLUE);
gc.fillRect(250, 856, 50, 60);
tab.setContent(canvas);
tabPane.getTabs().add(tab);
}
Here tabPane and indexTab are defined and work as long as I do not use the canvas. Where am I going wrong. Should I use a different method to implement what I want to?
Quick Sample
There is no real trick to this, you just set the canvas as the content of the tab.
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class TabbedCanvas extends Application {
private int tabId = 0;
private double W = 200, H = 150;
private Random random = new Random(42);
#Override
public void start(Stage stage) {
TabPane tabPane = new TabPane();
Button newTabButton = new Button("New Tab");
newTabButton.setOnAction(
event -> addTab(tabPane)
);
newTabButton.fire();
ToolBar toolBar = new ToolBar(newTabButton);
toolBar.setMinHeight(ToolBar.USE_PREF_SIZE);
VBox layout = new VBox(toolBar, tabPane);
VBox.setVgrow(tabPane, Priority.ALWAYS);
stage.setScene(new Scene(layout));
stage.show();
}
private void addTab(TabPane tabPane) {
Tab tab = new Tab("Tab: " + tabId++);
tab.setContent(createTabContent());
tabPane.getTabs().add(tab);
tabPane.getSelectionModel().select(tab);
}
private Node createTabContent() {
Canvas canvas = new Canvas(W, H);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(randomColor());
gc.fillRect(0, 0, W, H);
return canvas;
}
private Color randomColor() {
return Color.rgb(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256)
);
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX - Clickable line on canvas

I have this JavaFX application that lets you plot locations on a map and connect them.
I do this by drawing a map as a background image on a canvas and then drawing circles and lines on it. I have made the circles clickable by using the contains() method in the Circle class, but how can I make the lines clickable?
edit: Look at this example where I just draw a line and set an event handler:
Canvas canvas = new Canvas();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(5);
gc.strokeLine(100, 100, 200, 200);
canvas.setOnMouseClicked(event -> {
double x = event.getX(), y = event.getY();
});
My question is simply this: how do I finish the event handler so that it detects if the click is inside the line I just drew?
You should create a canvas and add the nodes (Circle, Line, etc) to it. Then you add mouse listeners to the nodes.
Example:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class DragNodes extends Application {
public static List<Circle> circles = new ArrayList<Circle>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Canvas canvas = new Canvas(300, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawShapes(gc);
Circle circle1 = new Circle(50);
circle1.setStroke(Color.GREEN);
circle1.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.7));
circle1.relocate(100, 100);
Circle circle2 = new Circle(50);
circle2.setStroke(Color.BLUE);
circle2.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.7));
circle2.relocate(200, 200);
Line line = new Line(circle1.getLayoutX(), circle1.getLayoutY(), circle2.getLayoutX(), circle2.getLayoutY());
line.setStrokeWidth(20);
Pane overlay = new Pane();
overlay.getChildren().addAll(circle1, circle2, line);
MouseGestures mg = new MouseGestures();
mg.makeDraggable(circle1);
mg.makeDraggable(circle2);
mg.makeDraggable(line);
root.getChildren().addAll(canvas, overlay);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
private void drawShapes(GraphicsContext gc) {
gc.setStroke(Color.RED);
gc.strokeRoundRect(10, 10, 230, 230, 10, 10);
}
public static class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable(Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
} else {
Node p = ((Node) (t.getSource()));
orgTranslateX = p.getTranslateX();
orgTranslateY = p.getTranslateY();
}
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
} else {
Node p = ((Node) (t.getSource()));
p.setTranslateX(newTranslateX);
p.setTranslateY(newTranslateY);
}
}
};
}
}

Categories

Resources