7
\$\begingroup\$

See the next iteration: Bare bones painter app in Java - follow-up.

I have this tiny program for drawing:

PaintCanvas.java:

package net.coderodde.javapaint;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
/**
 * This class implements a GUI component for drawing.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Jun 8, 2016)
 */
public class PaintCanvas extends Canvas {
 /**
 * The actual image being drawn to.
 */
 private final BufferedImage image;
 /**
 * The graphics context of the above.
 */
 private final Graphics2D imageGraphics;
 public PaintCanvas(final int width, final int height) {
 super();
 super.setBounds(0, 0, width, height);
 this.image = new BufferedImage(width, 
 height, 
 BufferedImage.TYPE_INT_RGB);
 this.imageGraphics = this.image.createGraphics();
 this.imageGraphics.setColor(Color.WHITE);
 this.imageGraphics.fillRect(0, 0, width, height);
 this.imageGraphics.setColor(Color.BLACK);
 final PaintCanvasMouseListener listener =
 new PaintCanvasMouseListener();
 this.addMouseListener(listener);
 this.addMouseMotionListener(listener);
 }
 @Override
 public void paint(final Graphics g) {
 update(g);
 }
 @Override
 public void update(final Graphics g) {
 g.drawImage(this.image, 0, 0, null);
 }
 private class PaintCanvasMouseListener 
 extends MouseAdapter implements MouseMotionListener {
 @Override
 public void mouseClicked(final MouseEvent event) {
 processEvent(event);
 }
 @Override
 public void mouseDragged(final MouseEvent event) {
 processEvent(event);
 }
 private void processEvent(final MouseEvent event) {
 PaintCanvas.this.imageGraphics.fillOval(event.getX(), event.getY(), 5, 5);
 PaintCanvas.this.repaint();
 }
 }
}

App.java:

package net.coderodde.javapaint;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
/**
 * This class implements an application for simple drawing.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Jun 8, 2016)
 */
public class App {
 private final JFrame frame = new JFrame("JavaPaint");
 public App() {
 frame.getContentPane().add(new PaintCanvas(640, 480));
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 frame.pack();
 setFrameLocationToCenter();
 frame.setVisible(true);
 }
 private void setFrameLocationToCenter() {
 final Dimension screenDimension = Toolkit.getDefaultToolkit()
 .getScreenSize();
 final int screenWidth = screenDimension.width;
 final int screenHeight = screenDimension.height;
 frame.setLocation((screenWidth - frame.getWidth()) / 2, 
 (screenHeight - frame.getHeight()) / 2);
 }
 public static void main(final String... args) {
 final App app = new App();
 }
}

It allowed me to draw the following masterpiece:

coderodde's awesome masterpiece

I am most interested to hear about the internals, yet feel free to tell me anything that comes to mind.

asked Jun 8, 2016 at 12:15
\$\endgroup\$
2
  • 5
    \$\begingroup\$ I see a future artist in you. \$\endgroup\$ Commented Jun 8, 2016 at 12:25
  • 1
    \$\begingroup\$ Yeah, Ima pretty hard core. \$\endgroup\$ Commented Jun 8, 2016 at 12:27

2 Answers 2

10
\$\begingroup\$

A few notes:

  • Is there a reason you aren't using JavaFX? See this answer as to why you should be using it.

  • Since I mostly rewrote your code, I'll do the best I can to explain all the components of implementing an app in JavaFX:

    1. There are a few layers within a JavaFX app; the application, the stage, the scene, and the canvas. I'll let you do more reading on your own about all of those if you wish to learn more, but these are the basic components for creating a GUI.

    2. Within the canvas, we add the event handlers to handle all the different mouse events. These are pretty self-explanatory, since you have them in your app as well.

    3. Now we have to handle the actual painting on the canvas. Again, you have a similar approach in your code, so I don't have to cover this in depth.

  • Small user-experience quip: don't start the program in the middle of the screen. Often comes across as adware-like.


Final Code:

package javafx_drawoncanvas;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class JavaFX_DrawOnCanvas extends Application {
 @Override
 public void start(Stage primaryStage) {
 Canvas canvas = new Canvas(400, 400);
 final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
 initDraw(graphicsContext);
 canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, 
 new EventHandler<MouseEvent>(){
 @Override
 public void handle(MouseEvent event) {
 graphicsContext.beginPath();
 graphicsContext.moveTo(event.getX(), event.getY());
 graphicsContext.stroke();
 }
 });
 canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, 
 new EventHandler<MouseEvent>(){
 @Override
 public void handle(MouseEvent event) {
 graphicsContext.lineTo(event.getX(), event.getY());
 graphicsContext.stroke();
 }
 });
 canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, 
 new EventHandler<MouseEvent>(){
 @Override
 public void handle(MouseEvent event) {
 }
 });
 StackPane root = new StackPane();
 root.getChildren().add(canvas);
 Scene scene = new Scene(root, 400, 400);
 primaryStage.setTitle("Paint");
 primaryStage.setScene(scene);
 primaryStage.show();
 }
 private void initDraw(GraphicsContext gc){
 double canvasWidth = gc.getCanvas().getWidth();
 double canvasHeight = gc.getCanvas().getHeight();
 gc.setFill(Color.LIGHTGRAY);
 gc.setStroke(Color.BLACK);
 gc.setLineWidth(5);
 gc.fill();
 gc.strokeRect(
 0, //x of the upper left corner
 0, //y of the upper left corner
 canvasWidth, //width of the rectangle
 canvasHeight); //height of the rectangle
 gc.setFill(Color.RED);
 gc.setStroke(Color.BLUE);
 gc.setLineWidth(1); 
 }
 public static void main(String... args) {
 launch(args);
 }
}
answered Jun 8, 2016 at 13:15
\$\endgroup\$
2
  • \$\begingroup\$ I will check your reimplementation later, but it definitely looks interesting. \$\endgroup\$ Commented Jun 8, 2016 at 13:17
  • \$\begingroup\$ Funky. I did not realize that I need to use beginPath/moveTo/stroke pattern in order to funkify the user experience. \$\endgroup\$ Commented Jun 8, 2016 at 13:48
6
\$\begingroup\$
 this.imageGraphics.setColor(Color.WHITE);
 this.imageGraphics.fillRect(0, 0, width, height);
 this.imageGraphics.setColor(Color.BLACK);

This part of your constructor could be moved to a private function clearScreen.

The rest of the code seems fine. There's comments you can make about various things like

 frame.setLocation((screenWidth - frame.getWidth()) / 2, 
 (screenHeight - frame.getHeight()) / 2);
  • Maybe put this (a - b) / 2 in a separate function...
 frame.getContentPane().add(new PaintCanvas(640, 480));

and

 PaintCanvas.this.imageGraphics.fillOval(event.getX(), event.getY(), 5, 5);

and

 private final JFrame frame = new JFrame("JavaPaint");
  • Put your constants at the topic of your class, magic strings/numbers are bad...

But what you've made is a quick app. You've got the right level of time spent to code quality out, in my opinion. ... Well, I don't know how long you spent on this, but basically, this is clean enough. Add more features and then clean as necessary whenever duplicated code pops up.

answered Jun 8, 2016 at 12:41
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.