Graphics: Drawing with Java's Libraries

The graphics appendix Chapter 15 tells you to read first: a Canvas you extend, a paint method the window system calls, a coordinate system whose origin is in the upper-left corner, bounding boxes, RGB colours, and a Hidden Mickey drawn with three ovals and a Rectangle. Follows Think Java 2e, Chapter C (Graphics), Sections C.1-C.5, pp. 319-324, cross-referenced against Java SE 21 API — java.awt.Graphics.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Graphics: Drawing with Java's Libraries

Title

Think Java 2e · Chapter C · Graphics

Sections C.1-C.5 · pp. 319-324

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter C (Graphics), Sections C.1-C.5, pp. 319-324. Everything on these slides can be checked against those pages.

1. Write a class that extends Canvas and overrides paint to draw a shape.

2. Explain what a JFrame does and why the program keeps running after main returns.

3. Contrast Java's graphical coordinates with Cartesian coordinates.

4. Use a bounding box to specify where a shape is drawn.

5. Choose a colour by name or by RGB components.

6. Use a Rectangle and translate to position shapes relative to one another.

3. Retrieve before you read

Warm-up

Three things, and one of them you may already have met out of order.

Discussion prompt

From Lesson 14a: what does extends do? From Lesson 15a: which method does the window system call, and what does it pass in? And from Lesson 10a: what are a Rectangle's attributes?

Hint: paint, a Graphics, and four ints.

Answer:

extends makes a subclass with the superclass's attributes and methods. The window system calls paint, passing a Graphics object. And a Rectangle has x, y, width and height, plus a translate method.

If you haven't yet read Appendix C, you might want to read it now — that is Chapter 15's opening sentence. This lesson is that appendix, and everything in Chapters 15 to 17 rests on it.

4. Two classes and a window

Concept

The Java library includes the package java.awt for drawing 2D graphics. AWT stands for Abstract Window Toolkit. Drawing takes three things, and only one of them is yours.

Figure (svg): A pipeline from a JFrame window containing a Canvas whose paint method receives a Graphics object

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter C (Graphics), Sections C.1-C.5, pp. 319-324 — Appendix C begins on printed page 319.

5. Creating graphics

Section

Section C.1

6. A canvas, and something to draw with

Concept

There are several ways to create graphics in Java; the simplest way is to use java.awt.Canvas and java.awt.Graphics.

AWT — The Abstract Window Toolkit, a Java package for creating graphical user interfaces.

classiswho creates it
Canvasa blank rectangular area of the screen onto which the application can drawyou
Graphicsbasic drawing methods such as drawLine, drawRect and drawStringthe system
JFramethe window that will contain the canvasyou

We are only going to scratch the surface of graphics programming. The three classes above are enough for everything in Chapters 15 to 17 — and the split between what you create and what the system creates is the first thing worth noticing.

7. A complete graphics program

Picture it

Here is an example program that draws a circle by using the fillOval method. Twenty lines, and most of them are window setup.

Figure (svg): A four hundred by four hundred canvas with a filled circle occupying the middle two hundred pixels

If you run this code, you should see a black circle on a gray background. Black because that is the default colour, and grey because that is a Canvas's default background — both changeable, as Section C.2 shows.

8. What main does

Worked example

In the main method, we do the following — three steps, and each is two or three lines.

import java.awt.Canvas;
import java.awt.Graphics;
import javax.swing.JFrame;

public class Drawing extends Canvas {

    public static void main(String[] args) {
        JFrame frame = new JFrame("My Drawing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Drawing drawing = new Drawing();
        drawing.setSize(400, 400);
        frame.add(drawing);
        frame.pack();
        frame.setVisible(true);
    }

    public void paint(Graphics g) {
        g.fillOval(100, 100, 200, 200);
    }
}
linedoes
new JFrame("My Drawing")creates the window
setDefaultCloseOperation(EXIT_ON_CLOSE)closing the window ends the program
new Drawing()creates the canvas
drawing.setSize(400, 400)sets its width and height
frame.add(drawing)puts the canvas in the window
frame.pack()resizes the frame to fit the canvas
frame.setVisible(true)displays it on the screen

Make the window.

Why: Create a JFrame object, which is the window that will contain the canvas.

Make the canvas and add it.

Why: Create a Drawing object (which is the canvas), set its width and height, and add it to the frame.

Size and show.

Why: Pack the frame (resize it) to fit the canvas, and display it on the screen.

Note where setSize came from.

Why: The Drawing class extends Canvas, so it has all the methods provided by Canvas, including setSize.

Verify: Notice that pack comes after setSize, not before.

Why: Pack resizes the frame to fit its contents, so the canvas must already know its own size. Reversing the two lines produces a window sized for a canvas of zero — a symptom worth recognising, because nothing reports an error.

9. Where does the Graphics object come from?

Prediction

You never write new Graphics().

public void paint(Graphics g) {
    g.fillOval(100, 100, 200, 200);
}
objectcreated by
the JFrameyou
the Canvasyou
the Graphics?

Predict first

Who creates it?

  • The system — it is created with the Canvas and passed to paint
  • You, with new Graphics()
  • The JFrame constructor returns one
  • fillOval creates it

Correct: The system — it is created with the Canvas and passed to paint

Why: You don't have to create the Graphics object; it gets created when you create the Canvas, and it gets passed as an argument to paint. Lesson 17a adds the reason it could not work otherwise: Graphics is an abstract class, so new Graphics() would not compile.

10. Why the program does not end

Concept

main finishes in seven lines and the window stays on screen. That is worth an explanation.

// main returns here
}

// ...and the program keeps running
after main returnsstate
the windowvisible
the programstill running
what ends itclosing the window
what that callsSystem.exit

The application doesn't end after the main method returns; instead, it waits for the JFrame to close. When the JFrame closes, it calls System.exit, which ends the program. That is what setDefaultCloseOperation(EXIT_ON_CLOSE) arranged — without it the window would close and the program would keep running invisibly.

11. Calling paint yourself

Trap

The trap

paint needs a Graphics object only the system can supply.

Drawing drawing = new Drawing();
drawing.paint(???);        // where would the Graphics come from?
detail
paint takes a Graphicsrepresenting the actual drawing surface
you cannot make a valid oneonly the window system can
and timing mattersthe surface must be ready

You don't have to create the Graphics object; it gets created when you create the Canvas, and it gets passed as an argument to paint. Manufacturing one yourself means drawing somewhere the window is not showing.

The fix

Override paint and let the system call it.

public void paint(Graphics g) {
    g.fillOval(100, 100, 200, 200);
}

// and to ask for a redraw later:
drawing.repaint();
you writethe system does
paintcalls it when the canvas needs drawing
repaint()you call this to ask

Once the frame is visible, the paint method is called whenever the canvas needs to be drawn; for example, when the window is moved or resized. This is the first framework callback in the book, and Lesson 15a relies on it entirely.

12. Who creates what?

Definition probe

Three objects, two creators.

Sort into buckets

Sort each object.

your code creates it
the JFrame; the Canvas subclass
the system creates it
the Graphics object; the window's title bar
you
You write new for it in main — the window and the canvas are yours to construct and configure.
sys
Supplied by the window system, either passed to your paint method or drawn on its behalf.

13. Set up the window

Fill the middle

Size the canvas, then fit the frame to it.

Fill in the blanks

Drawing drawing = new Drawing();
drawing.setSize(400, 400);
frame.add(drawing);
frame.pack();
frame.setVisible(true);

Why: setSize is inherited from Canvas and gives the drawing area its dimensions; pack then resizes the frame to fit whatever it contains. The order matters — packing before the canvas knows its size produces a window with nothing in it, and no error.

14. Why extend Canvas rather than hold one?

Explain it to yourself

Composition would also work.

Discussion prompt

Drawing extends Canvas rather than having a Canvas as a field. Apply Lesson 14b's sentence test, and say what the choice buys.

Hint: Which method has to be overridden?

Answer:

A Drawing is a Canvas — it is a thing that appears on screen and gets drawn on, so the sentence is plainly true.

And only a subclass can override paint. That method is how the window system reaches your drawing code at all; holding a Canvas as a field would give you no way to supply it.

Inheritance here is for the hook, not only for the methods — Lesson 17a's specialization, and the reason Canvas was explicitly designed to be extended.

15. Graphical coordinates

Section

Section C.2

16. The origin is in the wrong corner

Concept

You are probably used to Cartesian coordinates, where x and y values can be positive or negative. In contrast, Java uses a coordinate system where the origin is in the upper-left corner. That way, x and y can always be positive integers.

Figure (svg): Two coordinate systems side by side: Cartesian with the origin in the middle and Java with it in the upper-left

coordinate — A value that specifies a location in a 2D graphical window.

pixel — The unit in which coordinates are measured.

Graphical coordinates are measured in pixels; each pixel corresponds to a dot on the screen. Integers, not real numbers — you cannot draw at x = 3.5, which is why Lesson 17a's polygon vertices are rounded.

17. Where the downward y shows up

Notation

Every graphics chapter in this book has a line that only makes sense because y increases downward.

Annotate

  • Row 0 is at the top, so a grid's row index maps naturally to a y coordinate.
  • North decreases y. Langton's ant would walk south if this were Cartesian.
  • Up is negative. Every arrow-key handler you will ever write has this line, and it looks wrong every time.
  • Nothing warns you. A sign error draws a perfectly good picture, upside down.
  • The reason is practical: coordinates are pixel positions on a screen that is scanned from the top, and negative positions would be off-screen anyway.

One convention, three chapters of consequences. It is the single most common source of why is my drawing inverted — and the only cure is knowing it in advance.

18. The bounding box

Worked example

The previous example used fillOval, which has the following signature. Four ints, and they do not describe the oval.

/**
 * Fills an oval bounded by the specified rectangle with
 * the current color.
 */
public void fillOval(int x, int y, int width, int height)
parametermeans
xthe left edge of the bounding box
ythe top edge of the bounding box
widthhow wide the box is
heighthow tall
the box itselfnot drawn

Four parameters describe a rectangle.

Why: The four parameters specify a bounding box, which is the rectangle in which the oval is drawn.

x and y are a corner, not a centre.

Why: x and y specify the location of the upper-left corner of the bounding box.

The box is invisible.

Why: The bounding box itself is not drawn.

A square box gives a circle.

