Skip to content

Instantly share code, notes, and snippets.

@abdelaziz321
Last active June 18, 2023 00:11
Show Gist options
  • Save abdelaziz321/e9932bd15e4b263c3dae08644c61600c to your computer and use it in GitHub Desktop.
Save abdelaziz321/e9932bd15e4b263c3dae08644c61600c to your computer and use it in GitHub Desktop.
simple paint App JavaFX
/*
* ===================================================
* contents
* ===================================================
* 00- Free draw
* 01- rubber
* 02- draw Line
* 03- draw Rectangele
* 04- draw Circle
* 05- draw Ellipse
* 06- Text
*
* ----------------------------------------------------
* Features
* ----------------------------------------------------
* - the ability to change Line color
* - the ability to change Fill color
* - the ability to change Line width
* - Undo & Redo
* - Open Image && save Image
*
* ____________________________________________________
* problems
* - undo & redo : not working with free draw and rubber
* - Line & Rect & Circ ... aren't be updated while drawing
* ===================================================
*/
package paint;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class Paint extends Application {
@Override
public void start(Stage primaryStage) {
Stack<Shape> undoHistory = new Stack();
Stack<Shape> redoHistory = new Stack();
/* ----------btns---------- */
ToggleButton drowbtn = new ToggleButton("Draw");
ToggleButton rubberbtn = new ToggleButton("Rubber");
ToggleButton linebtn = new ToggleButton("Line");
ToggleButton rectbtn = new ToggleButton("Rectange");
ToggleButton circlebtn = new ToggleButton("Circle");
ToggleButton elpslebtn = new ToggleButton("Ellipse");
ToggleButton textbtn = new ToggleButton("Text");
ToggleButton[] toolsArr = {drowbtn, rubberbtn, linebtn, rectbtn, circlebtn, elpslebtn, textbtn};
ToggleGroup tools = new ToggleGroup();
for (ToggleButton tool : toolsArr) {
tool.setMinWidth(90);
tool.setToggleGroup(tools);
tool.setCursor(Cursor.HAND);
}
ColorPicker cpLine = new ColorPicker(Color.BLACK);
ColorPicker cpFill = new ColorPicker(Color.TRANSPARENT);
TextArea text = new TextArea();
text.setPrefRowCount(1);
Slider slider = new Slider(1, 50, 3);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
Label line_color = new Label("Line Color");
Label fill_color = new Label("Fill Color");
Label line_width = new Label("3.0");
Button undo = new Button("Undo");
Button redo = new Button("Redo");
Button save = new Button("Save");
Button open = new Button("Open");
Button[] basicArr = {undo, redo, save, open};
for(Button btn : basicArr) {
btn.setMinWidth(90);
btn.setCursor(Cursor.HAND);
btn.setTextFill(Color.WHITE);
btn.setStyle("-fx-background-color: #666;");
}
save.setStyle("-fx-background-color: #80334d;");
open.setStyle("-fx-background-color: #80334d;");
VBox btns = new VBox(10);
btns.getChildren().addAll(drowbtn, rubberbtn, linebtn, rectbtn, circlebtn, elpslebtn,
textbtn, text, line_color, cpLine, fill_color, cpFill, line_width, slider, undo, redo, open, save);
btns.setPadding(new Insets(5));
btns.setStyle("-fx-background-color: #999");
btns.setPrefWidth(100);
/* ----------Drow Canvas---------- */
Canvas canvas = new Canvas(1080, 790);
GraphicsContext gc;
gc = canvas.getGraphicsContext2D();
gc.setLineWidth(1);
Line line = new Line();
Rectangle rect = new Rectangle();
Circle circ = new Circle();
Ellipse elps = new Ellipse();
canvas.setOnMousePressed(e->{
if(drowbtn.isSelected()) {
gc.setStroke(cpLine.getValue());
gc.beginPath();
gc.lineTo(e.getX(), e.getY());
}
else if(rubberbtn.isSelected()) {
double lineWidth = gc.getLineWidth();
gc.clearRect(e.getX() - lineWidth / 2, e.getY() - lineWidth / 2, lineWidth, lineWidth);
}
else if(linebtn.isSelected()) {
gc.setStroke(cpLine.getValue());
line.setStartX(e.getX());
line.setStartY(e.getY());
}
else if(rectbtn.isSelected()) {
gc.setStroke(cpLine.getValue());
gc.setFill(cpFill.getValue());
rect.setX(e.getX());
rect.setY(e.getY());
}
else if(circlebtn.isSelected()) {
gc.setStroke(cpLine.getValue());
gc.setFill(cpFill.getValue());
circ.setCenterX(e.getX());
circ.setCenterY(e.getY());
}
else if(elpslebtn.isSelected()) {
gc.setStroke(cpLine.getValue());
gc.setFill(cpFill.getValue());
elps.setCenterX(e.getX());
elps.setCenterY(e.getY());
}
else if(textbtn.isSelected()) {
gc.setLineWidth(1);
gc.setFont(Font.font(slider.getValue()));
gc.setStroke(cpLine.getValue());
gc.setFill(cpFill.getValue());
gc.fillText(text.getText(), e.getX(), e.getY());
gc.strokeText(text.getText(), e.getX(), e.getY());
}
});
canvas.setOnMouseDragged(e->{
if(drowbtn.isSelected()) {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
}
else if(rubberbtn.isSelected()){
double lineWidth = gc.getLineWidth();
gc.clearRect(e.getX() - lineWidth / 2, e.getY() - lineWidth / 2, lineWidth, lineWidth);
}
});
canvas.setOnMouseReleased(e->{
if(drowbtn.isSelected()) {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
gc.closePath();
}
else if(rubberbtn.isSelected()) {
double lineWidth = gc.getLineWidth();
gc.clearRect(e.getX() - lineWidth / 2, e.getY() - lineWidth / 2, lineWidth, lineWidth);
}
else if(linebtn.isSelected()) {
line.setEndX(e.getX());
line.setEndY(e.getY());
gc.strokeLine(line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY());
undoHistory.push(new Line(line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY()));
}
else if(rectbtn.isSelected()) {
rect.setWidth(Math.abs((e.getX() - rect.getX())));
rect.setHeight(Math.abs((e.getY() - rect.getY())));
//rect.setX((rect.getX() > e.getX()) ? e.getX(): rect.getX());
if(rect.getX() > e.getX()) {
rect.setX(e.getX());
}
//rect.setY((rect.getY() > e.getY()) ? e.getY(): rect.getY());
if(rect.getY() > e.getY()) {
rect.setY(e.getY());
}
gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
gc.strokeRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
undoHistory.push(new Rectangle(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()));
}
else if(circlebtn.isSelected()) {
circ.setRadius((Math.abs(e.getX() - circ.getCenterX()) + Math.abs(e.getY() - circ.getCenterY())) / 2);
if(circ.getCenterX() > e.getX()) {
circ.setCenterX(e.getX());
}
if(circ.getCenterY() > e.getY()) {
circ.setCenterY(e.getY());
}
gc.fillOval(circ.getCenterX(), circ.getCenterY(), circ.getRadius(), circ.getRadius());
gc.strokeOval(circ.getCenterX(), circ.getCenterY(), circ.getRadius(), circ.getRadius());
undoHistory.push(new Circle(circ.getCenterX(), circ.getCenterY(), circ.getRadius()));
}
else if(elpslebtn.isSelected()) {
elps.setRadiusX(Math.abs(e.getX() - elps.getCenterX()));
elps.setRadiusY(Math.abs(e.getY() - elps.getCenterY()));
if(elps.getCenterX() > e.getX()) {
elps.setCenterX(e.getX());
}
if(elps.getCenterY() > e.getY()) {
elps.setCenterY(e.getY());
}
gc.strokeOval(elps.getCenterX(), elps.getCenterY(), elps.getRadiusX(), elps.getRadiusY());
gc.fillOval(elps.getCenterX(), elps.getCenterY(), elps.getRadiusX(), elps.getRadiusY());
undoHistory.push(new Ellipse(elps.getCenterX(), elps.getCenterY(), elps.getRadiusX(), elps.getRadiusY()));
}
redoHistory.clear();
Shape lastUndo = undoHistory.lastElement();
lastUndo.setFill(gc.getFill());
lastUndo.setStroke(gc.getStroke());
lastUndo.setStrokeWidth(gc.getLineWidth());
});
// color picker
cpLine.setOnAction(e->{
gc.setStroke(cpLine.getValue());
});
cpFill.setOnAction(e->{
gc.setFill(cpFill.getValue());
});
// slider
slider.valueProperty().addListener(e->{
double width = slider.getValue();
if(textbtn.isSelected()){
gc.setLineWidth(1);
gc.setFont(Font.font(slider.getValue()));
line_width.setText(String.format("%.1f", width));
return;
}
line_width.setText(String.format("%.1f", width));
gc.setLineWidth(width);
});
/*------- Undo & Redo ------*/
// Undo
undo.setOnAction(e->{
if(!undoHistory.empty()){
gc.clearRect(0, 0, 1080, 790);
Shape removedShape = undoHistory.lastElement();
if(removedShape.getClass() == Line.class) {
Line tempLine = (Line) removedShape;
tempLine.setFill(gc.getFill());
tempLine.setStroke(gc.getStroke());
tempLine.setStrokeWidth(gc.getLineWidth());
redoHistory.push(new Line(tempLine.getStartX(), tempLine.getStartY(), tempLine.getEndX(), tempLine.getEndY()));
}
else if(removedShape.getClass() == Rectangle.class) {
Rectangle tempRect = (Rectangle) removedShape;
tempRect.setFill(gc.getFill());
tempRect.setStroke(gc.getStroke());
tempRect.setStrokeWidth(gc.getLineWidth());
redoHistory.push(new Rectangle(tempRect.getX(), tempRect.getY(), tempRect.getWidth(), tempRect.getHeight()));
}
else if(removedShape.getClass() == Circle.class) {
Circle tempCirc = (Circle) removedShape;
tempCirc.setStrokeWidth(gc.getLineWidth());
tempCirc.setFill(gc.getFill());
tempCirc.setStroke(gc.getStroke());
redoHistory.push(new Circle(tempCirc.getCenterX(), tempCirc.getCenterY(), tempCirc.getRadius()));
}
else if(removedShape.getClass() == Ellipse.class) {
Ellipse tempElps = (Ellipse) removedShape;
tempElps.setFill(gc.getFill());
tempElps.setStroke(gc.getStroke());
tempElps.setStrokeWidth(gc.getLineWidth());
redoHistory.push(new Ellipse(tempElps.getCenterX(), tempElps.getCenterY(), tempElps.getRadiusX(), tempElps.getRadiusY()));
}
Shape lastRedo = redoHistory.lastElement();
lastRedo.setFill(removedShape.getFill());
lastRedo.setStroke(removedShape.getStroke());
lastRedo.setStrokeWidth(removedShape.getStrokeWidth());
undoHistory.pop();
for(int i=0; i < undoHistory.size(); i++) {
Shape shape = undoHistory.elementAt(i);
if(shape.getClass() == Line.class) {
Line temp = (Line) shape;
gc.setLineWidth(temp.getStrokeWidth());
gc.setStroke(temp.getStroke());
gc.setFill(temp.getFill());
gc.strokeLine(temp.getStartX(), temp.getStartY(), temp.getEndX(), temp.getEndY());
}
else if(shape.getClass() == Rectangle.class) {
Rectangle temp = (Rectangle) shape;
gc.setLineWidth(temp.getStrokeWidth());
gc.setStroke(temp.getStroke());
gc.setFill(temp.getFill());
gc.fillRect(temp.getX(), temp.getY(), temp.getWidth(), temp.getHeight());
gc.strokeRect(temp.getX(), temp.getY(), temp.getWidth(), temp.getHeight());
}
else if(shape.getClass() == Circle.class) {
Circle temp = (Circle) shape;
gc.setLineWidth(temp.getStrokeWidth());
gc.setStroke(temp.getStroke());
gc.setFill(temp.getFill());
gc.fillOval(temp.getCenterX(), temp.getCenterY(), temp.getRadius(), temp.getRadius());
gc.strokeOval(temp.getCenterX(), temp.getCenterY(), temp.getRadius(), temp.getRadius());
}
else if(shape.getClass() == Ellipse.class) {
Ellipse temp = (Ellipse) shape;
gc.setLineWidth(temp.getStrokeWidth());
gc.setStroke(temp.getStroke());
gc.setFill(temp.getFill());
gc.fillOval(temp.getCenterX(), temp.getCenterY(), temp.getRadiusX(), temp.getRadiusY());
gc.strokeOval(temp.getCenterX(), temp.getCenterY(), temp.getRadiusX(), temp.getRadiusY());
}
}
} else {
System.out.println("there is no action to undo");
}
});
// Redo
redo.setOnAction(e->{
if(!redoHistory.empty()) {
Shape shape = redoHistory.lastElement();
gc.setLineWidth(shape.getStrokeWidth());
gc.setStroke(shape.getStroke());
gc.setFill(shape.getFill());
redoHistory.pop();
if(shape.getClass() == Line.class) {
Line tempLine = (Line) shape;
gc.strokeLine(tempLine.getStartX(), tempLine.getStartY(), tempLine.getEndX(), tempLine.getEndY());
undoHistory.push(new Line(tempLine.getStartX(), tempLine.getStartY(), tempLine.getEndX(), tempLine.getEndY()));
}
else if(shape.getClass() == Rectangle.class) {
Rectangle tempRect = (Rectangle) shape;
gc.fillRect(tempRect.getX(), tempRect.getY(), tempRect.getWidth(), tempRect.getHeight());
gc.strokeRect(tempRect.getX(), tempRect.getY(), tempRect.getWidth(), tempRect.getHeight());
undoHistory.push(new Rectangle(tempRect.getX(), tempRect.getY(), tempRect.getWidth(), tempRect.getHeight()));
}
else if(shape.getClass() == Circle.class) {
Circle tempCirc = (Circle) shape;
gc.fillOval(tempCirc.getCenterX(), tempCirc.getCenterY(), tempCirc.getRadius(), tempCirc.getRadius());
gc.strokeOval(tempCirc.getCenterX(), tempCirc.getCenterY(), tempCirc.getRadius(), tempCirc.getRadius());
undoHistory.push(new Circle(tempCirc.getCenterX(), tempCirc.getCenterY(), tempCirc.getRadius()));
}
else if(shape.getClass() == Ellipse.class) {
Ellipse tempElps = (Ellipse) shape;
gc.fillOval(tempElps.getCenterX(), tempElps.getCenterY(), tempElps.getRadiusX(), tempElps.getRadiusY());
gc.strokeOval(tempElps.getCenterX(), tempElps.getCenterY(), tempElps.getRadiusX(), tempElps.getRadiusY());
undoHistory.push(new Ellipse(tempElps.getCenterX(), tempElps.getCenterY(), tempElps.getRadiusX(), tempElps.getRadiusY()));
}
Shape lastUndo = undoHistory.lastElement();
lastUndo.setFill(gc.getFill());
lastUndo.setStroke(gc.getStroke());
lastUndo.setStrokeWidth(gc.getLineWidth());
} else {
System.out.println("there is no action to redo");
}
});
/*------- Save & Open ------*/
// Open
open.setOnAction((e)->{
FileChooser openFile = new FileChooser();
openFile.setTitle("Open File");
File file = openFile.showOpenDialog(primaryStage);
if (file != null) {
try {
InputStream io = new FileInputStream(file);
Image img = new Image(io);
gc.drawImage(img, 0, 0);
} catch (IOException ex) {
System.out.println("Error!");
}
}
});
// Save
save.setOnAction((e)->{
FileChooser savefile = new FileChooser();
savefile.setTitle("Save File");
File file = savefile.showSaveDialog(primaryStage);
if (file != null) {
try {
WritableImage writableImage = new WritableImage(1080, 790);
canvas.snapshot(null, writableImage);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(writableImage, null);
ImageIO.write(renderedImage, "png", file);
} catch (IOException ex) {
System.out.println("Error!");
}
}
});
/* ----------STAGE & SCENE---------- */
BorderPane pane = new BorderPane();
pane.setLeft(btns);
pane.setCenter(canvas);
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setTitle("Paint");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
@mrl1992
Copy link

mrl1992 commented Oct 21, 2019

hi

how can imake the rubber button larger? right now it only erases a small amaount of what i've drawn

really cool application btw

@abdelaziz321
Copy link
Author

Hey @mrl1992,
you can achieve this by increasing the line width using the slider at line 275.

@lg28literconvectionmicrowaveoven

noice bruh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment