Custom Graphics

Topics

  1. Custom Painting 
  2. Simple 2D Graphics 
  3. A Graphics Example 

1. Custom Painting

(Ref. Java® Tutorial)

So far, we have seen user interface components that display static content. The individual components posessed sufficient knowledge to draw themselves and so we did not have to do anything special beyond creating the components and describing their layout. If a component is obscured by some other window and then uncovered again, it is the job of the window system to make sure that the component is properly redrawn.

There are instances, however, where we will want to change the appearance of a component e.g. we may wish to draw a graph, display an image or even display an animation within the component. This requires the use of custom painting code. The recommended way to implement custom painting is to extend the JPanel class. We will need to be concerned with two methods:

  • The paintComponent() method specifies what the component should draw. We can override this method to draw text, graphics, etc. The paintComponent() method should never be called directly. It will be called indirectly, either because the window system thinks that the component should draw itself or because we have issued a call to repaint().
  • The repaint() method forces the screen to update as soon as possible. It results in a call to the paintComponent() method. repaint() behaves asynchronously i.e. it returns immediately without waiting for the paintComponent() method to complete.

The following code illustrates how custom painting works. A JPanel subclass is used to listen to mouse events and then display a message at the location where the mouse is pressed or released.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class Main {
    public static void main(String[] args0) {
        JFrame frame = new JFrame();
        frame.setSize(400, 400);

        DrawingArea drawingArea = new DrawingArea();
        frame.getContentPane().add(drawingArea);

        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        frame.setVisible(true);
    }
}

class DrawingArea extends JPanel {
    private String mText;
    private static String mStr1 = "The mouse was pressed here!";
    private static String mStr2 = "The mouse was released here!";
    private int miX, miY;

    // The constructor simply registers the drawing area to receive mouse events from itself.
    public DrawingArea() {
        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                miX = e.getX();
                miY = e.getY();
                mText = mStr1;
                repaint();
            }
            public void mouseReleased(MouseEvent e) {
                miX = e.getX();
                miY = e.getY();
                mText = mStr2;
                repaint();
            }
        });
    }

    // The paint method.  This gets called in response to repaint().
    public void paintComponent(Graphics g) {
        super.paintComponent(g);           // This paints the background.
        if (mText != null)
            g.drawString(mText, miX, miY);
    }
}

Note that prior to the introduction of the Swing package, one would override the paint() method to implement custom painting. In Swing applications, however, we override the  paintComponent() method instead. The paintComponent() method will be called by the paint() method in class JComponent. The JComponent's paint() method also implements features such as double buffering, which are useful in animation.

2. Simple 2D Graphics

The paintComponent() method gives us a graphics context, which is an instance of a Graphics subclass. A graphics context bundles information such as the area into which we can draw, the font and color to be used, the clipping region, etc. Note that we do not instantiate the graphics context in our program; in fact the Graphics class itself is an abstract class. The Graphics class provides methods for drawing simple graphics primitives, like lines, rectangles, ovals, arcs and polygons. It also provides methods for drawing text, as we saw above.

This program illustrates how to draw some basic shapes.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class Main {
    public static void main(String[] args0) {
        JFrame frame = new JFrame();
        frame.setSize(400, 400);

        DrawingArea drawingArea = new DrawingArea();
        frame.getContentPane().add(drawingArea);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        frame.setVisible(true);
    }
}

class DrawingArea extends JPanel {
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // Draw some simple geometric primitives.
        g.setColor(Color.red);
        g.drawLine(10, 10, 40, 50);                                            // x1, y1, x2, y2

        g.setColor(Color.green);
        g.drawRect(100, 100, 40, 30);                                        // x, y, width, height

        g.setColor(Color.yellow);
        g.drawOval(100, 200, 30, 50);                                        // x, y, width, height

        g.setColor(Color.blue);
        g.drawArc(200, 200, 50, 30, 45, 90);                             // x, y, width, height, start angle, arc angle

        int x1_points[] = {100, 130, 140, 115, 90};
        int y1_points[] = {300, 300, 340, 370, 340};
        g.setColor(Color.black);
        g.drawPolygon(x1_points, y1_points, x1_points.length);   // x array, y array, length

        int x2_points[] = {300, 330, 340, 315, 290};
        int y2_points[] = {300, 300, 340, 370, 340};
        g.setColor(Color.cyan);
        g.drawPolyline(x2_points, y2_points, x2_points.length);    // x array, y array, length