Why: fillOval(100, 100, 200, 200) — equal width and height.

Verify: Work out where the circle's centre is: (100 + 100, 100 + 100) = (200, 200).

Why: The centre is not a parameter and has to be computed, which is the opposite of how you would describe a circle mathematically. Every AWT drawing method uses the bounding-box convention, so it is worth getting used to rather than fighting.

19. Which way is positive y?

Prediction

Java's origin is in the upper-left corner.

// drawing something 50 pixels "up" from y = 100
systempositive y
Cartesianup
Java graphics?

Predict first

In Java graphics, increasing y moves the drawing which way?

  • Down the screen
  • Up the screen
  • To the right
  • It depends on the canvas

Correct: Down the screen

Why: Java uses a coordinate system where the origin is in the upper-left corner. That way, x and y can always be positive integers. So moving up the screen means decreasing y — which is why Langton's ant does ypos -= 1 to go north and a sprite sets dy = -5 for the up arrow.

20. Colour

Concept

To choose the color of a shape, invoke setColor on the Graphics object.

g.setColor(Color.RED);

// the named constants:
// BLACK  BLUE     CYAN    DARKGRAY  GRAY   LIGHTGRAY
// GREEN  MAGENTA  ORANGE  PINK      WHITE  YELLOW

// or make your own:
Color purple = new Color(128, 0, 128);

canvas.setBackground(Color.WHITE);
detail
setColordetermines the colour of everything drawn afterward
Color.REDa constant provided by the Color class
new Color(128, 0, 128)red, green and blue components
each component0 (darkest) to 255 (lightest)
(0, 0, 0) and (255, 255, 255)black and white

RGB — A color model based on adding red, green, and blue light.

The setColor method determines the color of everything that gets drawn afterward — Graphics is stateful, which is why Lesson 15a's Cell.draw sets the colour immediately before each shape rather than once at the start. To use Color.red you have to import java.awt.Color.

21. Treating x and y as the centre

Trap

The trap

fillOval's x and y are a corner, not the middle.

// wanting a circle of radius 100 centred at (200, 200):
g.fillOval(200, 200, 200, 200);   // wrong

// this draws a circle centred at (300, 300)
callbox corneractual centre
fillOval(200, 200, 200, 200)(200, 200)(300, 300)
fillOval(100, 100, 200, 200)(100, 100)(200, 200)

The shape appears, in the wrong place, by exactly half its size. A displacement of half the width is the signature of this mistake — and it is much harder to see with an irregular shape than a circle.

The fix

Subtract the radius to get the corner.

int cx = 200, cy = 200, r = 100;
g.fillOval(cx - r, cy - r, 2 * r, 2 * r);
fromto
centre (cx, cy) and radius rcorner (cx − r, cy − r)
radius rwidth and height 2r

Convert once, at the point of drawing. Keeping your own model in centres and radii — which is how you think about circles — and converting only in the draw method is cleaner than storing corners everywhere.

22. Where is this circle centred?

Prediction

The parameters are a bounding box.

g.fillOval(100, 100, 200, 200);
parametervalue
x, y — the box corner(100, 100)
width, height200, 200

Predict first

Where is the centre of the circle?

  • (200, 200)
  • (100, 100)
  • (300, 300)
  • (150, 150)

Correct: (200, 200)

Why: x and y specify the location of the upper-left corner of the bounding box, so the centre is half the width and half the height further along: (100 + 100, 100 + 100). The centre is never a parameter in AWT — you compute it.

23. Named colour or RGB?

Definition probe

Two ways to specify a colour.

Sort into buckets

Sort each.

a Color constant
Color.RED; Color.LIGHTGRAY
constructed from components
new Color(128, 0, 128); a colour not in the list
named
One of the constants the Color class provides — twelve of them, covering the common cases.
rgb
Built from red, green and blue components, each 0 to 255, which is how you get anything the constants do not cover.

24. What is Color(255, 255, 255)?

Edge cases

Each component runs from 0 to 255.

Discussion prompt

RGB is described as a color model based on adding red, green, and blue light. What do (0, 0, 0) and (255, 255, 255) give, and why does that surprise anyone who has mixed paint?

Hint: Adding light, not pigment.

Answer:

(0, 0, 0) is black and (255, 255, 255) is white — no light at all, and all three at full strength.

That is backwards from paint, where mixing every colour gives something close to black. Paint subtracts light; a screen adds it.

Each value is an integer in the range 0 (darkest) to 255 (lightest) — 256 levels per channel, which is one byte each and about sixteen million colours in total. The range is not arbitrary; it is what fits in eight bits.

25. An example drawing

Section

Section C.3

26. Three ovals make a Mickey

Concept

Suppose we want to draw a Hidden Mickey, which is an icon that represents Mickey Mouse. We can use the oval we just drew as the face, and then add two ears.

public void boxOval(Graphics g, Rectangle bb) {
    g.fillOval(bb.x, bb.y, bb.width, bb.height);
}
detail
the parametera Rectangle rather than four ints
bb.x, bb.ythe corner
bb.width, bb.heightthe size
what it drawsan oval filling that box

To make the code more readable, let's use Rectangle objects to represent bounding boxes. Here's a method that takes a Rectangle and invokes fillOval. One object instead of four loose integers — which is Lesson 10a's Rectangle finally earning its place.

