Drawing Lines and Pixels with Graphics
The rectangle outline is drawn one pixel wider and one pixel taller than the width and height passed in.
Why a Border Seems to Miss Its Edge
Suppose you want to draw a border around a component, the rectangular area a program paints on screen. The natural first idea is to ask for a rectangle whose width and height match the component's size, using getWidth() and getHeight(). Both values feel like the right numbers to pass in, and it seems reasonable that a rectangle of width 100 and height 50 would cover exactly 100 by 50 pixels.
Both of those assumptions lead to trouble. The border ends up with its right and bottom edges missing or misplaced. This article traces what the drawing methods of the Graphics class actually paint, one pixel at a time, so you can see where the missing edge goes and how to fix it. Predicting the result before reading on is a good habit here, because the answer is not what the numbers suggest.
Every method in this article belongs to the Graphics class, so each one is called through a Graphics object. The examples refer to that object as g, but the programmer chooses the real name of the graphics context.
Tracing a Rectangle Column by Column
The drawRect 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 outline 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 result is that the outline's real width is the width you passed in plus one, and its real height is the height you passed in plus one. The extra pixel column lies past the right edge, and the extra pixel row lies past the bottom edge. The pixels you see extend one beyond the numbers you typed. This is the key correction to the belief from the opening section.
Read the diagram below as a strip of pixel columns. Each box is one column, numbered from the left. Passing a width of 10 with x equal to 0 paints the outline across columns 0 through 10, which is eleven columns in total. Column 10 is the extra column the pen adds beyond the width you requested.
What do you think happens?
Inside paint, a component is 100 pixels wide and 50 pixels tall. You call g.drawRect(0, 0, getWidth(), getHeight()). Will the right and bottom edges of the border be visible?
Reveal answer
No, the outline reaches one column and one row past the component's last visible pixels
The outline is drawn one pixel wider and one pixel taller than the width and height passed in. With a width of 100, the outline reaches column 100, but the component's visible columns run from 0 to 99. Column 100 lies outside the boundary, so it is ignored, and the same happens to the bottom row.
Parameters of the Four Drawing Methods
The four methods drawString, drawLine, drawRect, and drawOval share one pattern. Each is a method of the Graphics object, and each takes integer coordinates or sizes. The table below summarizes what each argument controls. The entries for drawRect, drawLine, and drawString follow directly from the source's descriptions. The source pack does not spell out the individual arguments of drawOval, so that row is marked accordingly.
| Method | Horizontal argument | Vertical argument | Size or endpoint argument |
|---|---|---|---|
| drawLine(x1, y1, x2, y2) | x1 and x2 locate the two endpoints horizontally | y1 and y2 locate the two endpoints vertically | The second point (x2, y2) is the endpoint of the line |
| drawRect(x, y, width, height) | x is the left side of the upper left corner | y is the top side of the upper left corner | width and height set the size; the outline paints one pixel more in each direction |
| drawString(text, x, y) | x marks the left end of the string | y marks the baseline the characters rest on | Not a size argument; the font and color come from the graphics context |
| drawOval(x, y, width, height) | Takes an integer horizontal argument (meaning not detailed in the source pack) | Takes an integer vertical argument (meaning not detailed in the source pack) | Takes integer sizes (meaning not detailed in the source pack) |
What each argument controls. The drawOval row reflects only what the source pack states: that the method takes integer coordinates or sizes.
The drawLine method draws a line from the point (x1, y1) to the point (x2, y2). The pen that draws it extends one pixel to the right and one pixel down from the point where the pen sits. The source also notes that coordinates refer to the lines between pixels rather than to the pixels themselves. Keeping that picture in mind explains why a single point is not invisible.
The drawString method draws text using the current color and font of the graphics context. The x coordinate marks the left end of the string. The y coordinate marks the baseline, the horizontal line on which the characters rest. Some characters, such as the tail of a lowercase y or g, extend below the baseline, which is why the y value is not the top of the text.
Fixing the Border with getWidth and getHeight
The source gives the standard fix for drawing a border around a component. Subtract one from the width and height. The drawn outline's extra pixel column and row then land on the last pixel inside the component, rather than past its edge. The source's example is shown below.
- 0, 0 places the upper left corner at the top left of the component
getWidth()- 1 makes the outline's extra column land on the last visible columngetHeight()- 1 makes the outline's extra row land on the last visible row
Choosing the width for a 100-pixel component
A component is 100 pixels wide. Which width should be passed to drawRect with x equal to 0 so that the right edge is still visible?
- 1Identify the visible columns
A component 100 pixels wide has visible columns numbered 0 through 99.
- 2Apply the one-pixel rule
drawRectpaints one column past the width passed in, so with x equal to 0 the outline reaches column width. - 3Solve for the width
To keep the outline inside columns 0 through 99, the column reached must be 99, so the width passed in is 99.
- 4Check with getWidth
Passing
getWidth()- 1, which is 100 - 1, gives 99, matching the calculation.
Pass getWidth() - 1, which is 99, so the right edge lands on column 99, the last visible pixel. The height follows the same reasoning with getHeight() - 1.
Tracing What Gets Clipped at the Edge
All of the drawing commands in this group respect the boundaries of the component. Any drawing done outside those boundaries is ignored. This is the reason the getWidth() - 1 adjustment matters: pixels placed past the component's right or bottom edge are simply not shown. The result is that a shape extending past the edge is partly drawn, with the parts inside the boundary kept and the parts outside discarded.
The before-and-after view shows the difference between the area you asked for and the area you actually see. The request reaches column 150, but only the columns from 0 through 99 belong to the component, so the rest is discarded. Your intended shape is cut short at the boundary, and no error is reported.
Mistakes Beginners Make with Border Sizes
Common mistake Assuming drawRect with width 100 covers exactly 100 pixels
What it looks likeg.drawRect(0, 0, 100, 50) in a 100 by 50 componentWhy it failsThe outline is drawn one pixel wider and one pixel taller than the values passed in, so it reaches column 100 and row 50.
Do this insteadPass getWidth() - 1 and getHeight() - 1 to keep the right and bottom edges inside the component.
Common mistake Passing getWidth() and getHeight() directly to draw a border
What it looks likeg.drawRect(0, 0, getWidth(), getHeight())Why it failsThe extra pixel column and row lie just outside the component, so they are discarded and the right and bottom edges are missing.
Do this insteadSubtract one from each size before passing it to drawRect.
Common mistake Treating the y value of drawString as the top of the text
What it looks likeg.drawString("gym", 10, 20) expecting the text to start at row 20
Why it failsThe y coordinate marks the baseline, and descenders such as the tail of g extend below it.
Do this insteadThink of y as the line the characters rest on, and allow for letters that extend below it.
Common mistake Expecting a negative width to draw a flipped or partial rectangle, when nothing is drawn at all
Practice: Sizing a Border
A component is 240 pixels wide and 80 pixels tall. Write the drawRect call with x and y equal to 0 that draws a border whose right and bottom edges are the last visible pixels inside the component. Then explain, using the one-pixel rule, why passing 240 and 80 directly would miss those edges.
Hints
- The outline reaches one column and one row past the width and height you pass in.
- The last visible column of a 240-pixel component is column 239.
- Subtract one from each size before passing it to
drawRect.
Key Takeaways
- A rectangle drawn with a given width and height is painted one pixel wider and one pixel taller than those values, because the pen colors pixels one to the right of and one below the geometric line.
drawRecttakes x and y for the upper left corner and width and height for its size;drawLinetakes two endpoints;drawStringtakes a left x and a baseline y; the source pack gives only general guidance ondrawOval's integer arguments.- To draw a border that stays inside a component, pass
getWidth()- 1 andgetHeight()- 1 so the extra pixel row and column land on the last visible pixels. - Drawing outside a component's boundaries is ignored and gets clipped, while a negative width or height causes nothing to be drawn at all.