CS 161 Lab J - Painter

Due Fri Apr 17 at noon

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 is hard :p)

This lab will be completed in pairs. Be sure to review the pair programming guidelines. You should also try to work with a different partner than you have before!

Objectives

Necessary Files

You will need to download and extract the BlueJ project from the LabJ.zip file. This project will supply you with a PainterGUI class. This class will get you started with the frame and buttons for your Painting program (you should know how to do this, but the provided class will help keep the lab a manageable size). You will not need to modify this class.

Part 1: The Painter Class

For this lab you will be making one new class: a Painter class is a GUI component (a JPanel) which will represent and display the drawing you have created.

  1. Start by making thew new Painter class. This class should extends JPanel, so declare it as such in the class declaration. This class will need a couple of specific methods, described in Part 4. a ways further down the page (you can also see what methods are required by the PainterGUI). 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.
    • Reminder: you will need to import the necessary packages (java.awt.*, javax.swing.*, and java.awt.event.*) for building a GUI!
    • You'll also want to call .setPreferredSize() to specify how big your canvas will be (800x600 is nice--maybe make a CANVAS_WIDTH and CANVAS_HEIGHT constant?)
      • Remember that because your Painter is a JPanel, you can call the setPreferredSize() method on this. The setPreferredSize() method takes a Dimension object as a parameter.
      • Note that JPanels already have WIDTH and HEIGHT constants. However, because of inheritance these don't quite work the way you'd think they do. If you want to make particular constants, you'll want to create different variables (with different names), rather than trying to override the inherited constants!
  2. You will need to override the
    public void paintComponent(Graphics brush)
    method, so that you can have the Painter be drawn differently than the normal parent JPanel---that is, with something on it! This should be a familiar step from other graphical programs we've made.
    • You might try using the fillRect() method to draw a quick rectangle on the canvas, just to make sure things work!
  3. Remember to switch drivers regularly.

Part 2: The Image Buffer

There is a problem though: we want to be able to update the display to show whatever the user has drawn. However, every time the panel refreshes and paintComponent() is called, any previous display gets erased and forgotten! So we will need to store the drawing the user creates. (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 with the JPanel's Graphics object, we're instead going to have the user draw onto a separate image object. This image object is represented by the BufferedImage class (it is an image that has a "buffer" of data behind it--kind of like the buffer you used in your PianoSynthesizer). We will then draw that BufferedImage onto the JPanel's Graphics object, thereby transferring the user's drawing onto the screen!

Details on setting up the BufferedImage are below:

  1. You will need to import the BufferedImage class; you can look up its package in the linked documentation.
  2. You'll need to declare a new BufferedImage instance variable, and assign a value to it in the Painter's constructor.

    BufferedImage's constructor is a bit funky to call, but can instantiate one with:

    //makes a BufferedImage of the given width/height and using an int-precision RGB buffer
    image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  3. Here's the sneaky part: the BufferedImage does have a Graphics2D object that we can draw on. (A Graphics2D is just an extension of Graphics with some extra methods. For our purposes: it is a Graphics brush!) We can access this by calling the createGraphics() method on the BufferedImage object we've created. This method returns a Graphics2D object (which is like our normal paintbrush object).
    • This is the same thing you did with the IOFrame in the PianoSynthesizer
    • You'll want to store the "buffer's brush" Graphics2D object as an instance variable (so that you can get at it easily in the future).
  4. Now whenever you want to draw something, you should draw on the "buffer brush" 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.
    • You might do this in your Painter's constructor for testing
  5. Last step: we need to make sure that the BufferedImage (along with its associated Graphics2D object) is being drawn on the JPanel (that we've hung our picture in the window). Inside the paintComponent() method, call the drawImage() method on the parameter brush, passing in the BufferedImage instance variable. For example:
    brush.drawImage(image,0,0,null); //image is the name of the BufferedImage
  6. Be sure and test that this works! Your colored oval should show up on the JFrame when you run the program.
  7. Remember to switch drivers regularly.

Part 3: Drawing Lines

The next step is to add Mouse interaction, so that you can click and draw pictures!

  1. You will need to have your Painter implement the MouseListener interface so that you can respond to Mouse events. However, the MouseListener 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 the MouseMotionListener interface.

    You can implement multiple interfaces by separating them with a comma. This comes after any inheritance, for example:

    public class MyClass extends Parent implements FirstInterface, SecondInterface
    • There will be a lot of methods you'll need to add--but you won't necessary use them all (many of them will be left empty). But since the Java event system only works with these listeners, we have to promise to include all the methods to become a full-blown listener.
  2. Follow the three steps when implementing a listener:
    1. Modify the class declaration so you implement the interface. 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!).
    2. Register the listener. You'll need to call addMouseListener() and addMouseMotionListener() 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!)
    3. Fill in the required methods. You might start by just printing out a message like "mouse was pressed!" and "mouse was dragged!" Also try printing out the MouseEvent parameters: what kinds of information are you seeing?
  3. Now you need to make it so that you draw on the Graphics2D bufferBrush instance variable whenever the mouse is pressed and/or dragged (think: which of the interface's methods do you need to work with?). Start by just drawing a tiny oval (diameter 1 or 2) at the position where the MouseEvent occurred. 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() and getY() methods of the MouseEvent object to get the x- and y-coordinates of the event's location.
    • Important: you will need to call the repaint() method on your JPanel whenever you've updated the drawing! This will cause Java to call paintComponent(), thereby showing the updated BufferedImage on the screen (since your paintComponent() method draws the BufferedImage).
  4. This is a good time to switch drivers.
  5. 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, we're going to draw a line between the location of successive MouseEvents. This will let us (literally) connect the dots!

  6. To do this, your program will need to remember the location (i.e., the x and y variables) of the previous MouseEvent. You'll need to store this location as instance variable(s).
    • You can use a pair of ints or a Point object.
  7. 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.
    • Make sure to include some comments explaining how this is working (it's not only polite, but it's useful for understanding)!
  8. This can get tricky, but isn't too complicated. Be sure and test your code! Remember to "play computer" and/or use print statements to figure out what is going on when different methods are called.
  9. This is a good time to switch drivers.

Part 4: Changing the Line

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 PainterGUI's buttons being pressed. The methods you need to add and their functions are described below:

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 :)

Submitting

  1. Make sure both of your names are in a JavaDoc comment at the top of the Painter class. If your names aren't on your assignment, we can't give you credit!
  2. Right-click on the project folder, then:
    • If using Linux, select Compress...
    • If using Windows, select Send to and then Zip file
    • If using Mac, select Compress ... items

    This will let you take the selected folder (or files) and generate a new compressed .zip file.

  3. Navigate to the course on Moodle (Lecture Section A), (Lecture Section B). Upload your .zip file to the Lab J Submission page. Remember to click "Save Changes"! You may submit as often as you'd like before the deadline; we will grade the most recent copy.
  4. While you're on Moodle, remember to fill out the Lab J Partner Evaluation. Both partners need to submit evaluations.

Extensions

  1. Add the ability to go into "eraser" mode. This is basically drawing thick lines that are the same color as the background!
  2. For extra complexity, try adding the ability to draw simple shapes (i.e., rectangles and circles). The user might click somewhere to start the shape, then drag to size how big the rectangle should be. Note that you can use the built-in Rectangle and Ellipse2D.Double classes to temporarily store the shape that is being created, so you can easily erase/modify it as the position of the mouse changes. (This can be challenging to get right--it's actually a pretty good option for the final project!)

Grading

This assignment will be graded out of 16 points.