27. The finished drawing

Picture it

One large oval and two smaller ones, positioned relative to it.

Figure (svg): A large circle with two smaller circles at its upper left and upper right, forming a Mickey Mouse silhouette

The first line draws the face. The next three lines create a smaller rectangle for the ears. Every position is computed from the face's bounding box, so the whole figure scales together.

28. Positioning the ears

Worked example

We translate the rectangle up and left for the first ear, then to the right for the second ear.

public void mickey(Graphics g, Rectangle bb) {
    boxOval(g, bb);

    int hx = bb.width / 2;
    int hy = bb.height / 2;
    Rectangle half = new Rectangle(bb.x, bb.y, hx, hy);

    half.translate(-hx / 2, -hy / 2);
    boxOval(g, half);

    half.translate(hx * 2, 0);
    boxOval(g, half);
}
stephalf's positiondraws
created at bb.x, bb.ythe face's corner—
translate(−hx/2, −hy/2)up and leftthe left ear
translate(hx * 2, 0)right by a full face widththe right ear

Draw the face first.

Why: boxOval(g, bb); — the whole bounding box.

Make a half-size rectangle.

Why: hx and hy are half the width and height; half starts at the face's own corner.

Move it up and left, and draw.

Why: half.translate(-hx / 2, -hy / 2); — note that up is negative y.

Move it right and draw again.

Why: half.translate(hx * 2, 0); — the same object, moved, not a new one.

Verify: Trace half's x: it starts at bb.x, moves to bb.x - hx/2, then to bb.x + hx*2 - hx/2.

Why: The same Rectangle object is reused for both ears — Lesson 10a's mutable object, translated between draws. That works because fillOval reads the values immediately; the ear is already on the canvas before the rectangle moves again.

29. What does translate do?

Prediction

The Rectangle from Lesson 10a.

Rectangle half = new Rectangle(100, 100, 50, 50);
half.translate(-25, -25);
beforeafter
x = 100, y = 100?

Predict first

Where is the rectangle now?

  • x = 75, y = 75 — it moved up and left
  • x = 125, y = 125
  • x = −25, y = −25
  • unchanged; translate returns a new rectangle

Correct: x = 75, y = 75 — it moved up and left

Why: translate adds its arguments to the rectangle's x and y, modifying the object in place — Lesson 10a's mutable Rectangle. Negative values move left and up, since y increases downward.

30. Everything relative to the box

Concept

Not one coordinate in mickey is a literal. Every position is computed from the bounding box it was given.

quantitycomputed asso if bb doubles
hxbb.width / 2doubles
the ear sizehx by hydoubles
the offset up and lefthx/2, hy/2doubles
the gap between earshx * 2doubles

The whole figure scales with its bounding box, which is what makes Exercise C.2 possible: drawing ears on the ears, and ears on those ears is a recursive call with a smaller Rectangle, and none of the arithmetic changes.

31. Hard-coding positions

Trap

The trap

Literal coordinates only work at one size.

public void mickey(Graphics g) {
    g.fillOval(100, 100, 200, 200);   // face
    g.fillOval(50,  50,  100, 100);   // left ear
    g.fillOval(250, 50,  100, 100);   // right ear
}
wantwith literals
a bigger Mickeyrewrite six numbers
one somewhere elserewrite six numbers
recursive earsimpossible
is it correct today?yes

It draws the right picture. It draws exactly one picture — and Exercise C.2, which asks for ears all the way down to three pixels wide, cannot be built on it at all.

The fix

Take a bounding box and compute everything from it.

public void mickey(Graphics g, Rectangle bb) {
    boxOval(g, bb);
    int hx = bb.width / 2;
    int hy = bb.height / 2;
    ...
}
parameterwhat it lets you change
bb.x, bb.ywhere the figure sits
bb.width, bb.heighthow big it is
neitherthe shape, which stays the same

You should have to add or modify only a few lines of code — that is Exercise C.2's hint, and it is only true because every position is relative. Parameterising the box is Lesson 5b's generalisation, applied to a drawing.

32. How big is each ear?

Prediction

hx and hy are half the face.

int hx = bb.width / 2;
int hy = bb.height / 2;
Rectangle half = new Rectangle(bb.x, bb.y, hx, hy);
faceear
200 by 200?

Predict first

For a 200 by 200 face, how big is each ear?

  • 100 by 100
  • 200 by 200
  • 50 by 50
  • 400 by 400

Correct: 100 by 100

Why: hx and hy are half the face's width and height, and the ear rectangle uses them as its own width and height. Everything is relative to the bounding box, which is what lets the same method draw a Mickey of any size.

33. Draw an oval in a box

Fill the middle

A Rectangle carries all four values.

Fill in the blanks

public void boxOval(Graphics g, Rectangle bb) fillOval}(bb.x, bb.y, bb.width, bb.height);
}

Why: The Rectangle's four public attributes map exactly onto fillOval's four parameters, which is why passing one object instead of four loose ints makes the calling code so much more readable — and why translate can then reposition the whole box in one call.

34. Could mickey draw itself recursively?

Counterexample

Exercise C.2: draw ears on the ears, and ears on those ears, and more ears all the way down.

Discussion prompt

