The program I am working on simply paints over text or images by using a canvas layer over a StackPane. What I want to accomplish is that when I release the mouse the MouseEvent.MOUSE_RELEASED handler it will automatically get a snapshot of the Canvas, add the image to an ImageView Cover and display it on top of the TextArea, but it cannot add the changes to the class StackPane, namely the ImageView.
What I have here is a program that I will add to another one I'm working on, and I plan to take everything from the main class, TextCanvas, into the menu controller class from the main project.
Main class TextCanvas.java:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class TextCanvas extends Application
{
private ScrollPane Scroll = new ScrollPane();
Canvas Can = new Canvas(800, 400);
GraphicsContext GG = Can.getGraphicsContext2D();
TextArea TA = new TextArea();
ImageView Cover = new ImageView();
VBox ButtonBox = new VBox();
WordCanvas WC = new WordCanvas(Can, TA, GG);
#Override
public void start(Stage PrimaryStage)
{
ToggleGroup DrawErase = new ToggleGroup();
ToggleButton Draw = new ToggleButton("Draw");
ToggleButton Erase = new ToggleButton("Erase");
Button Clear = new Button("Clear");
Draw.setToggleGroup(DrawErase);
Erase.setToggleGroup(DrawErase);
double DotsPerInch = Screen.getPrimary().getDpi();
double W = DotsPerInch * 8.5;
double H = DotsPerInch * 11.0;
StackPane Stack = new WordCanvas(W, H);
WC.GetArea().setMaxWidth(W);
WC.GetArea().setMaxHeight(H);
WC.GetCan().setWidth(W);
WC.GetCan().setHeight(H);
DrawErase.selectedToggleProperty().addListener(new ChangeListener<Toggle>()
{
public void changed(ObservableValue<? extends Toggle> OV, Toggle TOld, Toggle TNew)
{
if(TNew == null)
{
GG.setStroke(Color.TRANSPARENT);
Stack.getChildren().remove(WC.GetCan());
}
else if(DrawErase.getSelectedToggle().equals(Draw))
{
GG.setStroke(Color.BLACK);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
else if(DrawErase.getSelectedToggle().equals(Erase))
{
GG.setStroke(Color.WHITE);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
}
});
Clear.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
WC.GetGC().clearRect(0, 0, W, H);
Stack.getChildren().remove(WC.GetCover());
}
});
Button Snap = new Button("Snap");
Snap.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(WC.GetCover()))
{
WC.GetCover().setImage(OutputImage);
Stack.getChildren().add(WC.GetCover());
}
else
{
WC.GetCover().setImage(OutputImage);
}
}
});
ButtonBox.getChildren().addAll(Draw, Erase, Clear, Snap);
BorderPane Border = new BorderPane();
Border.setCenter(Stack);
Border.setBottom(ButtonBox);
Scroll.setContent(Border);
Scroll.setFitToWidth(true);
Scroll.setFitToHeight(true);
Scene MainScene = new Scene(Scroll);
PrimaryStage.setMaximized(true);
PrimaryStage.setTitle("Practice Canvas");
PrimaryStage.setScene(MainScene);
PrimaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Secondary class that contains the TextArea and Canvas:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
public class WordCanvas extends StackPane
{
private Canvas Can = new Canvas();
private GraphicsContext GC = Can.getGraphicsContext2D();
private TextArea Area = new TextArea();
private ImageView Cover = new ImageView();
double Width;
double Height;
public WordCanvas()
{
CreateUI();
}
public WordCanvas(double W, double H)
{
this.Width = W;
this.Height = H;
CreateUI();
}
public WordCanvas(ImageView IV)
{
this.Cover = IV;
}
public WordCanvas(Canvas C, TextArea TA, GraphicsContext GG)
{
this.Can = C;
this.Area = TA;
this.GC = GG;
CreateUI();
}
public void CreateUI()
{
Caligraphy();
Imagination();
Color C = Color.STEELBLUE;
BackgroundFill BFill = new BackgroundFill(C, CornerRadii.EMPTY, Insets.EMPTY);
Background BGround = new Background(BFill);
this.getChildren().addAll(Area);
this.setBackground(BGround);
}
public void Caligraphy()
{
Area.setMaxWidth(Width);
Area.setMaxHeight(Height);
}
public void Imagination()
{
double CanvasWidth = GC.getCanvas().getWidth();
double CanvasHeight = GC.getCanvas().getHeight();
GC.setFill(Color.TRANSPARENT);
GC.fillRect(0, 0, Can.getWidth(), Can.getHeight());
GC.rect(0, 0, CanvasWidth, CanvasHeight);
GC.setLineWidth(3);
Can.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.CROSSHAIR);
}
});
Can.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.DEFAULT);
}
});
Can.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
GC.beginPath();
GC.lineTo(event.getX(), event.getY());
GC.moveTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
GC.lineTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
ImageView IV = new ImageView();
WordCanvas Stack = new WordCanvas(IV);
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(Cover))
{
Cover.setImage(OutputImage);
Stack.getChildren().add(Cover);
}
else
{
Cover.setImage(OutputImage);
}
}
});
}
public void SetCan(Canvas C)
{
this.Can = C;
}
public Canvas GetCan()
{
return Can;
}
public void SetGC(GraphicsContext GG)
{
this.GC = GG;
}
public GraphicsContext GetGC()
{
return GC;
}
public void SetArea(TextArea TA)
{
this.Area = TA;
}
public TextArea GetArea()
{
return Area;
}
public ImageView GetCover()
{
return Cover;
}
}
In the main class the Snap button handler does work as I intend to, but what I want is that in the secondary class the MouseEvent.MOUSE_RELEASED even handler automatically creates the snapshot and does what the Snap button in the main class does. However nothing I have tried works, and it won't even accept this.getChildren().add(Cover).
Another minor problem, I want the WritableImage to automatically become transparent with a more elegant solution. User #jewelsea gives a solution here which works perfectly but I would prefer something a bit shorter that doesn't have to read through every pixel. Existing png files do work as intended but when I make my own png files they are not transparent.
It's not clear why you're creating a canvas on every draw, play with the children of the parent, and why you need to capture the screen and create an image each time.
You simply want to draw over the text area, so have a single canvas over it on which you draw. The canvas's mouseTransparentProperty can be used to decide which layer gets the input.
public class TextCanvas extends Application {
private GraphicsContext gc;
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
Canvas canvas = createCanvas();
ToggleButton draw = new ToggleButton("Draw");
ToggleButton erase = new ToggleButton("Erase");
ToggleGroup drawErase = new ToggleGroup();
draw.setToggleGroup(drawErase);
erase.setToggleGroup(drawErase);
drawErase.selectedToggleProperty().addListener((ov, oldV, newV) -> {
if (newV == null) {
gc.setStroke(Color.TRANSPARENT);
canvas.setMouseTransparent(true);
} else if (drawErase.getSelectedToggle().equals(draw)) {
System.out.println("Fd");
gc.setStroke(Color.BLACK);
canvas.setMouseTransparent(false);
} else if (drawErase.getSelectedToggle().equals(erase)) {
gc.setStroke(Color.WHITE);
canvas.setMouseTransparent(false);
}
});
Button clear = new Button("Clear");
clear.setOnAction(e -> gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()));
CheckBox cb = new CheckBox("Show canvas");
cb.setSelected(true);
canvas.visibleProperty().bind(cb.selectedProperty());
VBox buttonBox = new VBox(draw, erase, clear, cb);
StackPane stack = new StackPane(textArea, canvas);
ScrollPane scrollPane = new ScrollPane(stack);
scrollPane.setFitToHeight(true);
canvas.widthProperty().bind(scrollPane.widthProperty());
canvas.heightProperty().bind(scrollPane.heightProperty());
stack.setBackground(new Background(new BackgroundFill(Color.STEELBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
BorderPane border = new BorderPane();
border.setCenter(scrollPane);
border.setBottom(buttonBox);
primaryStage.setMaximized(true);
primaryStage.setTitle("Practice Canvas");
primaryStage.setScene(new Scene(border));
primaryStage.show();
}
private Canvas createCanvas() {
Canvas canvas = new Canvas();
gc = canvas.getGraphicsContext2D();
gc.setLineWidth(3);
canvas.setOnMouseEntered(event -> canvas.setCursor(Cursor.CROSSHAIR));
canvas.setOnMouseExited(event -> canvas.setCursor(Cursor.DEFAULT));
canvas.setOnMousePressed(event -> {
gc.beginPath();
gc.lineTo(event.getX(), event.getY());
gc.moveTo(event.getX(), event.getY());
gc.stroke();
});
canvas.setOnMouseDragged(event -> {
gc.lineTo(event.getX(), event.getY());
gc.stroke();
});
canvas.setMouseTransparent(true);
return canvas;
}
public static void main(String[] args) {
launch(args);
}
}
Edit: I added a check box to toggle the canvas's visibility. You will have to clear up your usage requirements as to what each user operation does with each mode. In any case, this should be enough to play with.
Also, use proper Java naming conventions: local variables, (non-constant) field names and method argument should start with a lower case.
Related
I'm want to be able to drag-to-draw a line that ends in the center of each of two nodes (circles). I have two event handlers that I'm thinking listen for new mouse clicks, but whenever I click, nothing happens.
Here is the Start method:
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.shape.Line;
import javafx.scene.shape.Circle;
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.layout.Pane;
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Canvas canvas = new Canvas(500,500);
GraphicsContext gc = canvas.getGraphicsContext2D();
//Draw Circles onto a Pane:
Pane overlay = new Pane();
for (int i = 50; i < xDim; i+=50) {
for (int j = 50; j < yDim; j+=50) {
Circle c1 = new Circle();
c1.setCenterX(i);
c1.setCenterY(j);
c1.setRadius(5);
overlay.getChildren().add(c1);
}
}
drawLine(overlay);
root.getChildren().addAll(canvas,overlay);
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
And here is drawLine():
Line l = new Line();
overlay.addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(MouseEvent t) {
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
double circleX = p.getCenterX();
double circleY = p.getCenterY();
l.setStartX(circleX);
l.setStartY(circleY);
} else {
Node p = ((Node) (t.getSource()));
double orgTranslateX = p.getTranslateX();
double orgTranslateY = p.getTranslateY();
}
}
});
overlay.addEventHandler(MouseEvent.MOUSE_RELEASED,
new EventHandler<MouseEvent>() {
public void handle(MouseEvent t) {
if (t.getSource() instanceof Circle) {
Circle p = ((Circle) (t.getSource()));
double circleX = p.getCenterX();
double circleY = p.getCenterY();
l.setEndX(circleX);
l.setEndY(circleY);
overlay.getChildren().add(l);
} else{}
}
});
};
You can see draw line has two different event handlers, one for a click and one for release, and the only thing that changes is myLine.setEndX().
Any help would be appreciated! I apologize in advance for any unknown transgressions.
I am working on a javafx program to create three buttons which are "circle", "ellipse" and "reverse". The circle button is the first thing shown when the program is run, the reverse button is supposed to correspond with the movement of the rectangle around the circle. I am having trouble getting the reverse button to work, I set autoReverse to true but it isn't doing anything. My second issue with the code is when the ellipse button is clicked the class MyEllipse isn't showing the ellipse shape like it is supposed to and it's not removing the circle animation from the pane. I tried to create a new pane in the EventHandler for the buttonellipse but I am assuming that isn't the correct way to do it. Any help on these two issues would be greatly appreciated.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.animation.PathTransition;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Ellipse;
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.scene.layout.HBox;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class exam3b extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle (0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(115, 90, 45);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(false);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
HBox panel = new HBox(10);
panel.setAlignment(Pos.BOTTOM_CENTER);
Button button = new Button("Circle");
Button buttonellipse = new Button("Ellipse");
Button reverse = new Button("Reverse");
panel.getChildren().addAll(button,buttonellipse,reverse);
button.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
reverse.setVisible(true);
}
});
reverse.setOnAction(new EventHandler<ActionEvent>() //supposed to make rectangle move in reverse direction
{
#Override
public void handle(ActionEvent e)
{
pt.setAutoReverse(true);
}
});
buttonellipse.setOnAction(new EventHandler<ActionEvent>() //button to make ellipse appear from class MyEllipse, reverse button is supposed to disappear
{
#Override
public void handle(ActionEvent e)
{
reverse.setVisible(false);
Pane ellipse = new Pane();
ellipse.getChildren().add(new MyEllipse());
}
});
Pane pane = new Pane();
pane.getChildren().addAll(panel,circle,rectangle);
Scene scene = new Scene(pane, 350, 250);
primaryStage.setTitle("exam3b");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class MyEllipse extends Pane
{
private void paint() {
getChildren().clear();
for (int i = 0; i < 16; i++) {
Ellipse e1 = new Ellipse(getWidth() / 2, getHeight() / 2,
getWidth() / 2 - 50, getHeight() / 2 - 50);
e1.setStroke(Color.color(Math.random(), Math.random(),
Math.random()));
e1.setFill(Color.WHITE);
e1.setRotate(i * 180 / 16);
getChildren().add(e1);
}
}
#Override
public void setWidth(double width) {
super.setWidth(width);
paint();
}
#Override
public void setHeight(double height) {
super.setHeight(height);
paint();
}
}
You had an issue with how you were using MyEllipse, in that you were never adding it to your root pane nor were you settign the width/height (which leads to your paint method being called). I renamed your root pane to 'root'. Created an instance of 'MyEllipse', and in the 'Ellipse' button i add the instance of 'MyEllipse' to the root pane.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.animation.PathTransition;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Ellipse;
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.scene.layout.HBox;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class exam3b extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle(0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(115, 90, 45);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(true);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
HBox panel = new HBox(10);
panel.setAlignment(Pos.BOTTOM_CENTER);
Button button = new Button("Circle");
Button buttonellipse = new Button("Ellipse");
Button reverse = new Button("Reverse");
panel.getChildren().addAll(button, buttonellipse, reverse);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
reverse.setVisible(true);
}
});
reverse.setOnAction(new EventHandler<ActionEvent>() //supposed to make rectangle move in reverse direction
{
#Override
public void handle(ActionEvent e) {
pt.setAutoReverse(true);
}
});
Pane root = new Pane();
MyEllipse myEllipse = new MyEllipse();
myEllipse.setWidth(200);
myEllipse.setHeight(400);
buttonellipse.setOnAction(new EventHandler<ActionEvent>() //button to make ellipse appear from class MyEllipse, reverse button is supposed to disappear
{
#Override
public void handle(ActionEvent e) {
// reverse.setVisible(false);
if (root.getChildren().contains(myEllipse)) {
root.getChildren().remove(myEllipse);
} else {
System.out.println("adding ellipse");
root.getChildren().add(myEllipse);
}
}
});
root.getChildren().addAll(panel, circle, rectangle);
Scene scene = new Scene(root, 350, 250);
primaryStage.setTitle("exam3b");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class MyEllipse extends Pane {
public MyEllipse() {
}
private void paint() {
getChildren().clear();
for (int i = 0; i < 16; i++) {
Ellipse e1 = new Ellipse(getWidth() / 2, getHeight() / 2,
getWidth() / 2 - 50, getHeight() / 2 - 50);
e1.setStroke(Color.color(Math.random(), Math.random(),
Math.random()));
e1.setFill(Color.WHITE);
e1.setStrokeWidth(1);
e1.setRotate(i * 180 / 16);
getChildren().add(e1);
}
}
#Override
public void setWidth(double width) {
super.setWidth(width);
paint();
}
#Override
public void setHeight(double height) {
super.setHeight(height);
paint();
}
}
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'm now running into a problem with the input and output streams. It won't convert the socket input stream into a byte[] or the socket output stream into an int. How would get the program to actually send the byte[] array to the server? Here's the updated code:
import java.io.*;
import java.net.*;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.awt.image.BufferedImage;
import java.io.File;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.*;
import javax.imageio.ImageIO;
public class PaintClient extends Application {
//GUI components
private TextField tfRed = new TextField("");
private TextField tfGreen = new TextField("");
private TextField tfBlue = new TextField("");
private Button btSetColor = new Button("Set Color");
private Button btReset = new Button("Reset");
private Button btSend = new Button("Send");
//Networking components
private Socket socket;
private ByteArrayOutputStream byteOut;
private ByteArrayInputStream byteIn;
#Override
public void start(Stage primaryStage) {
tfRed.setPrefWidth(80);
tfGreen.setPrefWidth(80);
tfBlue.setPrefWidth(80);
GridPane gridPane = new GridPane();
gridPane.add(new Label("Color"), 0, 0);
gridPane.add(tfRed, 1, 0);
gridPane.add(tfGreen, 2, 0);
gridPane.add(tfBlue, 3, 0);
gridPane.add(btSetColor, 4, 0);
gridPane.add(btReset, 2, 1);
gridPane.add(btSend, 3, 1);
Canvas canvas = new Canvas(365,375);
final GraphicsContext gc = canvas.getGraphicsContext2D();
initDraw(gc);
BorderPane bPane = new BorderPane();
bPane.setTop(gridPane);
bPane.setCenter(canvas);
Scene scene = new Scene(bPane, 375, 450);
primaryStage.setTitle("Drawing Canvas");
primaryStage.setScene(scene);
primaryStage.show();
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
gc.beginPath();
gc.moveTo(event.getX(), event.getY());
gc.stroke();
}
});
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
gc.lineTo(event.getX(), event.getY());
gc.stroke();
}
});
//Networking
try {
socket = new Socket("localhost", 8000);
byteIn = new ByteArrayInputStream(socket.getInputStream());
byteOut = new ByteArrayOutputStream(socket.getOutputStream());
new Thread(() -> run()).start();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
public void run(){
while(true) {
/*try {
} catch (IOException ex) {
ex.printStackTrace();
}*/
}
}
public void process (Canvas canvas) {
try {
WritableImage image = canvas.snapshot(null, null);
BufferedImage bImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bImage, "jpg", byteOut);
byteOut.flush();
byte[] byteImage = byteOut.toByteArray();
byteOut.close();
byteOut.write(byteImage);
} catch (IOException ex) {
System.err.println(ex);
}
}
public static void main(String[] args) {
Application.launch(args);
}
private void initDraw(GraphicsContext gc) {
double canvasWidth = gc.getCanvas().getWidth();
double canvasHeight = gc.getCanvas().getHeight();
//Event handler when set color button is clicked
btSetColor.setOnAction(e -> {
if(!(tfRed.getText().trim().isEmpty()) && !(tfGreen.getText().trim().isEmpty()) &&
!(tfBlue.getText().trim().isEmpty())) {
int red = Integer.parseInt(tfRed.getText());
int green = Integer.parseInt(tfGreen.getText());
int blue = Integer.parseInt(tfBlue.getText());
gc.setStroke(Color.rgb(red, green, blue));
}
});
gc.setLineWidth(5);
gc.fill();
gc.strokeRect(
0, //x of the upper left corner of the drawing area
0, //y of the upper left corner of the drawing area
canvasWidth, //width of the drawing area
canvasHeight); //height of the drawing area
gc.setLineWidth(1);
//Event handler when reset button is clicked
btReset.setOnAction(e -> {
gc.clearRect(5, 5, 355, 365);
});
}
}
Sending GraphicsContext directly is not possible, as GraphicsContext is not explicitly Serializable. Typical ways of achieving what you intend are given below.
A. Remember user actions as "commands" and send them to the server / other clients. The commands will be executed on the client side and you will have the same rendered view. As example of implementation, you can wrap GraphicsContext API with your own that creates those commands.
public void fillOvalX(double x, double y, double w, double h) {
// a possible approach
commands.put(new DrawCommand(Type.FILL_OVAL, x, y, w, h));
g.fillOval(x, y, w, h);
}
B. Perform a snapshot of the GraphicsContext into a JavaFX Image by calling snapshot(). Then convert it into a BufferedImage using SwingFXUtils and finally to byte[] using ImageIO and ByteArrayOutputStream. The array byte[] can then be serialized over network. At the other end of the connection you will need to perform same operations in the opposite order. The links provide with sufficient information to do that.
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);
}
}