Drawing Shapes and Text with Graphics
A graphics context is an object of class java.awt.Graphics, and Java needs one for any drawing.
Predicting a Border Around a Panel
A common first attempt at drawing a border around a panel is to ask for a rectangle the same size as the panel. Inside a paint method, the size of the component is available through getWidth() and getHeight(), so the call looks like it should outline the whole panel. This article follows what actually happens when that call runs, and why the answer depends on how a rectangle's pixels are counted.
What do you think happens?
A panel is 200 pixels wide and 100 pixels tall. Inside its paint method you call g.drawRect(0, 0, getWidth(), getHeight()). Which edges of the panel do you expect to see outlined?
Reveal answer
Only the top and left edges
The drawn outline is one pixel wider and one pixel taller than the width and height passed in, so the extra column and row land at x = 200 and y = 100, just past the component's edges. Drawing outside a component's boundaries is ignored, so the right and bottom edges are discarded. The fix is to subtract one from each size, as covered later in this article.
The Graphics Context as a Drawing Handle
A graphics context is an object of class java.awt.Graphics. Java needs one for any drawing that a program does on a component.
When you write a paintComponent() method for a Swing component, the method header already contains a parameter of type Graphics. It is easy to treat that parameter as a convenient detail and move on, but it is the key to all drawing in this style of programming. Whatever you draw, from a rectangle to a line of text, is requested through an object that knows where the drawing should go. The useful question to keep in mind is: where did this object come from, and what is it allowed to draw on?
A graphics context has a limited scope. A single Graphics object can draw to only one location, and in the programs covered here that location is always a GUI component that belongs to some subclass of JPanel. A graphics context is therefore not a general-purpose drawing surface. It is a handle tied to one component.
A natural question follows: could you create one yourself? The Graphics class is abstract, so you cannot make a graphics context by calling a constructor directly. Because you cannot construct one, it has to be handed to you. Java supplies it, and the program's job is to use it.
Two Routes to a Graphics Context
There are exactly two routes for getting a graphics context that can draw on a component. The first is the one you will use most of the time: the Graphics parameter of paintComponent(). The second is the getGraphics() method on a component, which exists but comes with an important caveat. Both routes produce the same kind of object. The difference lies in when and where you receive it, and whether the system is managing the situation for you.
The paintComponent() route hands you the context at the moment the component is painted. You do not ask for it; it arrives as the parameter. The getGraphics() route is different: your program asks the component for a context at a time you choose. Because that context is one you requested yourself, the source treats the release step as your responsibility, which leads to the next section.
Releasing a Context with dispose
A graphics context obtained from getGraphics() may hold operating system resources. The source recommends calling g.dispose() once you have finished using it. That call releases whatever resources the context is holding. The source describes calling dispose() as good form, meaning it is a sound habit. The program might sometimes run without it, but releasing the context is the disciplined way to finish with it.
- 1Obtain a graphics context, either from the
paintComponent()parameter or by callinggetGraphics()on the component. - 2Use the context to draw. Each call such as
drawString,drawLine,drawRect, ordrawOvalis requested through that one object. - 3If the context came from
getGraphics(), calldispose()on it once drawing is finished, so the operating system resources it may hold are released.
How a Rectangle Covers Pixels
The rectangle method draws the outline of a rectangle. Its upper left corner sits at the point (x, y), and its width and height are the values you pass in. That description is only part of the story. The rectangle is drawn with the same pen used for lines, and that pen colors pixels one pixel to the right of the geometric line and one pixel below it.
The consequence is that the outline's real width is width plus one, and its real height is height plus one. The extra pixel column lies past the right edge, and the extra pixel row lies past the bottom edge. This corrects the belief that a rectangle with width 100 and height 50 covers exactly 100 by 50 pixels. The pixels you see extend one beyond the numbers you typed.
The diagram shows the x axis only, but the same reasoning applies to the y axis. Column 10 is the extra pixel that the width value of 10 does not account for, because the outline is drawn one pixel wider than the width passed in. The row logic follows the same rule with the height value.
Parameters of the Four Drawing Methods
The four methods drawString, drawLine, drawRect, and drawOval belong to the Graphics class, so each is called through a graphics context such as g. Each one takes integer coordinates or sizes. The table summarizes what each parameter controls, following the source's descriptions of the methods.
| Method | Parameters | What each parameter controls |
|---|---|---|
| drawString | (text, x, y) | x is the left end of the string; y is the baseline, the line on which the characters rest. The text is the string drawn. Current color and font are used. |
| drawLine | (x1, y1, x2, y2) | The line runs from the point (x1, y1) to the point (x2, y2). These are the endpoints of the line. |
| drawRect | (x, y, width, height) | (x, y) is the upper left corner; width and height set the size. The real outline is one pixel wider and one pixel taller than width and height. |
| drawOval | integer coordinates and sizes | Takes integer coordinates and sizes like the other methods in this group. The source pack does not spell out its bounding-box behavior, so check the Graphics reference before relying on exact placement. |
Parameters of the four drawing methods in this group
The source explains that coordinates refer to the lines between pixels rather than to the pixels themselves. The pen that draws a line extends one pixel to the right and one pixel down from the (x, y) point where the pen sits. Keeping that picture in mind explains why a single point is not invisible. For text, the y value is the baseline rather than the top of the characters, because some characters such as the tail of a lowercase y or g extend below it.
Sizing a Border to Fit the Panel
The source gives the standard fix for drawing a border around a component. Subtract one from the width and height, so the drawn outline's extra pixel lands on the last pixel inside the component. The source's example is g.drawRect(0, 0, getWidth()-1, getHeight()-1).
Border for a 300 by 150 panel
A panel is 300 pixels wide and 150 pixels tall. Choose the drawRect arguments that draw a border whose right and bottom edges are visible inside the panel.
- 1Identify the component size
getWidth()returns 300 andgetHeight()returns 150 for this panel. - 2Account for the extra pixel
The outline is drawn one pixel wider and one pixel taller than the values passed in, so the extra column and row fall just past the size passed in.
- 3Subtract one from each size
Use 300 - 1 = 299 for the width and 150 - 1 = 149 for the height, so the extra pixel lands on the last pixel inside the panel.
- 4Place the upper left corner
Starting at (0, 0) keeps the top and left edges at the panel's first pixel.
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1), which for this panel is g.drawRect(0, 0, 299, 149). The border is visible on all four edges.
- The upper left corner is at (0, 0), the panel's first pixel.
getWidth()- 1 andgetHeight()- 1 keep the extra pixel inside the panel.
Following Drawing Past the Edge
All of the drawing commands in this group respect the boundaries of the component. Any drawing done outside those boundaries is ignored, so pixels placed past the component's right or bottom edge are simply not shown. This is why the getWidth() - 1 adjustment matters. Without it, the extra pixel column and row of a border fall outside the panel and vanish.
Mistakes with Sizes and Contexts
Common mistake Assuming a rectangle covers exactly the width and height passed in
What it looks likeg.drawRect(0, 0, 100, 50) is expected to cover 100 by 50 pixels.Why it failsThe outline is drawn one pixel wider and one pixel taller than the values passed in, so the real coverage extends one pixel further on each axis.
Do this insteadRemember that the outline's real width is width plus one and its real height is height plus one when you plan edges.
Common mistake Drawing a border with the component's full size
What it looks likeg.drawRect(0, 0, getWidth(), getHeight()) in a paint method.Why it failsThe extra pixel column and row fall at the component's right and bottom edges, which lie outside the component, so they are ignored and those edges disappear.
Do this insteadUse g.drawRect(0, 0, getWidth() - 1, getHeight() - 1) so the extra pixel lands on the last pixel inside the component.
Common mistake Trying to build a graphics context with a constructor
What it looks likeGraphics g = new Graphics();Why it failsThe Graphics class is abstract, so a graphics context cannot be created directly with a constructor.
Do this insteadUse the Graphics parameter of paintComponent(), or ask the component for one with getGraphics().
Common mistake Forgetting to release a context obtained from getGraphics()
What it looks likeCalling getGraphics(), drawing, and then ending without calling dispose().
Why it failsA context from
getGraphics()may hold operating system resources, which the program should release when finished.Do this insteadCall dispose() on the context once you have finished drawing with it.
Common mistake Treating the y value of drawString as the top of the text instead of the baseline
Checking Your Border Sizes
A panel is 400 pixels wide and 200 pixels tall. Write the drawRect call inside its paintComponent() method that draws a border with all four edges visible inside the panel.
Hints
- Start from the upper left corner at (0, 0).
- Subtract one from the component's width and height, as the border example did.
- Check that the extra pixel column and row land on the last pixel inside the panel.
Answer the following: when you use getGraphics() to obtain a context, why does the source recommend calling dispose() on it? Then explain in one sentence why the same release step is not described for the paintComponent() parameter.
Hints
- Think about what the source says a
getGraphics()context may hold. - Consider which route the program requests itself, and which the system supplies.
Key Takeaways
- A graphics context is a
java.awt.Graphicsobject, and Java needs one for any drawing. It cannot be built with a constructor, so it is handed to the program through thepaintComponent()parameter or obtained withgetGraphics(). - A graphics context is tied to one location, a
JPanel-based component. Drawing outside that component's boundaries is ignored, and a negative width or height makesdrawRectdraw nothing. - A rectangle's outline is drawn one pixel wider and one pixel taller than the width and height passed in, so
drawRect(0, 0, getWidth() - 1, getHeight() - 1)keeps the right and bottom edges visible. - A context obtained from
getGraphics()may hold operating system resources, so callingdispose()when finished is the recommended good practice.