What would you change in mickey to make it recursive, and what base case would you need?

Hint: The hint says only a few lines.

Answer:

Replace each boxOval(g, half) with mickey(g, half) — the ear becomes a whole Mickey, which draws its own ears, and so on.

The base case comes from the exercise itself: stop when the box is smaller than three pixels wide. Without it, the halving continues until the rectangle has zero size and the recursion never ends — Lesson 8a's rule exactly.

It works only because every position was computed from the bounding box. Hard-coded coordinates could not be scaled down, which is why the parameterisation earns its keep here and not merely on principle.

35. Reading the Graphics class

Section

Sections C.1-C.2

36. A stateful object with many methods

Concept

The Graphics class provides basic drawing methods such as drawLine, drawRect, and drawString. The appendix uses four of them and points you at the documentation for the rest.

methoddraws
fillOval(x, y, w, h)a filled oval in that bounding box
drawRect(x, y, w, h)the outline of a rectangle
fillRect(x, y, w, h)a filled rectangle
drawLine(x1, y1, x2, y2)a line between two points
drawString(s, x, y)text
setColor(c)changes the colour of everything after it

**You can read about the other methods in the documentation, which you can find by doing a web search for Java Canvas.** Lesson B is about exactly that skill — and the fill/draw prefix pattern makes half the class guessable once you have seen two of them.

37. Graphics has state

Notation

setColor is not a parameter to anything. It changes the object, and stays changed.

Annotate

  • One call affects every later call, until the next setColor.
  • Which is why Lesson 15a's Cell.draw sets the colour twice — once for the fill and once for the light-gray border.
  • And why order matters. Setting the colour after drawing has no effect on what was drawn.
  • A shared Graphics object carries state between objects too. If one Actor leaves the colour red, the next one inherits it unless it sets its own.
  • The habit that follows: set the colour immediately before you draw, never once at the top.

Stateful APIs are common in graphics and rare elsewhere in this book. The rule they demand is simple — set what you need, when you need it — and forgetting it produces drawings in mysteriously wrong colours.

38. Fill against draw

Worked example

Most shapes come in two versions, and the prefix tells you which is which.

g.fillRect(10, 10, 100, 50);   // a solid rectangle
g.drawRect(10, 10, 100, 50);   // just the outline

g.fillOval(10, 10, 100, 50);   // a solid ellipse
g.drawOval(10, 10, 100, 50);   // just the outline
prefixmeansexample
fillthe interior is paintedfillOval, fillRect
drawonly the outlinedrawOval, drawRect, drawLine
neithertext and imagesdrawString, drawImage

Same parameters, different result.

Why: The bounding box is identical; only the prefix changes.

fill paints the inside.

Why: Which is what Lesson 15a's Cell.draw uses for a live cell.

draw outlines it.

Why: Which is what the same method uses for the light-gray border.

Both use the current colour.

Why: So the two lines in Cell.draw need a setColor between them.

Verify: Look back at Lesson 15a's Cell.draw: fillRect for the interior, then setColor, then drawRect for the border.

Why: Four lines that use every idea in this appendix: a bounding box inset by a pixel, a stateful colour changed between calls, and the fill/draw distinction — which is why Chapter 15 tells you to read this appendix first.

39. What colour is the second shape?

Prediction

Graphics is stateful.

g.setColor(Color.RED);
g.fillOval(10, 10, 50, 50);
g.fillRect(70, 10, 50, 50);
callcurrent colour
setColor(RED)red
fillOvalred
fillRect?

Predict first

What colour is the rectangle?

  • Red — setColor affects everything drawn afterward
  • Black — the default returns after each shape
  • Gray
  • It does not compile without a second setColor

Correct: Red — setColor affects everything drawn afterward

Why: The setColor method determines the color of everything that gets drawn afterward. The colour is state on the Graphics object, not an argument to each shape — which is why Cell.draw calls setColor twice, once before the fill and once before the border.

40. What paint is called for

Concept

Once the frame is visible, the paint method is called whenever the canvas needs to be drawn.

eventcalls paint?
the window first appearsyes
the window is movedyes
the window is resizedyes
another window covers and uncovers ityes
you call repaint()yes, when the system is ready
you call paint directlydon't

The consequence is that paint may run many times and at unpredictable moments — so it must be able to redraw everything from scratch, and it must not change the program's state. A paint method that increments a counter will produce different behaviour depending on how often the window is moved.

41. Changing state inside paint

Trap

The trap

paint runs at unpredictable times.

public void paint(Graphics g) {
    xpos += 5;                       // moving the object HERE
    g.fillOval(xpos, 100, 50, 50);
}
what happenseffect on xpos
the window appears+5
you drag the window+5 many times
another window uncovers it+5
the animation's actual speedunpredictable

The object drifts whenever the window is disturbed, and stands still when it is not. The drawing speed becomes a function of how much the user moves the mouse, which is a genuinely bewildering bug.

The fix

paint draws; something else updates.

public void paint(Graphics g) {
    g.fillOval(xpos, 100, 50, 50);   // draw only
}

public void step() {
    xpos += 5;                       // update, driven by a Timer
}
methodresponsibility
paintrender the current state
stepadvance the state
who calls eachthe system; the Timer

