Next we’ll embark on a quick tour of Java 2D, including working with shapes and text. We’ll finish with an example of Java 2D in action.
The simplest path through the rendering pipeline is
filling shapes. For example, the following code creates an ellipse and
fills it with a solid color. (This code would live inside a paint()
method somewhere. We’ll present a
complete, ready-to-run example a little later.)
Shape
c
=
new
Ellipse2D
.
Float
(
50
,
25
,
150
,
150
);
// x,y,width,height
g2
.
setPaint
(
Color
.
blue
);
g2
.
fill
(
c
);
Here, g2
is our Graphics2D
object. The Ellipse2D
shape class is abstract, but is
implemented by concrete inner subclasses called Float
and Double
that work with float or double
precision, respectively. The Rectangle2D
class, similarly, has concrete
subclasses Rectangle2D.Float
and
Rectangle2D.Double
.
In the call to setPaint()
, we tell
Graphics2D
to use a solid color,
blue, for all subsequent filling operations. Next, the call to
fill()
tells Graphics2D
to fill the given shape.
All geometric shapes in the 2D API are represented by
implementations of the java.awt.geom.Shape
interface. This interface
defines methods that are common to all shapes, like returning a
rectangle bounding box or testing if a point is inside the shape. The
java.awt.geom
package is a
smorgasbord of useful shape classes, including Rectangle2D
, RoundRectangle2D
(a rectangle with rounded
corners), Arc2D
, Ellipse2D
, and others. In addition, a few more
basic classes in java.awt
are Shape
s: Rectangle
, Polygon
, and Area
.
Drawing a shape’s outline is only a little bit more complicated. Consider the following example:
Shape
r
=
new
Rectangle2D
.
Float
(
100
,
75
,
100
,
100
);
g2
.
setStroke
(
new
BasicStroke
(
4
));
g2
.
setPaint
(
Color
.
yellow
);
g2
.
draw
(
r
);
Here, we tell Graphics2D
to use
a stroke that is four units wide and a solid color, yellow, for filling
the stroke. When we call draw()
, Graphics2D
uses the stroke to create a new
shape, the outline, from the given rectangle. The outline shape is then
filled just as before; this effectively draws the rectangle’s outline.
The rectangle itself is not filled.
Graphics2D
includes
quite a few convenience methods for drawing and filling common shapes;
these methods are actually inherited from the Graphics
class. Table 20-1 summarizes these methods. It’s a
little easier to call fillRect()
rather than
instantiating a rectangle shape and passing it to fill()
.
Table 20-1. Shape-drawing methods in the graphics class
Method | Description |
---|---|
Draws a highlighted, 3D rectangle | |
Draws an arc | |
Draws a line | |
Draws an oval | |
Draws a polygon, closing it by connecting the endpoints | |
Draws a line connecting a series of points, without closing it | |
Draws a rectangle | |
Draws a rounded-corner rectangle | |
| Draws a filled, highlighted, 3D rectangle |
Draws a filled arc | |
Draws a filled oval | |
Draws a filled polygon | |
Draws a filled rectangle | |
Draws a filled, rounded-corner rectangle |
As you can see, for each of the fill()
methods in the table, there is a
corresponding draw()
method that
renders the shape as an unfilled line drawing. With the exception of
fillArc()
and fillPolygon()
, each method takes a simple
x
, y
specification for the top-left corner of the
shape and a width
and height
for its size.
The most flexible convenience method draws a polygon, which is
specified by two arrays that contain the x
and y
coordinates of the vertices. Methods in the Graphics
class take two such arrays and draw
the polygon’s outline or fill the polygon.
The methods listed in Table 20-1
are shortcuts for more general methods in Graphics2D
. The more general procedure is to
first create a java.awt.geom.Shape
object and then pass it to the draw()
or fill()
method of Graphics2D
. For example, you could create a
Polygon
object from coordinate
arrays. Since a Polygon
implements
the Shape
interface, you can pass it
to Graphics2D
’s general draw()
or fill()
method.
The fillArc()
method requires
six integer arguments. The first four specify the bounding box for an
oval—just like the fillOval()
method.
The final two arguments specify what portion of the oval we want to
draw, as a starting angular position and an offset, both of which are
specified in degrees. The zero-degree mark is at three o’clock; a
positive angle is clockwise. For example, to draw the right half of a
circle, you might call:
g
.
fillArc
(
0
,
0
,
radius
*
2
,
radius
*
2
,
-
90
,
180
);
draw3DRect()
automatically
chooses shading colors by “darkening” the current color. So you should
set the color to something other than black, which is the default (maybe
gray or white); if you don’t, you’ll just get a black rectangle with a
thick outline.
Like drawing a shape’s outline, drawing text is just a
simple variation on filling a shape. When you ask Graphics2D
to draw text, it determines the
shapes that need to be drawn and fills them. The shapes that represent
characters are called glyphs. A font is a collection of
glyphs. Here’s an example of drawing text:
g2
.
setFont
(
new
Font
(
"Times New Roman"
,
Font
.
PLAIN
,
64
));
g2
.
setPaint
(
Color
.
red
);
g2
.
drawString
(
"Hello, 2D!"
,
50
,
150
);
When we call drawString()
,
Graphics2D
uses the current font to
retrieve the glyphs that correspond to the characters in the string.
Then the glyphs (which are really just Shape
s) are filled using the current Paint
.
Images are treated a little differently than shapes. In
particular, the current Paint
is not
used to render an image because the image contains its own color
information for each pixel (it is the paint,
effectively). The following example loads an image from a file and
displays it:
Image
i
=
Toolkit
.
getDefaultToolkit
().
getImage
(
"camel.gif"
);
g2
.
drawImage
(
i
,
75
,
50
,
this
);
In this case, the call to drawImage()
tells Graphics2D
to place the image at the given
location. We’ll explain the fourth argument, which is used for
monitoring image loading later.
Four parts of the pipeline affect every graphics
operation. In particular, all rendering is subject to being
transformed, composited, and clipped. Rendering hints are used to
affect all of Graphics2D
’s
rendering.
This example shows how to modify the current transformation with a translation and a rotation:
g2
.
translate
(
50
,
0
);
g2
.
rotate
(
Math
.
PI
/
6
);
Every graphics primitive drawn by g2
will now have this transformation applied
to it (a shift of 50 units right and a rotation of 30 degrees
clockwise). We can have a similarly global effect on
compositing:
AlphaComposite
ac
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
(
float
).
5
);
g2
.
setComposite
(
ac
);
Now, every graphics primitive we draw will be half transparent; we’ll explain more about this later.
All drawing operations are clipped by the current clipping
shape, which is any object implementing the Shape
interface. In
the following example, the clipping shape is set to an ellipse:
Shape
e
=
new
Ellipse2D
.
Float
(
50
,
25
,
250
,
150
);
g2
.
clip
(
e
);
You can obtain the current clipping shape using getClip()
; this is
handy if you want to restore it later using the setClip()
method.
Finally, the rendering hints influence all drawing operations.
In the following example, we tell Graphics2D
to use anti-aliasing, a technique
that smoothes out the rough pixel edges of shapes and text:
g2
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
The RenderingHints
class
contains other keys and values that represent other rendering hints.
If you really like to fiddle with knobs and dials, this is a good
class to check out.
Let’s put everything together now, just to show how graphics
primitives travel through the rendering pipeline. The following example
demonstrates the use of Graphics2D
from the beginning to the end of the rendering pipeline. With very few
lines of code, we are able to draw some pretty complicated stuff (see
Figure 20-2).
Here’s the code:
//file: Iguana.java
import
java.awt.*
;
import
java.awt.event.*
;
import
java.awt.geom.*
;
import
javax.swing.*
;
public
class
Iguana
extends
JComponent
{
private
Image
image
;
private
int
theta
;
public
Iguana
()
{
image
=
Toolkit
.
getDefaultToolkit
().
getImage
(
"Piazza di Spagna.small.jpg"
);
theta
=
0
;
addMouseListener
(
new
MouseAdapter
()
{
public
void
mousePressed
(
MouseEvent
me
)
{
theta
=
(
theta
+
15
)
%
360
;
repaint
();
}
});
}
public
void
paint
(
Graphics
g
)
{
Graphics2D
g2
=
(
Graphics2D
)
g
;
g2
.
setRenderingHint
(
RenderingHints
.
KEY_ANTIALIASING
,
RenderingHints
.
VALUE_ANTIALIAS_ON
);
int
cx
=
getSize
().
width
/
2
;
int
cy
=
getSize
().
height
/
2
;
g2
.
translate
(
cx
,
cy
);
g2
.
rotate
(
theta
*
Math
.
PI
/
180
);
Shape
oldClip
=
g2
.
getClip
();
Shape
e
=
new
Ellipse2D
.
Float
(-
cx
,
-
cy
,
cx
*
2
,
cy
*
2
);
g2
.
clip
(
e
);
Shape
c
=
new
Ellipse2D
.
Float
(-
cx
,
-
cy
,
cx
*
3
/
4
,
cy
*
2
);
g2
.
setPaint
(
new
GradientPaint
(
40
,
40
,
Color
.
blue
,
60
,
50
,
Color
.
white
,
true
));
g2
.
fill
(
c
);
g2
.
setPaint
(
Color
.
yellow
);
g2
.
fillOval
(
cx
/
4
,
0
,
cx
,
cy
);
g2
.
setClip
(
oldClip
);
g2
.
setFont
(
new
Font
(
"Times New Roman"
,
Font
.
PLAIN
,
64
));
g2
.
setPaint
(
new
GradientPaint
(-
cx
,
0
,
Color
.
red
,
cx
,
0
,
Color
.
black
,
false
));
g2
.
drawString
(
"Hello, 2D!"
,
-
cx
*
3
/
4
,
cy
/
4
);
AlphaComposite
ac
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
(
float
).
75
);
g2
.
setComposite
(
ac
);
Shape
r
=
new
RoundRectangle2D
.
Float
(
0
,
-
cy
*
3
/
4
,
cx
*
3
/
4
,
cy
*
3
/
4
,
20
,
20
);
g2
.
setStroke
(
new
BasicStroke
(
4
));
g2
.
setPaint
(
Color
.
magenta
);
g2
.
fill
(
r
);
g2
.
setPaint
(
Color
.
green
);
g2
.
draw
(
r
);
g2
.
drawImage
(
image
,
-
cx
/
2
,
-
cy
/
2
,
this
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"Iguana"
);
frame
.
setLayout
(
new
BorderLayout
());
frame
.
add
(
new
Iguana
(),
BorderLayout
.
CENTER
);
frame
.
setSize
(
300
,
300
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setVisible
(
true
);
}
}
The Iguana
class is a subclass
of JComponent
with a very fancy
paint()
method. The main()
method takes care of creating a
JFrame
that holds the Iguana
component.
Iguana
’s constructor loads a
small image (we’ll talk more about this later) and sets up a mouse event
handler. This handler changes a member variable, theta
, and repaints the component. Each time
you click, the entire drawing is rotated by 15 degrees.
Iguana
’s paint()
method does some pretty interesting
stuff, but none of it is very difficult. First, user space is
transformed so that the origin is at the center of the component. The
user space is then rotated by theta
:
g2
.
translate
(
cx
,
cy
);
g2
.
rotate
(
theta
*
Math
.
PI
/
180
);
Iguana
saves the current
(default) clipping shape before setting it to a large ellipse. Then,
Iguana
draws two filled ellipses. The
first is drawn by instantiating an Ellipse2D
and filling it; the second is drawn
using the fillOval()
convenience
method. (We’ll talk about the color gradient in the first ellipse in the
next section.) As you can see in Figure 20-2, both ellipses are clipped by the
elliptical clipping shape. After filling the two ellipses, Iguana
restores the old clipping shape.
Next, Iguana
draws some text
(see the section Using Fonts). The next
action is to modify the compositing rule as follows:
AlphaComposite
ac
=
AlphaComposite
.
getInstance
(
AlphaComposite
.
SRC_OVER
,
(
float
).
75
);
g2
.
setComposite
(
ac
);
The only thing this means is that we want everything to be drawn
with transparency. The AlphaComposite
class
defines constants that represent different compositing rules, much the
way the Color
class contains
constants that represent different predefined colors. In this case,
we’re asking for the source over destination rule
(SRC_OVER
), but with an additional
alpha multiplier of 0.75. Source over destination means that whatever
we’re drawing (the source) should be placed on top of whatever’s already
there (the destination). The alpha multiplier means that everything we
draw will be treated at 0.75, or three quarters, of its normal opacity,
allowing the existing drawing to “show through.”
You can see the effect of the new compositing rule in the rounded rectangle and the image, which both allow previously drawn elements to show through.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.