        g.setColor(Color.orange);
        g.fillRect(300, 100, 40, 30);                                             // x, y, width, height

        g.setColor(Color.magenta);
        g.fill3DRect(300, 200, 40, 30, true);                                // x, y, width, height, raised
    }
}

The Java® 2D API provides a range of advanced capabilities, such as stroking and filling, affine transformations, compositing and transparency.

3. A Graphics Example

Here is a complete program that allows you to interactively define points, lines and polygon using mouse input. This program can be run either as an application or as an applet.

// This is a Java graphics example that can be run either as an applet or as an application.
// Created by Kevin Amaratunga 10/17/1997.  Converted to Swing 10/17/1999.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

// In order to run as an applet, class Geometry must be declared as a public class.  Note that there
// cannot  be more than one public class in a .java file.  Also, the public class must have the same
// same name as the .java file.
 public class Geometry extends JApplet {
    JTextArea mTextArea;
    DrawingArea mDrawingArea;

    public Geometry() {
        // Get the applet's container.
        Container c = getContentPane();

        // Choose a layout manager.  BorderLayout is a straightforward one to use.
        c.setLayout(new BorderLayout());

       // Create a drawing area and add it to the center of the applet.
       mDrawingArea = new DrawingArea(this);
       c.add("Center", mDrawingArea);

       // Create a read only text area to be used for displaying
       // information.  Add it to the bottom of the applet.
       mTextArea = new JTextArea();
       JScrollPane scrollPane = new JScrollPane(mTextArea);
       scrollPane.setPreferredSize(new Dimension(600, 100));
       mTextArea.setEditable(false);
       c.add("South", scrollPane);
    }

    public JTextArea getTextArea() {
        return mTextArea;
    }

    public static void main(String args[]) {
        // Create the applet object.
        Geometry geomApplet = new Geometry();

        // Create a frame.  Then set its size and title.
        JFrame frame = new JFrame();
        frame.setSize(600, 600);
        frame.setTitle(geomApplet.getClass().getName());

        // Make the frame closable.
        WindowListener listener = new WindowAdapter() {
            // An anonymous class that extends WindowAdapter.
            public void windowClosing(WindowEvent e) {
                System.out.println("Window closing");
                System.exit(0);
            }
        };
        frame.addWindowListener(listener);

        // Add the applet to the center of the frame.
        frame.getContentPane().add("Center", geomApplet);

        // Initialize the applet.
        geomApplet.init();

        // Make the frame visible.
        frame.setVisible(true);

        // Start the applet.
        geomApplet.start();
    }
}
 

// The drawing area is the area within all the objects will be drawn.
class DrawingArea extends JPanel implements MouseListener {
    // Parent and child widgets.
    Geometry mGeomApplet;                     // The parent applet.
    JPopupMenu mPopupMenu;                 // Popup menu for creating new objects.

    // Object lists.
    Vector mPointList;                             // List of all Point objects.
    Vector mLineList;                               // List of all Line objects.
    Vector mPolygonList;                         // List of all Polygon objects.

    // Constants that indicate which kind of object (if any) is currently being created.
    static final int NO_OBJECT = 0;
    static final int POINT_OBJECT = 1;
    static final int LINE_OBJECT = 2;
    static final int POLYGON_OBJECT = 3;

    // Miscellaneous state variables.
    int miLastButton = 0;                        // Last button for which an event was received.
    int miAcceptingInput = 0;                  // Type of object (if any) that we are currently creating.
    int miPointsEntered = 0;                   // Number of points entered for this object so far.
    Object mCurrentObject = null;           // The object that we are currently creating.
 

    // DrawingArea constructor.
    DrawingArea(Geometry geomApplet) {
        JMenuItem menuItem;

        mGeomApplet = geomApplet;

        // Set the background color.
        setBackground(Color.white);

        // Register the drawing area to start listening to mouse events.
        addMouseListener(this);

        // Create a popup menu and make it a child of the drawing area, but don't show it just yet.
        mPopupMenu = new JPopupMenu("New Object");
        menuItem = new JMenuItem("Point");
        menuItem.addActionListener(new PointActionListener(this));
        mPopupMenu.add(menuItem);
        menuItem = new JMenuItem("Line");
        menuItem.addActionListener(new LineActionListener(this));
        mPopupMenu.add(menuItem);
        menuItem = new JMenuItem("Polygon");
        menuItem.addActionListener(new PolygonActionListener(this));
        mPopupMenu.add(menuItem);
        add(mPopupMenu);

        // Create the object lists with a reasonable initial capacity.
        mPointList = new Vector(10);
        mLineList = new Vector(10);
        mPolygonList = new Vector(10);
    }
 