This is exactly the split Chapters 15 to 17 use: update or step changes the model, paint renders it, and repaint asks for the render. Keeping them apart is what makes the animation speed depend on the timer rather than on the window manager.

42. Fill or outline?

Definition probe

The prefix says which.

Sort into buckets

Sort each method.

paints the interior
fillOval; fillRect
outline only
drawRect; drawLine
fill
The prefix fill means the shape's interior is painted in the current colour.
draw
The prefix draw means only the outline is drawn — and a line has no interior to fill.

43. Match the method to what it draws

Matching

Five Graphics methods.

Match the pairs

  • a. fillOval(x, y, w, h)
  • b. drawLine(x1, y1, x2, y2)
  • c. drawString(s, x, y)
  • d. setColor(c)
  • r1. a solid ellipse inside a bounding box
  • r2. a line between two points
  • r3. text
  • r4. nothing — it changes what comes next

Why: Note the parameter patterns: shapes take a bounding box, a line takes two points, and text takes a position. setColor draws nothing at all — it changes the state of the Graphics object, which is the one thing about this API worth remembering.

44. Why must paint be repeatable?

Explain it to yourself

It may be called at any moment.

Discussion prompt

The window system calls paint when the window is moved, resized or uncovered. What does that require of the method you write?

Hint: How many times, and in what order?

Answer:

It must draw everything from scratch, every time. There is no partial redraw — the canvas may have been cleared, so paint has to reproduce the whole picture.

And it must not change anything. Calling it twice must produce the same drawing, so any state change inside it makes the program's behaviour depend on window management.

Render, do not update. That separation is what Chapters 15 to 17 build on — a step or update method advances the model on a clock, and paint only ever reads it.

45. What the exercises are for

Section

Section C.5

46. Three drawings, three techniques

Concept

Before you start the exercises, we recommend that you compile and run the examples. Each of the three practises something different.

exercisethe drawingthe technique
C.1the flag of Japana bounding box and two colours
C.2Mickey Moose — ears all the way downrecursion with a base case
C.3Moiré patternsloops that generate many shapes

Just about any kind of graphical pattern can generate Moiré-like interference patterns. Play around and see what you can create. The graphics are the reward; the techniques underneath are Chapters 6, 8 and 10.

47. The recursive drawing

Notation

Exercise C.2 asks for the same figure at every scale, and gives away how little should change.

Annotate

  • The hint is a claim about the existing design. Only a few lines change because every position was already computed from the bounding box.
  • Each boxOval(g, half) becomes mickey(g, half) — the ear becomes a whole figure, drawing its own ears.
  • **The base case is 3 pixels wide** — Lesson 8a's requirement, supplied by the exercise so you cannot forget it.
  • The recursion branches twice, like Lesson 13b's merge sort, so the number of shapes doubles at each level.
  • Halving from 200 reaches 3 in about six levels, which is log2(200/3) — the same arithmetic as binary search.

A recursive drawing is a recursive method with a base case, and nothing about it is specific to graphics. The picture just makes the structure visible in a way a printed number does not.

48. The flag of Japan

Worked example

Exercise C.1: draw the flag of Japan: a red circle on a white background that is wider than it is tall. Three things to get right.

public void paint(Graphics g) {
    // 1. the background
    setBackground(Color.WHITE);

    // 2. the colour of the circle
    g.setColor(Color.RED);

    // 3. a circle, centred, on a canvas wider than it is tall
    // (the sizes are yours to choose)
    g.fillOval(x, y, d, d);
}
requirementwhat it needs
a white backgroundsetBackground on the Canvas
a red circlesetColor before fillOval
a circle, not an ellipseequal width and height
centredcorner = centre − radius
wider than tallsetSize with unequal dimensions

Set the background on the canvas.

Why: setBackground is a Canvas method, not a Graphics one — inherited by your subclass.

Set the colour before drawing.

Why: Graphics is stateful, so the order matters.

Keep the width and height equal.

Why: A bounding box that is not square gives an ellipse.

Compute the corner from the centre.

Why: Which is the arithmetic from Section C.2's hazard.

Verify: Make the canvas 600 by 400 and the circle 200 across, and work out the corner: (200, 100).

Why: Centre (300, 200) minus radius 100 in each direction. Every drawing you make will involve that subtraction somewhere — it is the single most repeated calculation in AWT code.

49. Why is the flag's circle a circle?

Prediction

fillOval draws an ellipse in general.

g.fillOval(x, y, d, d);
width vs heightshape
equal?
unequalan ellipse

Predict first

What makes it a circle rather than an ellipse?

  • The bounding box is square — equal width and height
  • fillOval always draws circles
  • The colour is red
  • The canvas is wider than it is tall

Correct: The bounding box is square — equal width and height

Why: fillOval fills whatever bounding box it is given, so a square box produces a circle and any other box produces an ellipse. The canvas being wider than tall is a separate requirement — it is the shape of the flag, not of the disc.

50. Where this appendix is used

Concept

Nothing here is optional if you intend to read Chapters 15 to 17.

from this appendixused in
extending Canvas and overriding paintGridCanvas, Lesson 15a
the Graphics parameterCell.draw and every draw method after it
y increasing downwardthe ant's heading, Lesson 16
setColor's statefulnessCell.draw's two calls, Lesson 15a
bounding boxesfillRect in Cell, Lesson 15a
Color constants and RGBthe COLORS array, Lesson 15a

