Looking to Reprint this content?
Cover | Table of Contents | Colophon
Graphics2D object in your application,
and then I'll present a useful class that will be used
throughout the book. Finally, the chapter concludes with a
"teaser" example that shows off some of Java 2D's
features.java.awt
java.awt.image
java.awt.color
java.awt.font
java.awt.geom
java.awt.print
java.awt.image.renderable
com.sun.image.codec.jpeg
com.sun.image.code.jpeg. This means that, except
for the JPEG package, you can rely on the 2D API in all
implementations of the Java 2
platform.
java.awt
java.awt.image
java.awt.color
java.awt.font
java.awt.geom
java.awt.print
java.awt.image.renderable
com.sun.image.codec.jpeg
com.sun.image.code.jpeg. This means that, except
for the JPEG package, you can rely on the 2D API in all
implementations of the Java 2
platform.
java.awt.image.renderable. This package serves as
a bridge to the Java Advanced Imaging API (JAI), which is outside the
scope of this book.Java2Demo
class. For example:C:> cd \jdk1.2\demo\jfc\Java2D C:> java Java2Demo
http://java.sun.com/products/jfc/ for
details.http://www.adobe.com/.http://www.ductus.com/.http://www.kodak.com/). Sun worked closely
with Kodak to develop the imaging and color management classes in the
2D API. Some of the implementation of these classes is based on
technology licensed from Kodak. Just as Adobe helped design the
graphics part of the 2D API, Kodak helped with the design and
implementation of the imaging and color management portions of the 2D
API.Graphics2D object. But where does the
Graphics2D come from? As usual, there's more
than one way to do it.Component that AWT shows on the screen has a
paint() method. The system passes a
Graphics to this method. In JDK 1.1 and earlier,
you could draw on Components by overriding the
paint() method and using the
Graphics to draw things.Graphics2D that is passed to
paint(). To take advantage of all the spiffy 2D
features, you'll have to perform a cast in your
paint() method, like this:public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
// Now we can do cool 2D stuff.
}
Graphics2D that gets passed to
paint() might actually represent a printer or any
other output device.paintComponent() method instead of
paint(). Swing uses the paint()
method to draw child components. Swing's implementation of
paint() calls paintComponent()
to draw the component itself. You may be able to get away with
implementing paint() instead of
paintComponent(), but then don't be
surprised if the component is not drawn correctly.Graphics
or Graphics2D to draw on images, as well. If you
have an Image that you have created yourself, you
can get a corresponding Graphics2D by calling
createGraphics(), as follows:public void drawOnImage(Image i) {
Graphics g = i.getGraphics();
// Now draw on the image using g.
}
Image you've created
yourself, not for an Image loaded from a file.BufferedImage (Java 2D's new image
class), you can obtain a http://www.arca.net/uffizi/.ApplicationFrame class presented earlier in this
chapter. If you haven't entered and compiled
ApplicationFrame, do it now.
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
import com.sun.image.codec.jpeg.*;
public class ShowOff
extends Component {
public static void main(String[] args) {
try {
// The image is loaded either from this
// default filename or the first command-
// line argument.
// The second command-line argument specifies
// what string will be displayed. The third
// specifies at what point in the string the
// background color will change.
String filename = "Raphael.jpg";
String message = "Java2D";
int split = 4;
if (args.length > 0) filename = args[0];
if (args.length > 1) message = args[1];
if (args.length > 2) split = Integer.parseInt(args[2]);
ApplicationFrame f = new ApplicationFrame("ShowOff v1.0");
f.setLayout(new BorderLayout());
ShowOff showOff = new ShowOff(filename, message, split);
f.add(showOff, BorderLayout.CENTER);
f.setSize(f.getPreferredSize());
f.center();
f.setResizable(false);
f.setVisible(true);
}
catch (Exception e) {
System.out.println(e);
System.exit(0);
}
}
private BufferedImage mImage;
private Font mFont;
private String mMessage;
private int mSplit;
private TextLayout mLayout;
public ShowOff(String filename, String message, int split)
throws IOException, ImageFormatException {
// Get the specified image.
InputStream in = getClass().getResourceAsStream(filename);
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(in);
mImage = decoder.decodeAsBufferedImage();
in.close();
// Create a font.
mFont = new Font("Serif", Font.PLAIN, 116);
// Save the message and split.
mMessage = message;
mSplit = split;
// Set our size to match the image's size.
setSize((int)mImage.getWidth(), (int)mImage.getHeight());
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
// Turn on antialiasing.
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawBackground(g2);
drawImageMosaic(g2);
drawText(g2);
}
protected void drawBackground(Graphics2D g2) {
// Draw circles of different colors.
int side = 45;
int width = getSize().width;
int height = getSize().height;
Color[] colors = { Color.yellow, Color.cyan, Color.orange,
Color.pink, Color.magenta, Color.lightGray };
for (int y = 0; y < height; y += side) {
for (int x = 0; x < width; x += side) {
Ellipse2D ellipse = new Ellipse2D.Float(x, y, side, side);
int index = (x + y) / side % colors.length;
g2.setPaint(colors[index]);
g2.fill(ellipse);
}
}
}
protected void drawImageMosaic(Graphics2D g2) {
// Break the image up into tiles. Draw each
// tile with its own transparency, allowing
// the background to show through to varying
// degrees.
int side = 36;
int width = mImage.getWidth();
int height = mImage.getHeight();
for (int y = 0; y < height; y += side) {
for (int x = 0; x < width; x += side) {
// Calculate an appropriate transparency value.
float xBias = (float)x / (float)width;
float yBias = (float)y / (float)height;
float alpha = 1.0f - Math.abs(xBias - yBias);
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha));
// Draw the subimage.
int w = Math.min(side, width - x);
int h = Math.min(side, height - y);
BufferedImage tile = mImage.getSubimage(x, y, w, h);
g2.drawImage(tile, x, y, null);
}
}
// Reset the composite.
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
}
protected void drawText(Graphics2D g2) {
// Find the bounds of the entire string.
FontRenderContext frc = g2.getFontRenderContext();
mLayout = new TextLayout(mMessage, mFont, frc);
// Find the dimensions of this component.
int width = getSize().width;
int height = getSize().height;
// Place the first full string, horizontally centered,
// at the bottom of the component.
Rectangle2D bounds = mLayout.getBounds();
double x = (width - bounds.getWidth()) / 2;
double y = height - bounds.getHeight();
drawString(g2, x, y, 0);
// Now draw a second version, anchored to the right side
// of the component and rotated by -PI / 2.
drawString(g2, width - bounds.getHeight(), y, -Math.PI / 2);
}
protected void drawString(Graphics2D g2,
double x, double y, double theta) {
// Transform to the requested location.
g2.translate(x, y);
// Rotate by the requested angle.
g2.rotate(theta);
// Draw the first part of the string.
String first = mMessage.substring(0, mSplit);
float width = drawBoxedString(g2, first, Color.white, Color.red, 0);
// Draw the second part of the string.
String second = mMessage.substring(mSplit);
drawBoxedString(g2, second, Color.blue, Color.white, width);
// Undo the transformations.
g2.rotate(-theta);
g2.translate(-x, -y);
}
protected float drawBoxedString(Graphics2D g2,
String s, Color c1, Color c2, double x) {
// Calculate the width of the string.
FontRenderContext frc = g2.getFontRenderContext();
TextLayout subLayout = new TextLayout(s, mFont, frc);
float advance = subLayout.getAdvance();
// Fill the background rectangle with a gradient.
GradientPaint gradient = new GradientPaint((float)x, 0, c1,
(float)(x + advance), 0, c2);
g2.setPaint(gradient);
Rectangle2D bounds = mLayout.getBounds();
Rectangle2D back = new Rectangle2D.Double(x, 0,
advance, bounds.getHeight());
g2.fill(back);
// Draw the string over the gradient rectangle.
g2.setPaint(Color.white);
g2.setFont(mFont);
g2.drawString(s, (float)x, (float)-bounds.getY());
return advance;
}
}Graphics2D class is the cornerstone of Java
2D. But what is it, exactly? And how does it work? In this chapter,
I'll lay the groundwork for the rest of the book by covering
the fundamental topics of the 2D API. I'll talk about the
Graphics2D class, compositing, and coordinate
spaces.Graphics2D class is
the rendering engine. Figure 2.1 shows this process
at a high level. The 2D rendering engine takes care of the details of
underlying devices and can accurately reproduce the geometry and
color of a drawing, regardless of the device that displays it.
Graphics2D also represents a drawing
surface, which is simply some collection of pixels, each
of which holds a color. It might be the inside of a window or a page
in a printer, or even an offscreen image. Each time you draw
something new, the new element is added to the existing drawing
represented by the Graphics2D.Graphics2D uses its internal state to decide
exactly how graphics primitives are converted to pixel colors. For
example, part of Graphics2D's internal state
is a java.awt.Paint object, which describes the
colors that should be used to fill shapes. Whenever you ask
Graphics2D to fill a shape, it uses its current
Paint to fill the shape.
Graphics2DGraphics2D class is
the rendering engine. Figure 2.1 shows this process
at a high level. The 2D rendering engine takes care of the details of
underlying devices and can accurately reproduce the geometry and
color of a drawing, regardless of the device that displays it.
Graphics2D also represents a drawing
surface, which is simply some collection of pixels, each
of which holds a color. It might be the inside of a window or a page
in a printer, or even an offscreen image. Each time you draw
something new, the new element is added to the existing drawing
represented by the Graphics2D.Graphics2D uses its internal state to decide
exactly how graphics primitives are converted to pixel colors. For
example, part of Graphics2D's internal state
is a java.awt.Paint object, which describes the
colors that should be used to fill shapes. Whenever you ask
Graphics2D to fill a shape, it uses its current
Paint to fill the shape.
Graphics2D
's
internal state is comprised of seven elements:Graphics2D
uses the current stroke for shapes that are passed to its
draw() method. The stroke determines how the
outline of the shape is drawn. The resulting shape (the stroked
outline) is then filled.Graphics2D's transformation converts
primitives from User Space to Device Space. By default,
Graphics2D creates a transformation that maps 72
User coordinates to one inch on the output device.
Graphics2D. A compositing
rule determines how the colors of a new graphics
primitive are combined with the existing colors on a drawing surface,
as shown in Figure 2.6.
Shape and PathIterator
java.awt.geom
packagejava.awt.geom.Point2D class encapsulates a single
point (an x and a y) in
User Space. It is the most basic of the Java 2D classes and is used
throughout the API. Note that a point is not the same as a pixel. A
pixel is a tiny square (ideally) on a screen or printer that contains
some color. A point, by contrast, has no area, so it can't be
rendered. Points are used to build rectangles or other shapes that
have area and can be rendered.Point2D demonstrates an inheritance pattern that
is used throughout java.awt.geom. In particular,
Point2D is an abstract class with inner child
classes that provide concrete implementations. Figure 3.1 shows Point2D's
family tree. It's a pattern that you'll see again and
again in the java.awt.geom package.
Point2D
represents a point in User Space, but
it doesn't specify how the point's coordinates are
stored. The subclasses provide different levels of precision for
storing the coordinates of the point. The original
java.awt.Point, which dates back to JDK 1.0,
stores the coordinates as integers. Java 2D provides
Point2D.Float and
Point2D.Double for higher precision.java.awt.geom.Point2D class encapsulates a single
point (an x and a y) in
User Space. It is the most basic of the Java 2D classes and is used
throughout the API. Note that a point is not the same as a pixel. A
pixel is a tiny square (ideally) on a screen or printer that contains
some color. A point, by contrast, has no area, so it can't be
rendered. Points are used to build rectangles or other shapes that
have area and can be rendered.Point2D demonstrates an inheritance pattern that
is used throughout java.awt.geom. In particular,
Point2D is an abstract class with inner child
classes that provide concrete implementations. Figure 3.1 shows Point2D's
family tree. It's a pattern that you'll see again and
again in the java.awt.geom package.
Point2D
represents a point in User Space, but
it doesn't specify how the point's coordinates are
stored. The subclasses provide different levels of precision for
storing the coordinates of the point. The original
java.awt.Point, which dates back to JDK 1.0,
stores the coordinates as integers. Java 2D provides
Point2D.Float and
Point2D.Double for higher precision.Graphics2D class is the rendering engine for the
Java 2D API. Two of its basic operations are filling shapes and
drawing their outlines. But Graphics2D
doesn't know much about geometry, as the song says. In fact,
Graphics2D only knows how to draw one thing: a
java.awt.Shape. The Shape
interface represents a geometric shape, something that has an outline
and an interior. With Graphics2D, you can draw the
border of the shape using draw(), and you can fill
the inside of a shape using
fill().
java.awt.geom package is a toolbox of useful
classes that implement the Shape interface. There
are classes that represent ellipses, arcs, rectangles, and lines.
First, I'll talk about the Shape interface,
and then briefly discuss the java.awt.geom
package.java.awt.Shape interface is one of the common
currencies of the 2D API. It contains four groups of methods:
getBounds(), contains(),
intersects(), and
getPathIterator().getBounds() methods return rectangles that
completely enclose a Shape:Shape interface, so they can be rendered and
manipulated like any other Shape. Although you
could create a single straight or curved line segment yourself using
GeneralPath, it's easier to use these canned
shape classes. It's interesting that these classes are
Shapes, even though they represent the basic
segment types that make up a Shape's path.Point2D, Line2D is
abstract. Subclasses can store coordinates in any way they wish.
Figure 3.11 shows the hierarchy.
Line2D
includes
several setLine() methods you can use to set a
line's endpoints:x1,
y1, and x2,
y2.p1
and p2.Line2D.Float class.
Two of them allow you to specify the endpoints of the line, which
saves you the trouble of calling setLine().java.awt.geom.RectangularShape is an important
class. It's the abstract parent class of the rectangle, rounded
rectangle, arc, and ellipse classes in
java.awt.geom. It implements the methods of
Shape and adds a few of its own.RectangularShape returns information about its
location and size:java.awt.geom.Ellipse2D is abstract. A concrete
inner subclass, Ellipse2D.Float, stores its
coordinates as floats:Ellipse2D.Float using the specified location,
width, and height.Ellipse2D.Double, offers a
corresponding constructor:Ellipse2D is a descendent of
RectangularShape. While this may not seem very
intuitive, it does mean that Ellipse2D inherits
all of Rectangular-Shape's methods. As with
round rectangles, an ellipse's location (x
and y) is outside the outline of the ellipse.java.awt.geom.Arc2D for drawing pieces of an
ellipse. Arc2D defines three different kinds of
arcs, as shown in Figure 3.18. These are represented
by constants in the Arc2D class:java.awt.geom.Area class supports constructive
area geometry. This class offers two constructors:Area. You can
accumulate area using the add() method, described
below.Area using the
interior of the supplied Shape.
setPaint(), setStroke(),
GradientPaint, and BasicStroke.
I'll explain these classes and methods, and more, in the rest
of the chapter. For now, this example should show you some of the
potential of painting and stroking:import java.awt.*;
import java.awt.geom.*;
public class PaintingAndStroking
extends ApplicationFrame {
public static void main(String[] args) {
PaintingAndStroking f = new PaintingAndStroking();
f.setTitle("PaintingAndStroking v1.0");
f.setSize(300, 150);
f.center();
f.setVisible(true);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
double x = 15, y = 50, w = 70, h = 70;
Ellipse2D e = new Ellipse2D.Double(x, y, w, h);
GradientPaint gp = new GradientPaint(75, 75, Color.white,
95, 95, Color.gray, true);
// Fill with a gradient.
g2.setPaint(gp);
g2.fill(e);
// Stroke with a solid color.
e.setFrame(x + 100, y, w, h);
g2.setPaint(Color.black);
g2.setStroke(new BasicStroke(8));
g2.draw(e);
// Stroke with a gradient.
e.setFrame(x + 200, y, w, h);
g2.setPaint(gp);
g2.draw(e);
}
}Graphics2D how to fill shapes with
a call to setPaint(). This method accepts any
object that implements the java.awt.Paint
interface. The Graphics2D stores the
Paint away as part of its state. When it comes
time to fill a shape, Graphics2D will use the
Paint to determine what colors should be used to
fill the shape. The 2D API comes with three kinds of
"canned" paints: solid colors, a linear color gradient,
and a texture fill. You can add your own Paint
implementations if you
wish.
Graphics2D to fill a shape by
passing it to fill().Paints are immutable, which
means they can't be modified after they are created. The reason
for this is to avoid funky behavior when rendering. Imagine, for
example, if you wanted to fill a series of shapes with a solid color.
First, you'd call setPaint() on the
Graphics2D; then you would paint the shapes using
fill(). But what if another part of your program
changed the Paint that
Graphics2D was using? The results might be quite
bizarre. For this reason, objects that implement Paint
should not allow themselves to be changed after they are
created.