Basic Java Graphics

The Java platform has dozens of classes for graphics programming. Graphics programming requires an understanding of components, event handling, and painting, among other things, so be ready for a bit of a learning curve.

A Simple Drawing

Our first program draws a Do Not Enter sign:

donotenterapp.png

DoNotEnterSign.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

/**
 * A panel maintaining a picture of a Do Not Enter sign.
 */
public class DoNotEnterSign extends JPanel {
    private static final long serialVersionUID = 7148504528835036003L;

    /**
     * Called by the runtime system whenever the panel needs painting.
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        var center = new Point(getWidth() / 2, getHeight() / 2);
        var radius = Math.min(getWidth() / 2, getHeight() / 2) - 5;
        var diameter = radius * 2;
        var innerRadius = (int) (radius * 0.9);
        var innerDiameter = innerRadius * 2;
        var barWidth = (int) (innerRadius * 1.4);
        var barHeight = (int) (innerRadius * 0.35);

        g.setColor(Color.WHITE);
        g.fillOval(center.x - radius, center.y - radius, diameter, diameter);
        g.setColor(Color.RED);
        g.fillOval(center.x - innerRadius, center.y - innerRadius, innerDiameter,
                innerDiameter);
        g.setColor(Color.WHITE);
        g.fillRect(center.x - barWidth / 2, center.y - barHeight / 2, barWidth,
                barHeight);
    }

    /**
     * A little driver in case you want a stand-alone application.
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            var panel = new DoNotEnterSign();
            panel.setBackground(Color.GREEN.darker());
            var frame = new JFrame("A simple graphics program");
            frame.setSize(400, 300);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.getContentPane().add(panel, BorderLayout.CENTER);
            frame.setVisible(true);
        });
    }
}

Let’s break it down, and learn the basics as we go:

Exercise: Build and run this application. Resize the window, making it tall and thin as well as short and fat. Watch the sizing of the drawing fit the window dynamically.

Sketching

Our first example was just a static drawing. Time to learn about interactive computer graphics. This means learning about events.Here is a little canvas you can sketch on, with a little main method so it can be run as an application:

trivialsketcher.png

SimpleSketchPanel.java
import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.Graphics;

import java.util.ArrayList;
import java.util.List;

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class SimpleSketchPanel extends JPanel {
    private static final long serialVersionUID = -3630443364990545965L;

    private List<List<Point>> curves = new ArrayList<>();

    public SimpleSketchPanel() {
        // Register event listeners on construction of the panel.
        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                var newCurve = new ArrayList<Point>();
                newCurve.add(new Point(e.getX(), e.getY()));
                curves.add(newCurve);
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseDragged(MouseEvent e) {
                curves.get(curves.size() - 1).add(new Point(e.getX(), e.getY()));
                repaint(0, 0, getWidth(), getHeight());
            }
        });
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (var curve: curves) {
            var previousPoint = curve.get(0);
            for (var point: curve) {
                g.drawLine(previousPoint.x, previousPoint.y, point.x, point.y);
                previousPoint = point;
            }
        }
    }

    /**
     * A little driver in case you want to sketch as a stand-alone application.
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            var frame = new JFrame("Simple Sketching Program");
            frame.getContentPane().add(new SimpleSketchPanel(), BorderLayout.CENTER);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(400, 300);
            frame.setVisible(true);
        });
    }
}

The panel saves the state of the drawing as a list of curves, where each curve is a list of points. Pressing the mouse button starts a new curve; dragging the mouse adds the current location of the mouse to the current curve. The entire drawing is rendered when needed, as usual, in paintComponent().

Notice the call to repaint when dragging. This tells Java to redraw the panel as soon as it can.

Exercise: Remove the repaint call. Try out the application now. What did you notice? Explain why you think this new behavior occurred.

Graphical User Interfaces

Here is a program with a text area and a couple of buttons. We’ll cover it in class.

capitalizer.png

Capitalizer.java
import java.awt.BorderLayout;
import javax.swing.SwingUtilities;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Capitalizer {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            var initialText = "¿Hablas español o inglés o ambos?";
            var area = new JTextArea(initialText, 8, 50);

            var lowerCaseButton = new JButton("To Lower Case");
            var upperCaseButton = new JButton("To Upper Case");
            lowerCaseButton.addActionListener(e -> area.setText(area.getText().toLowerCase()));
            upperCaseButton.addActionListener(e -> area.setText(area.getText().toUpperCase()));

            var buttonPanel = new JPanel();
            buttonPanel.add(lowerCaseButton);
            buttonPanel.add(upperCaseButton);

            var frame = new JFrame("Capitalizer");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new JScrollPane(area), BorderLayout.CENTER);
            frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
            frame.pack();
            frame.setVisible(true);
        });
    }
}

Buttons are boring. It’s so much better to see things happen while you type. Here is a program that reacts for each change to its input field.

changer.png

Changer.java
import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;

/**
 * A silly little application that lets you type in an amount of cents in a text
 * field and see a report of how to make that amount with the fewest number of
 * pennies, dimes, nickels and quarters.
 */
public class Changer extends JFrame {
    private static final long serialVersionUID = 1912881839758209062L;

    private JTextField amountField = new JTextField(12);
    private Document amountText = amountField.getDocument();
    private JTextArea report = new JTextArea(8, 40);

    public Changer() {
        var topPanel = new JPanel();
        topPanel.add(new JLabel("Amount:"));
        topPanel.add(amountField);
        getContentPane().add(topPanel, BorderLayout.NORTH);
        getContentPane().add(new JScrollPane(report), BorderLayout.CENTER);

        setBackground(Color.LIGHT_GRAY);
        report.setEditable(false);

        amountText.addDocumentListener(new DocumentListener() {
            public void changedUpdate(DocumentEvent e) {
                updateReport();
            }

            public void insertUpdate(DocumentEvent e) {
                updateReport();
            }

            public void removeUpdate(DocumentEvent e) {
                updateReport();
            }
        });
    }

    void updateReport() {
        try {
            var amount = Integer.parseInt(amountText.getText(0, amountText.getLength()));
            report.setText("To make " + amount + " cents, use\n");
            report.append(amount / 25 + " quarters\n");
            amount %= 25;
            report.append(amount / 10 + " dimes\n");
            amount %= 10;
            report.append(amount / 5 + " nickels\n");
            amount %= 5;
            report.append(amount + " pennies\n");
        } catch (NumberFormatException e) {
            report.setText("Not an integer or out of range");
        } catch (Exception e) {
            report.setText(e.toString());
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            var frame = new Changer();
            frame.setTitle("Changer");
            frame.pack();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

Java2D

Java2D is the informal name given to the parts of the Java standard library dealing with drawing and printing 2D lines, shapes, images, gradients, and textures, together with fun options like compositing, filtering, transforming these objects.

See the Java2D trail in the official tutorial; it’s pretty good.

Java 3D Graphics

Three-dimensional graphics are not part of the Java standard libraries, but you can use Java OpenGL (known as JOGL), or check out a community project called Java 3D that provides a nice scene-graph API that runs on top of OpenGL.