Chapter 15's first paragraph says so directly: if you haven't yet read Appendix C, you might want to read it now and become familiar with the Canvas, Color, and Graphics classes from the java.awt package. This is the appendix the numbered chapters depend on.

51. Forgetting that the canvas needs a size

Trap

The trap

A canvas with no size packs to nothing.

Drawing drawing = new Drawing();
// no setSize
frame.add(drawing);
frame.pack();
frame.setVisible(true);
result
the canvas's preferred sizezero
pack fits the frame to ita tiny window
your drawinginvisible
any error messagenone

pack resizes the frame to fit its contents, and a canvas that has not been sized reports nothing to fit. You get a title bar and no drawing area — which looks like the program failed rather than like a missing line.

The fix

Size the canvas before packing the frame.

Drawing drawing = new Drawing();
drawing.setSize(400, 400);
frame.add(drawing);
frame.pack();
frame.setVisible(true);
orderresult
setSize then packa 400 by 400 drawing area
pack then setSizea tiny window

Set the canvas's size, add it, then pack. The same three-line sequence appears in Lesson 15a's main, where GridCanvas's constructor calls setSize(cols * size, rows * size) for itself — a tidier arrangement, and the same requirement.

52. Canvas method or Graphics method?

Definition probe

Two different objects.

Sort into buckets

Sort each call.

on the Canvas
setSize(400, 400); setBackground(Color.WHITE)
on the Graphics object
setColor(Color.RED); fillOval(...)
canvas
A property of the component itself — its dimensions and its background — inherited by your subclass from Canvas.
g
A drawing operation or drawing state, called on the Graphics object passed into paint.

53. Set up the drawing

Fill the middle

Colour before shape.

Fill in the blanks

public void paint(Graphics g) setColor}(Color.RED);
g.fillOval(200, 100, 200, 200);
}

Why: Graphics is stateful, so the colour must be set before the shape is drawn — setting it afterwards has no effect on what is already on the canvas. The four arguments are a bounding box, so this is a 200-pixel circle whose centre is at (300, 200).

54. Why does a book on Java include an appendix on graphics?

Real world

None of Chapters 1 to 14 needs it.

Discussion prompt

The appendix is optional until Chapter 15, and then three chapters depend on it entirely. Why build the last part of the book on graphics at all?

Hint: What can you see about a 2D array that you cannot see about a number?

Answer:

Because a wrong picture is obvious and a wrong number is not. A transposed 2D array is instantly visible as a sideways grid; the same bug in a table of numbers can hide for hours.

And it makes the object-oriented ideas concrete. Every actor draws itself is an abstract principle until you watch a sprite and a polygon come out of the same list.

The last three chapters of this book use 2D graphics to illustrate more advanced object-oriented concepts — the graphics are the illustration, and the concepts are inheritance, interfaces and event-driven design.

55. Cartesian against graphical coordinates

Comparison

Fill the blanks.

Comparison matrix

CartesianJava graphics
the origin isin the middlein the upper-left corner
positive y isupdown
values may be negativeyesalways positive integers
the unitwhatever you choosethe pixel
a circle is specified bya centre and a radiusa bounding box

The last two rows cause the most trouble in practice. Integers mean no half-pixels, which is why Lesson 17a rounds its vertices; bounding boxes mean the centre must be computed, every single time.

56. The pattern to carry away

Pattern

Extend Canvas, override paint, and draw relative to a bounding box.

public class Drawing extends Canvas {

    public static void main(String[] args) {
        JFrame frame = new JFrame("My Drawing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Drawing drawing = new Drawing();
        drawing.setSize(400, 400);      // size the canvas...
        frame.add(drawing);
        frame.pack();                    // ...then fit the frame
        frame.setVisible(true);
    }

    public void paint(Graphics g) {
        g.setColor(Color.RED);           // colour first
        g.fillOval(100, 100, 200, 200);  // then the shape
    }
}
rulereason
you write paint; the system calls itonly it has a Graphics object
setSize before packpack fits the frame to its contents
setColor before drawingGraphics is stateful
x and y are a corner, not a centreAWT specifies a bounding box
y increases downwardup is negative, everywhere
paint must not change stateit runs at unpredictable times

57. Check: the coordinate system

Check

Work it out before you click.

// moving a shape UP the screen
originpositive y
upper-left?

Check your understanding

How do you move a shape up?

  • A. Decrease y — the origin is in the upper-left corner and y increases downward (correct)
  • B. Increase y
  • C. Decrease x
  • D. It depends on the canvas size

Answer: A

Why: Java uses a coordinate system where the origin is in the upper-left corner. That way, x and y can always be positive integers. So up is negative — which is why Langton's ant does ypos -= 1 to face north and a sprite sets dy = -5 for the up arrow. A sign error here draws a perfectly good picture, upside down, with no error message.

Why B tempts people
That moves it down; the convention is the reverse of Cartesian.
Why C tempts people
x controls horizontal position, not vertical.
Why D tempts people
The convention is fixed regardless of size.

58. Check: fillOval

Check

Work it out before you click.

g.fillOval(100, 100, 200, 200);
parameterrole
100, 100?
200, 200?

Check your understanding

What do the four parameters mean?