    // The paint method.
    public void paintComponent(Graphics g) {
        int i;

        // Paint the background.
        super.paintComponent(g);

        // Draw all objects that are stored in the object lists.
        for (i = 0; i < mPointList.size(); i++) {
            Point point = (Point)mPointList.elementAt(i);
            g.fillRect(point.x-1, point.y-1, 3, 3);
        }

        for (i = 0; i < mLineList.size(); i++) {
            Line line = (Line)mLineList.elementAt(i);
            line.draw(g);
        }

        for (i = 0; i < mPolygonList.size(); i++) {
            Polygon polygon = (Polygon)mPolygonList.elementAt(i);
            int j;

            g.setColor(Color.red);
            g.drawPolygon(polygon);
            g.setColor(Color.black);
            for (j = 0; j < polygon.npoints; j++) {
                g.fillRect(polygon.xpoints[j], polygon.ypoints[j], 3, 3);
            }
        }

        // Draw as much of the current object as available.
        switch (miAcceptingInput) {
        case LINE_OBJECT:
            Line line = (Line)mCurrentObject;
            if (line.mb1 && !line.mb2)
                g.fillRect(line.mEnd1.x-1, line.mEnd1.y-1, 3, 3);
            break;

        case POLYGON_OBJECT:
            Polygon polygon = (Polygon)mCurrentObject;
            int j;
            g.setColor(Color.red);
            g.drawPolyline(polygon.xpoints, polygon.ypoints, polygon.npoints);
            g.setColor(Color.black);
            for (j = 0; j < polygon.npoints; j++) {
                g.fillRect(polygon.xpoints[j], polygon.ypoints[j], 3, 3);
            }
            break;

        default:
            break;
        }

        // Draw some text at the top of the drawing area.
        int w = getSize().width;
        int h = getSize().height;
        g.drawRect(0, 0, w - 1, h - 1);
        g.setFont(new Font("Helvetica", Font.PLAIN, 15));
        g.drawString("Drawing area", (w - g.getFontMetrics().stringWidth("Drawing area"))/2, 10);
    }
 

    // The next five methods are required, since we implement the
    // MouseListener interface.  We are only interested in mouse pressed
    // events.
    public void mousePressed(MouseEvent e) {
        int iX = e.getX();  // The x and y coordinates of the
        int iY = e.getY();  // mouse event.
        int iModifier = e.getModifiers();

        if ((iModifier & InputEvent.BUTTON1_MASK) != 0) {
            miLastButton = 1;

            // If we are currently accepting input for a new object,
            // then add the current point to the object.
            if (miAcceptingInput != NO_OBJECT)
                addPointToObject(iX, iY);
        }
        else if ((iModifier & InputEvent.BUTTON2_MASK) != 0) {
            miLastButton = 2;

        }
        else if ((iModifier & InputEvent.BUTTON3_MASK) != 0) {
            miLastButton = 3;

            if (miAcceptingInput == NO_OBJECT) {
                // Display the popup menu provided we are not accepting
                // any input for a new object.
                mPopupMenu.show(this, iX, iY);
            }
            else if (miAcceptingInput == POLYGON_OBJECT) {
                // If current object is a polygon, finish it.
                mPolygonList.addElement(mCurrentObject);
                String str = "Finished creating polygon object.\n";
                mGeomApplet.getTextArea().append(str);
                mGeomApplet.repaint();
                miAcceptingInput = NO_OBJECT;
                miPointsEntered = 0;
                mCurrentObject = null;
            }
        }
    }

    public void mouseClicked(MouseEvent e) {}

    public void mouseEntered(MouseEvent e) {}

    public void mouseExited(MouseEvent e) {}

    public void mouseReleased(MouseEvent e) {}

    public void getPointInput() {
        miAcceptingInput = POINT_OBJECT;
        mCurrentObject = (Object)new Point();
        mGeomApplet.getTextArea().append("New point object: enter point.\n");
    }

