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
Title
Think Java 2e · Chapter C · Graphics
Sections C.1-C.5 · pp. 319-324
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.
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.
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.
Section
Section C.1
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.
| class | is | who creates it |
|---|---|---|
| Canvas | a blank rectangular area of the screen onto which the application can draw | you |
| Graphics | basic drawing methods such as drawLine, drawRect and drawString | the system |
| JFrame | the window that will contain the canvas | you |
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.
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.
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);
}
}| line | does |
|---|---|
| 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.
Prediction
You never write new Graphics().
public void paint(Graphics g) {
g.fillOval(100, 100, 200, 200);
}| object | created by |
|---|---|
| the JFrame | you |
| the Canvas | you |
| the Graphics | ? |
Predict first
Who 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.
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 returns | state |
|---|---|
| the window | visible |
| the program | still running |
| what ends it | closing the window |
| what that calls | System.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.
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 Graphics | representing the actual drawing surface |
| you cannot make a valid one | only the window system can |
| and timing matters | the 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.
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 write | the system does |
|---|---|
| paint | calls 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.
Definition probe
Three objects, two creators.
Sort into buckets
Sort each object.
new for it in main — the window and the canvas are yours to construct and configure.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.
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.
Section
Section C.2
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.
Notation
Every graphics chapter in this book has a line that only makes sense because y increases downward.
Annotate
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.
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)| parameter | means |
|---|---|
| x | the left edge of the bounding box |
| y | the top edge of the bounding box |
| width | how wide the box is |
| height | how tall |
| the box itself | not 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.
Prediction
Java's origin is in the upper-left corner.
// drawing something 50 pixels "up" from y = 100| system | positive y |
|---|---|
| Cartesian | up |
| Java graphics | ? |
Predict first
In Java graphics, increasing y moves the drawing which way?
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.
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 | |
|---|---|
| setColor | determines the colour of everything drawn afterward |
| Color.RED | a constant provided by the Color class |
| new Color(128, 0, 128) | red, green and blue components |
| each component | 0 (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.
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)| call | box corner | actual 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.
Subtract the radius to get the corner.
int cx = 200, cy = 200, r = 100;
g.fillOval(cx - r, cy - r, 2 * r, 2 * r);| from | to |
|---|---|
| centre (cx, cy) and radius r | corner (cx − r, cy − r) |
| radius r | width 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.
Prediction
The parameters are a bounding box.
g.fillOval(100, 100, 200, 200);| parameter | value |
|---|---|
| x, y — the box corner | (100, 100) |
| width, height | 200, 200 |
Predict first
Where is the centre of the circle?
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.
Definition probe
Two ways to specify a colour.
Sort into buckets
Sort each.
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.
Section
Section C.3
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 parameter | a Rectangle rather than four ints |
| bb.x, bb.y | the corner |
| bb.width, bb.height | the size |
| what it draws | an 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.
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.
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);
}| step | half's position | draws |
|---|---|---|
| created at bb.x, bb.y | the face's corner | — |
| translate(−hx/2, −hy/2) | up and left | the left ear |
| translate(hx * 2, 0) | right by a full face width | the 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.
Prediction
The Rectangle from Lesson 10a.
Rectangle half = new Rectangle(100, 100, 50, 50);
half.translate(-25, -25);| before | after |
|---|---|
| x = 100, y = 100 | ? |
Predict first
Where is the rectangle now?
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.
Concept
Not one coordinate in mickey is a literal. Every position is computed from the bounding box it was given.
| quantity | computed as | so if bb doubles |
|---|---|---|
| hx | bb.width / 2 | doubles |
| the ear size | hx by hy | doubles |
| the offset up and left | hx/2, hy/2 | doubles |
| the gap between ears | hx * 2 | doubles |
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.
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
}| want | with literals |
|---|---|
| a bigger Mickey | rewrite six numbers |
| one somewhere else | rewrite six numbers |
| recursive ears | impossible |
| 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.
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;
...
}| parameter | what it lets you change |
|---|---|
| bb.x, bb.y | where the figure sits |
| bb.width, bb.height | how big it is |
| neither | the 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.
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);| face | ear |
|---|---|
| 200 by 200 | ? |
Predict first
For a 200 by 200 face, how big is each ear?
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.
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.
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.
Section
Sections C.1-C.2
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.
| method | draws |
|---|---|
| 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.
Notation
setColor is not a parameter to anything. It changes the object, and stays changed.
Annotate
Cell.draw sets the colour twice — once for the fill and once for the light-gray border.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.
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| prefix | means | example |
|---|---|---|
| fill | the interior is painted | fillOval, fillRect |
| draw | only the outline | drawOval, drawRect, drawLine |
| neither | text and images | drawString, 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.
Prediction
Graphics is stateful.
g.setColor(Color.RED);
g.fillOval(10, 10, 50, 50);
g.fillRect(70, 10, 50, 50);| call | current colour |
|---|---|
| setColor(RED) | red |
| fillOval | red |
| fillRect | ? |
Predict first
What colour is the rectangle?
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.
Concept
Once the frame is visible, the paint method is called whenever the canvas needs to be drawn.
| event | calls paint? |
|---|---|
| the window first appears | yes |
| the window is moved | yes |
| the window is resized | yes |
| another window covers and uncovers it | yes |
| you call repaint() | yes, when the system is ready |
| you call paint directly | don'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.
Trap
paint runs at unpredictable times.
public void paint(Graphics g) {
xpos += 5; // moving the object HERE
g.fillOval(xpos, 100, 50, 50);
}| what happens | effect on xpos |
|---|---|
| the window appears | +5 |
| you drag the window | +5 many times |
| another window uncovers it | +5 |
| the animation's actual speed | unpredictable |
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.
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
}| method | responsibility |
|---|---|
| paint | render the current state |
| step | advance the state |
| who calls each | the 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.
Definition probe
The prefix says which.
Sort into buckets
Sort each method.
fill means the shape's interior is painted in the current colour.draw means only the outline is drawn — and a line has no interior to fill.Matching
Five Graphics methods.
Match the pairs
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.
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.
Section
Section C.5
Concept
Before you start the exercises, we recommend that you compile and run the examples. Each of the three practises something different.
| exercise | the drawing | the technique |
|---|---|---|
| C.1 | the flag of Japan | a bounding box and two colours |
| C.2 | Mickey Moose — ears all the way down | recursion with a base case |
| C.3 | Moiré patterns | loops 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.
Notation
Exercise C.2 asks for the same figure at every scale, and gives away how little should change.
Annotate
boxOval(g, half) becomes mickey(g, half) — the ear becomes a whole figure, drawing its own ears.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.
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);
}| requirement | what it needs |
|---|---|
| a white background | setBackground on the Canvas |
| a red circle | setColor before fillOval |
| a circle, not an ellipse | equal width and height |
| centred | corner = centre − radius |
| wider than tall | setSize 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.
Prediction
fillOval draws an ellipse in general.
g.fillOval(x, y, d, d);| width vs height | shape |
|---|---|
| equal | ? |
| unequal | an ellipse |
Predict first
What makes it a circle rather than an ellipse?
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.
Concept
Nothing here is optional if you intend to read Chapters 15 to 17.
| from this appendix | used in |
|---|---|
| extending Canvas and overriding paint | GridCanvas, Lesson 15a |
| the Graphics parameter | Cell.draw and every draw method after it |
| y increasing downward | the ant's heading, Lesson 16 |
| setColor's statefulness | Cell.draw's two calls, Lesson 15a |
| bounding boxes | fillRect in Cell, Lesson 15a |
| Color constants and RGB | the 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.
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 size | zero |
| pack fits the frame to it | a tiny window |
| your drawing | invisible |
| any error message | none |
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.
Size the canvas before packing the frame.
Drawing drawing = new Drawing();
drawing.setSize(400, 400);
frame.add(drawing);
frame.pack();
frame.setVisible(true);| order | result |
|---|---|
| setSize then pack | a 400 by 400 drawing area |
| pack then setSize | a 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.
Definition probe
Two different objects.
Sort into buckets
Sort each call.
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).
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.
Comparison
Fill the blanks.
Comparison matrix
| Cartesian | Java graphics | |
|---|---|---|
| the origin is | in the middle | in the upper-left corner |
| positive y is | up | down |
| values may be negative | yes | always positive integers |
| the unit | whatever you choose | the pixel |
| a circle is specified by | a centre and a radius | a 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.
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
}
}| rule | reason |
|---|---|
| you write paint; the system calls it | only it has a Graphics object |
| setSize before pack | pack fits the frame to its contents |
| setColor before drawing | Graphics is stateful |
| x and y are a corner, not a centre | AWT specifies a bounding box |
| y increases downward | up is negative, everywhere |
| paint must not change state | it runs at unpredictable times |
Check
Work it out before you click.
// moving a shape UP the screen| origin | positive y |
|---|---|
| upper-left | ? |
Check your understanding
How do you move a shape up?
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.
Check
Work it out before you click.
g.fillOval(100, 100, 200, 200);| parameter | role |
|---|---|
| 100, 100 | ? |
| 200, 200 | ? |
Check your understanding
What do the four parameters mean?
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.
Check
Work it out before you click.
public void paint(Graphics g) {
g.fillOval(100, 100, 200, 200);
}| who | |
|---|---|
| writes paint | you |
| calls paint | ? |
Check your understanding
When is paint 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.
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.
Commit first
Commit to an answer and to your confidence.
Predict first
Why does the program keep running after main returns?
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.
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.
Exit ticket
One question before you close the deck.
Predict first
Where does the Graphics object come from, and what does that imply?
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.
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.
Recap
The appendix Chapter 15 depends on: five sections of drawing, and one coordinate convention that catches everyone.
| if you remember one thing | it is this |
|---|---|
| about coordinates | up is negative |
| about shapes | x and y are a corner, not a centre |
| about paint | render only — never update state in it |
Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.