  • A. The upper-left corner of the bounding box, then its width and height (correct)
  • B. The centre of the oval, then its two radii
  • C. Two corners of the bounding box
  • D. The centre, then the width and height

Answer: A

Why: The four parameters specify a bounding box, which is the rectangle in which the oval is drawn. x and y specify the location of the upper-left corner of the bounding box. The box itself is not drawn, and the centre — here (200, 200) — is never a parameter. Treating x and y as the centre displaces the shape by exactly half its size, which is this API's most common mistake.

Why B tempts people
That is how you would describe an ellipse mathematically; AWT does not work that way.
Why C tempts people
The last two are a width and a height, not a second point.
Why D tempts people
The size is right and the position is not — x and y are a corner.

59. Check: paint

Check

Work it out before you click.

public void paint(Graphics g) {
    g.fillOval(100, 100, 200, 200);
}
who
writes paintyou
calls paint?

Check your understanding

When is paint called?

  • A. By the window system whenever the canvas needs drawing — on first display, and when the window is moved or resized (correct)
  • B. Once, by main
  • C. Every time you call it
  • D. Only when repaint is called

Answer: A

Why: Once the frame is visible, the paint method is called whenever the canvas needs to be drawn; for example, when the window is moved or resized. You never call it yourself, because only the system can supply a valid Graphics object. The consequence is that paint must redraw everything from scratch and must not change the program's state, since how often it runs is out of your control.

Why B tempts people
main never mentions paint; it only makes the frame visible.
Why C tempts people
Calling paint directly is exactly what you should not do — you have no Graphics to pass.
Why D tempts people
repaint is one trigger among several; moving the window is another.

60. Why the origin is in the corner

Real world

Nearly every graphics system on every platform puts (0, 0) at the top left and counts y downward.

Discussion prompt

Mathematics has used the Cartesian convention for centuries. Why did computer graphics do something else?

Hint: How was a screen originally drawn?

Answer:

Because early displays were scanned from the top left, line by line, like reading a page — so the first pixel drawn was naturally position zero, and the count went the way the beam went.

And it keeps every coordinate a positive integer, which the appendix says outright: that way, x and y can always be positive integers. Negative positions would be off the screen and useless.

The convention outlived its reason and became universal — the same choice appears in image formats, web layout, and mobile toolkits. That is worth knowing: a convention you cannot argue with is usually the residue of a decision made for a machine that no longer exists.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

Why does the program keep running after main returns?

  • It waits for the JFrame to close, which then calls System.exit
  • main never actually returns
  • The Canvas runs an infinite loop
  • paint blocks until the window is closed

Correct: It waits for the JFrame to close, which then calls System.exit

Why: The application doesn't end after the main method returns; instead, it waits for the JFrame to close. When the JFrame closes, it calls System.exit, which ends the program. That behaviour comes from setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) — without it, closing the window would hide it and leave the program running invisibly, which is exactly the footnote Lesson 15a mentions about Frame needing you to write the handler yourself. It is also what lets Lesson 17c's main create a Timer, start it, and return immediately.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate's drawing is appearing at the wrong place — every shape is offset up and to the left by half its size. Explain what is wrong.

Hint: What do fillOval's first two parameters mean?

Answer:

They are treating x and y as the centre of the shape. In AWT the first two parameters are the upper-left corner of the bounding box, and the last two are its width and height.

So fillOval(200, 200, 200, 200) puts the centre at (300, 300), not (200, 200) — displaced by exactly half the size, which is the symptom they are seeing.

The fix is to subtract the radius: fillOval(cx - r, cy - r, 2*r, 2*r). Keep your own model in centres and convert at the point of drawing — every AWT method uses bounding boxes, so the conversion has to happen somewhere.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Where does the Graphics object come from, and what does that imply?

  • The system creates it and passes it to paint, so you override paint rather than calling it
  • You create it with new Graphics() in main
  • The JFrame constructor returns one
  • Each drawing method creates its own

Correct: The system creates it and passes it to paint, so you override paint rather than calling it

Why: You don't have to create the Graphics object; it gets created when you create the Canvas, and it gets passed as an argument to paint. Two consequences follow. First, you cannot usefully call paint yourself, because you have no valid Graphics to pass — you call repaint and let the system call paint. Second, since the system decides when to call it, paint must be able to redraw everything from scratch and must not change the program's state. Lesson 17a adds the reason new Graphics() would not even compile: Graphics is an abstract class.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the two coordinate systems side by side — Cartesian with the origin in the middle, Java's with it in the upper-left — and mark which way positive y goes in each. Beside them, draw an oval inside its bounding box, labelling x, y, width and height and marking where the centre falls. Then write the seven lines of main in order, noting why setSize must come before pack. Finish with the three lines of mickey that position an ear, and say why every value in them is computed from the bounding box.

65. Recap

Recap

The appendix Chapter 15 depends on: five sections of drawing, and one coordinate convention that catches everyone.

if you remember one thingit is this
about coordinatesup is negative
about shapesx and y are a corner, not a centre
about paintrender only — never update state in it

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter C (Graphics), Sections C.1-C.5, pp. 319-324
  2. Java SE 21 API — java.awt.Graphics
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108