    public void getLineInput() {
        miAcceptingInput = LINE_OBJECT;
        mCurrentObject = (Object)new Line();
        mGeomApplet.getTextArea().append("New line: enter end points.\n");
    }

    public void getPolygonInput() {
        miAcceptingInput = POLYGON_OBJECT;
        mCurrentObject = (Object)new Polygon();
        mGeomApplet.getTextArea().append("New polygon: enter vertices ");
        mGeomApplet.getTextArea().append("(click right button to finish).\n");
    }

    void addPointToObject(int iX, int iY) {
        String str;

        miPointsEntered++;
        switch (miAcceptingInput) {
        case POINT_OBJECT:
            str = "Point at (" + iX + "," + iY + ")\n";
            mGeomApplet.getTextArea().append(str);
            Point point = (Point)mCurrentObject;
            point.x = iX;
            point.y = iY;
            mPointList.addElement(mCurrentObject);
            str = "Finished creating point object.\n";
            mGeomApplet.getTextArea().append(str);
            mGeomApplet.repaint();
            miAcceptingInput = NO_OBJECT;
            miPointsEntered = 0;
            mCurrentObject = null;
            break;

        case LINE_OBJECT:
            if (miPointsEntered <= 2) {
                str = "End " + miPointsEntered + " at (" + iX + "," + iY + ")";
                str += "\n";
                mGeomApplet.getTextArea().append(str);
            }
            Line line = (Line)mCurrentObject;
            if (miPointsEntered == 1) {
                line.setEnd1(iX, iY);
                mGeomApplet.repaint();
            }
            else {
                if (miPointsEntered == 2) {
                    line.setEnd2(iX, iY);
                    mLineList.addElement(mCurrentObject);
                    str = "Finished creating line object.\n";
                    mGeomApplet.getTextArea().append(str);
                    mGeomApplet.repaint();
                }
                miAcceptingInput = NO_OBJECT;
                miPointsEntered = 0;
                mCurrentObject = null;
            }
            break;

        case POLYGON_OBJECT:
            str = "Vertex " + miPointsEntered + " at (" + iX + "," + iY + ")";
            str += "\n";
            mGeomApplet.getTextArea().append(str);
            Polygon polygon = (Polygon)mCurrentObject;
            polygon.addPoint(iX, iY);
            mGeomApplet.repaint();
            break;

        default:
            break;
        }                           // End switch.
    }
}
 

// Action listener to create a new Point object.
class PointActionListener implements ActionListener {
    DrawingArea mDrawingArea;

    PointActionListener(DrawingArea drawingArea) {
        mDrawingArea = drawingArea;
    }

    public void actionPerformed(ActionEvent e) {
        mDrawingArea.getPointInput();
    }
}
 

// Action listener to create a new Line object.
class LineActionListener implements ActionListener {
    DrawingArea mDrawingArea;

    LineActionListener(DrawingArea drawingArea) {
        mDrawingArea = drawingArea;
    }

    public void actionPerformed(ActionEvent e) {
        mDrawingArea.getLineInput();
    }
}
 

// Action listener to create a new Polygon object.
class PolygonActionListener implements ActionListener {
    DrawingArea mDrawingArea;

    PolygonActionListener(DrawingArea drawingArea) {
        mDrawingArea = drawingArea;
    }

    public void actionPerformed(ActionEvent e) {
        mDrawingArea.getPolygonInput();
    }
}
 

// A line class.
class Line {
    Point mEnd1, mEnd2;
    boolean mb1, mb2;

    Line() {
        mb1 = mb2 = false;
        mEnd1 = new Point();
        mEnd2 = new Point();
    }

    void setEnd1(int iX, int iY) {
        mEnd1.x = iX;
        mEnd1.y = iY;
        mb1 = true;
    }

    void setEnd2(int iX, int iY) {
        mEnd2.x = iX;
        mEnd2.y = iY;
        mb2 = true;
    }

    void draw(Graphics g) {
        g.fillRect(mEnd1.x-1, mEnd1.y-1, 3, 3);
        g.fillRect(mEnd2.x-1, mEnd2.y-1, 3, 3);
        g.setColor(Color.green);
        g.drawLine(mEnd1.x, mEnd1.y, mEnd2.x, mEnd2.y);
        g.setColor(Color.black);
    }
}