CS 261 Lab B - Painter
Due Wed Sep 10 at 3:00pm
Overview
In this lab you will create a simple graphical "painting" program, similar to Microsoft Paint. With this program, the user should be able to drag the mouse and draw lines on a window in order to sketch a pretty picture :) The user will also be able to change the color of the "brush" (what color line they are drawing), and the thickness of the brush stroke. (Drawing lines without a mouse exacerbates my already poor handwriting :p)
This lab will be completed in pairs. Partner assignments for this lab can be found on Moodle
- Remember to review the pair programming guidelines before you get started!
Objectives
- To practice with GUIs and event-driven programming and graphical drawing
- To practice writing event-driven programs using Listeners
Necessary Files
You will need to download the copy of the zipped source files. and import them into Eclipse (see previous lab for instructions).
This file includes a PainterFrame
class, which will get you started with the frams and buttons for your painting program. (You should know how to put these together, but the provided class will help keep the lab managable). Note that this class PainterFrame
contains the main()
method.
Lab Details
For this lab, you'll be making a new class: a Painter
which is a GUI element that will represent and display the drawing you have created.
-
Start by creating the new
Painter
class. This is going to be a GUI element, so you should extend the existingJPanel
class to automatically give it basic display functionality (e.g., allow it to be shown on the screen). You will also need a couple of specific methods, described a ways further down the page (you can also see what methods are required to get the providedPainterFrame
to compile). First fill in the "skeleton" of your program by writing in the method signatures, so that everything can compile and you can test your code.- Remember to import the necessary packages (
java.awt.*
,javax.swing.*
, andjava.awt.event.*
) for building a GUI! You can do this easily using the "Source > Organize Imports" menu option in Eclipse. -
You'll want to call the inherited
setPreferredSize()
method to specify how big your canvas will be. The size should be stored as constants for easy modification--don't use "magic numbers"!- Helpful hint: JPanels already have WIDTH and HEIGHT constants, which get inherited. But you can't override inherited constants (because static variables/methods are not polymorphic!), so you can't change the variables the way you want. Instead, you'll need to create new variables (with different names)--for example,
CANVAS_WIDTH
andCANVAS_HEIGHT
.
- Helpful hint: JPanels already have WIDTH and HEIGHT constants, which get inherited. But you can't override inherited constants (because static variables/methods are not polymorphic!), so you can't change the variables the way you want. Instead, you'll need to create new variables (with different names)--for example,
- Remember to import the necessary packages (
-
You will need to override the inherited
public void paintComponent(Graphics g)
method, so that you can have the Painter be drawn differently than the normal parent JPanel---that is, with something on it! This method will be automatically called by Java whenever you need to "refresh" what is shown on the screen, such as when you decide to draw something new.- Be sure and call
super.paintComponent(g)
to make sure any secret setup performed by the parent method occurs. -
You should try using the
fillRect()
method to draw a quick rectangle on the canvas, just to make sure things work!
- Be sure and call
-
There is a problem with this basic setup though: you want to be able to update the display to show whatever the user has drawn. However, everytime the panel refreshes and
paintComponent()
is called, any previous display gets erased and forgotten! So you will need to store the drawing the user creates somehow. (Take a moment and think about this problem---do you understand what the issue is?)-
To do this, we are going to be slightly tricky: rather than having the user draw directly onto the JPanel's Graphics object, we're instead going to have the user draw onto a separate image object: in particular a
BufferedImage
(this will act as a "buffer" between the user and the display). We will then draw that BufferedImage onto the JPanel's Graphics object, thereby transferring the user's drawing onto the screen! -
You will need to make a
BufferedImage
instance variable. Be sure and initialize the object in your Painter's constructor.-
To save you some time:
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
is the basic template you want.
-
To save you some time:
-
Here's the sneaky part: the BufferedImage does have a
Graphics
object that you can draw on just like any otherGraphics
object! We can access this by calling the createGraphics() method on the BufferedImage object we've created. This method returns aGraphics2D
object (a class that extends Graphics with extra methods; our normal paintbrush object). Call this method in your Painter's constructor and store thisGraphics2D
object as an instance variable. - Now whenever you want to draw on a graphics context, just draw on that Graphics2D instance variable. For example, you might start by painting a giant white rectangle on that object to act as a background, as well as a smaller colored oval to test things.
-
Last step: you need to make sure that the BufferedImage (along with its associated Graphics2D object) is being drawn on the JPanel! Inside the
paintComponent()
, call thedrawImage()
method on the parameterg
, passing in the BufferedImage instance variable. For example:g.drawImage(image,0,0,null); //image is the name of the BufferedImage. null is the "imageobserver" we're not worrying about.
- This is like drawing on a large sheet of butcher paper (the BufferedImage), and then hanging that paper in the window (drawing it in paintComponent).
- Be sure and test that this works! Your colored oval should show up on the JFrame when you run the program. Now you have a basic double-buffered graphics program!
-
To do this, we are going to be slightly tricky: rather than having the user draw directly onto the JPanel's Graphics object, we're instead going to have the user draw onto a separate image object: in particular a
-
The next step is to add in the user interaction (via the mouse). This will use event-driven programming--when an event happens (e.g., the user moves the mouse), you will respond with some code to run. To set this up, you will need to have your Painter implement the
MouseListener
interface, which will let you respond to Mouse events. However, theMouseListener
only handles when the mouse buttons are pressed and when the mouse enters or leaves the JPanel--it doesn't track when the mouse moves around the JPanel. To do that, you'll need to also implement theMouseMotionListener
interface.- There will be a lot of methods you'll need to add--but you won't necessary use them all (many of them can be empty). However, in order to work with the Java's event system, you need to use these particular interfaces which means having all the methods. In effect, you're agreeing to fulfill the contract, but you fulfill that contract by doing nothing.
-
Remember the three steps when implementing a listener:
- Modify the class declaration so you implement the interfaces. Be sure to define the required methods (yes there are a lot, but you won't need to fill in code for all of them, I promise!).
- Register the listener, so that the events know which listener object will respond to them. You'll need to call
addMouseListener()
andaddMouseMotionListener()
in your constructor. Think about what object you are calling these methods on (hint: it's the Painter you're creating!) and what object is the listener that you are adding (hint: it's the Painter you're creating!) - Fill in the required methods. You might start by just printing out a message like "mouse was pressed!" or "mouse was dragged!" Also try printing out the
MouseEvent
objects themselves: what kinds of information are you seeing?
-
Now you need to make it so that you draw on the
Graphics2D
instance variable whenever the mouse is pressed and/or dragged. Start by just drawing a tiny oval (diameter 1 or 2) at the position where the MouseEvent occured. Since lots of events should occur, you'll get lots of tiny circles that when connected together will form lines!- You can use the
getX()
andgetY()
methods of theMouseEvent
object to get thex-
andy-
coordinates of the event's location. - Remember to call
repaint()
whenever you've updated the drawing, so that Java knows it should call the paintComponent() method!
- You can use the
-
You might notice that the dots get scattered if you move the mouse too fast: this is because MouseEvents don't generate fast enough to keep up with your lightning hands! So how do you fix this?
- The cleanest way is to think about instead of drawing a dot at each MouseEvent, you're going to draw a line between the location of succesive MouseEvents. This will let you (literally) connect the dots!
- To do this, your program will need to remember the location (i.e., the
x
andy
variables) of the previous MouseEvent. -
Then whenever a MouseEvent occurs where you'd want to draw, you'll draw a line from the previous location to the location of the current event, and then store the location of current event as the previous location!
- You'll need to be careful about whether there IS a previous location (i.e., when the mouse is first pressed, you can't draw a line from nowhere). Similarly, when the mouse button is released, you'll want to "clear" your previous event. Storing values of
-1
for locations are a good way to "flag" or mark that a location has not been set, or you could use a separate sentinel variable.
- You'll need to be careful about whether there IS a previous location (i.e., when the mouse is first pressed, you can't draw a line from nowhere). Similarly, when the mouse button is released, you'll want to "clear" your previous event. Storing values of
-
This can get tricky, but gets to the heart of event-driven programming: rather than having code simply run when you start a program, you have bits of code run every time an event occurs--even if you don't know what those events will be!
- Be sure and test your code! Remember to "play computer" and/or use print statements to figure out what is going on during different methods.
-
At this point, you should be able to draw nice black lines all over your program! Now you can add some finishing touches to enable interesting drawing in response to the PainterFrame's buttons being pressed. The methods you need to add and their functions are described below:
-
public void eraseAll()
-
This method should clear the current drawing. Since there is no "erase" method for a Graphics object, you should instead just white everything out. Be sure to call
repaint()
to make the new blank canvas show up! - You should store the previously selected color in a local variable, and then change the paint color to that after you've whited things out.
-
This method should clear the current drawing. Since there is no "erase" method for a Graphics object, you should instead just white everything out. Be sure to call
-
public void setPaintColor(Color c)
- This method will let you change the color of the "brush" the user is currently drawing with. This method is easy to fill in: just set the color or paint of the Graphics2D instance variable!
-
public void setBrushSize(float width)
-
This method will let you change the thickness of the "brush" the user is currently drawing with. Again, you can use built-in Java classes to do this easily. The
Graphics2D
has asetStroke()
method that lets you change the thickness of the stroke. This method takes as a parameter aStroke
object. ButStroke
is just an interface, so instead you'll want to use aBasicStroke
object (whew!) But don't worry, aBasicStroke
is simple to create: check the documentation for how to call its constructor. - This is only one or two lines of code: do not overthink it!
-
This method will let you change the thickness of the "brush" the user is currently drawing with. Again, you can use built-in Java classes to do this easily. The
-
- And that should about do it! Be sure and test your code (and all the buttons) thoroughly, and be sure to draw lots of pretty pictures :)
Submission
Make sure both your names are on all the classes you've modified, and upload your source code to the LabB submission folder on vhedwig (see the previous lab if you need help). Only one partner needs to upload the code. Make sure you upload your work to the correct folder! The lab is due at the start of class the day after lab.
After you have submitted your solution, log onto Moodle and submit the Lab B Partner Evaluation. Both partners need to submit evaluations.
Extensions
- Add the ability to go into "eraser" mode. This is basically drawing thick lines that are the same color as the background!
- Add in additional input methods: for example, maybe a text input where the user can click on a spot on the screen, is prompted for some text (using JOptionPane) and that text is then drawn on the image.
-
Add in a button that lets you save the drawn image to a file. You can use file writing code similar to that found in Hwk 1; the
ImageIO.write()
method will let you write the image to a file (rather than writing text to file as in Hwk1).
Grading
This assignment will be graded out of 15 points:
- [2pt] You have implement a JPanel that displays an image by overriding parent methods
- [1pt] You use a BufferedImage to store your drawing and draw it on the panel
- [3pt] Your program responds to MouseListener and MouseMotionListener events by drawing on the screen
- [2pt] Your program draws solid lines on the screen (without skipping)!
- [1pt] Your program implements the eraseAll() method
- [1pt] Your program implements the setPaintColor() method
- [1pt] Your program implements the setBrushStroke() method
- [1pt] Each class has a class comment with your name in the
@author
field - [2pt] Your program makes good use of instance variables and constants (for "magic numbers")
- [1pt] You have completed your lab partner evaluation.