Chapter 23. Java2D
1. Java2D API overview
2. Rendering charts
3. Rendering text strings
4. Rendering images
In this chapter:
- Java2D API overview
- Rendering charts
- Rendering text strings
- Rendering images
23.1 Java2D API overview
Java 2 offers a very powerful new rendering model known as Java2D. This model consists of a set of classes and interfaces for advanced 2D line art, text, and image rendering. Although this API is not considered a part of Swing, it is closely related to Swing and may be effectively used to develop sophisticated Swing applications.
This chapter includes a Java2D API overview and shows how to use this API for chart rendering, enhanced label creation, and advanced image rendering. Below we briefly discuss the classes and interfaces that are fundamental to the 2D API. Note, however, that a complete description of all Java2D features lies beyond the scope of this book.
23.1.1 The Shape interface
abstract interface java.awt.Shape
This interface provides the definition for a 2D geometrical object. Most of the classes contained in the java.awt.geom package implement this interface. These classes define such things as points, lines, arcs, rectangles, round rectangles, ellipses, and more complex shapes such as cubic and quadratic parametric curves. These geometries allow a high degree of flexibility and with them, we can create almost any shape imaginable. We can render these geometries into a 2D graphics context using its draw() or fill() methods (see below). We can also perform boolean operations on multiple shapes such as union, intersection, exclusive or, etc. using the java.awt.geom.Area class. The geometry of each Shape's boundary is defined as a path which is represented by a set of line segments and curves encapsulated in a PathIterator instance (we will not discuss the details of this here).
Several overloaded contains() methods determine whether a given point or rectangle lies inside a Shape, and the getBounds() method returns a Shape's minimum bounding rectangle.
This class implements the Shape interface and represents a geometric path constructed from several line segments, or quadratic and cubic curves. Particularly important is its append(Shape s, boolean connect) method which provides us with a way to append one shape to another by optionally connecting their paths with a line segment.
GeneralPath maintains a current coordinate at all times which represents the coordinate that, if we were to add a line segment, would be the beginning of the added line segment. To do this we use its lineTo() method passing it two floats representing the destination coordinate. Similarly, we can use its moveTo() and quadTo() methods to add a point or curve to the path.
This class serves as a superclass for three classes: the well-known java.awt.Rectangle class, Rectangle2D.Double, and Rectangle2D.Float. These classes not only provide new ways to work with rectangles, but also allow us to specify a rectangle's coordinates in int, float, or double form. Along with Rectangle2D, the java.awt.geom package also includes a set of classes which provide new functionality to familiar graphical primitives, such as Dimension2D, Line2D, and Point2D. Each of these classes allow us to specify their coordinates using ints, floats, or doubles through appropriate subclasses.
This class encapsulates a general form affine transformation between two coordinate systems. This transformation is essentially a coordinate transformation represented by a 3x3 matrix with an implied last row ([0 0 1]) mapping each x and y in the bounding rectangle of a Shape to a new x' and y' according to the following:
x' = m00 x + m01 y + m02
y' = m10 x + m11 y + m12
The mxx's represent the first two rows of a 3x3 matrix. These formulas are quite simple to understand and can be rewritten, for most operations, as the following:
x' = (scaleX * x) + (shearX * y) + offsetX
y' = (scaleY * x) + (shearY * y) + offsetY
Thess transformations preserve lines and parallelism (i.e. parallel lines are mapped to parallel lines). We use them to perform scaling, shearing, and translation. To construct an AffineTransform we use either the double or float version of the following constructor:
AffineTransform(m00, m10, m01, m11, m02, m12)
Note the order of the parameters. This directly corresponds to the columns of our matrix described above.
Rotation also preserves parallelism. Given an angle of rotation in radians, q :
x' = x*(cosq) + y*(-sinq) + offsetX
y' = x*(sinq) + y*(cosq) + offsetY
Note that (degrees * p/180) = radians.
The Java2D graphics context (see below) maintains a transform attribute, just as it maintains a color and font attribute. Whenever we draw or fill a shape, this operation will be performed according to the current state of the transform attribute. We can create an instance of AffineTransform by specifying the first two rows of the matrix as described above. Alternatively, we can use static methods to create specific types of transformations: getRotateInstance(), getScaleInstance(), getShearInstance(), or getTranslateInstance(). We can use the concatenate() method to concatenate multiple transformations successively. We can also compose specific transformations with an existing AffineTransform using its rotate(), scale(), shear(), and translate() methods.
AffineTransforms are widely used throughout the 2D API as parameters to methods requiring a transformation to produce various visual effects.
23.1.5 The Stroke interface
abstract interface java.awt.Stroke
This interface defines only one method: createStrokedShape(Shape p), which generates a Shape that is the outline of the given Shape parameter. This outline can be of various size, shape, and decor. The only implementing class is BasicStroke (see below). We use Strokes to define line styles for drawing in the Java2D graphics context. To set the stroke attribute of a given Graphics2D we use its setStroke() method.
This class implements the Stroke interface and defines a set of rendering attributes specifying how to render the outline of a Shape. These attributes consist of line width, join style, end-cap style, and dash style:
The line width (often called the pen width) is the thickness measured perpendicular to its trajectory.
The end-cap style specifies whether round, butt, or square ends are used to render the ends of line segments: CAP_ROUND, CAP_BUTT, and CAP_SQUARE.
The join style specifies how to render the joints between segments. This can be one of bevel, miter, or round: JOIN_BEVEL, JOIN_MITER, and JOIN_ROUND.
The dash style defines a pattern of opaque and transparent regions rendered along a line segment.
23.1.7 The Paint interface
abstract interface java.awt.Paint
This interface defines how colors and color patterns may be assigned to the 2D graphics context for use in drawing and filling operations. Some important implementing classes are Color, GradientPaint, and TexturePaint. We use Paints to define fill patterns for filling in Shapes in the Java2D graphics context. To set the paint attribute of a given Graphics2D we use its setPaint() method.
This class implements the Paint interface and renders a shape by using a linear color gradient. The gradient is determined by two 2D points and two colors associated with them. The gradient can optionally be cyclical which means that between both points it will cycle through shades of each color several times, rather than just once. We use the Graphics2D.setPaint() method to assign a GradientPaint instance to a Graphics2D object. We can then call the fill() method to fill a specified Shape with this gradient. Note that this class provides an easy way to produce remarkable visual effects using only a few lines of code.
This class implements the Paint interface and is used to fill Shapes with a texture stored in a BufferedImage. We use the Graphics2D.setPaint() method to assign a TexturePaint instance to a Graphics2D object. We can call the fill() method to fill a specified Shape with this texture. Note that the BufferedImages used for a texture are expected to be small, as a TexturePaint object makes a copy of its data and stores it internally; it does not reference the provided BufferedImage. It is also important to reuse TexturePaint objects, rather than create new ones, whenever possible.
This class extends the java.awt.Graphics class to provide a more sophisticated API for working with geometry, transformations, colors, fill patterns and line styles, and text layout. In Java 2, the Graphics object passed to a component's paint() method is really a Graphics2D object. So we can use this class in our paint() implementation by simply casting our Graphics object to a Graphics2D:
public void paint(Graphics g)
Graphics2D g2 = (Graphics2D) g;
// Use Graphics2D ...
We can assign attributes to a Graphics2D instance using methods such as setTransform(), setStroke() or setPaint(), as we discussed above. We can then call draw() to outline a given Shape instance using the assigned Stroke, and we can call fill() to fill a given Shape with the assigned Color, GradientPaint, or TexturePaint. Depending on the state of the transform attribute, Shapes will be translated, rotated, scaled, or sheared appropriately as they are drawn (see AffineTransform). We can modify the current transform directly with methods rotate(), scale(), shear(), and translate(). We can also assign it a new transform using its setTransform() method, or compose the current transform with a given one using its transform() method.
A Graphics2D object can maintain preferences for specific rendering algorithms to use depending on whether speed or quality is the priority. These are called rendering hints. They can be assigned using the setRenderingHint() method and are stored as key/value pairs. Valid keys and values are defined in the RenderingHints class. Two of these pairs are especially important to us, as the examples in this chapter will always use them:
By setting the KEY_ANTIALIASING property to VALUE_ANTIALIAS_ON you can take advantage of a technique used to render objects with smoothly blended edges (by using intermediate colors to render a border between, say, black and white areas).
By setting the the KEY_RENDERING property to VALUE_RENDER_QUALITY, appropriate rendering algorithms will always be chosen to ensure the best output quality.
This class is capable of retrieving the collection of GraphicsDevice and Font instances available to a Java application on the running platform. GraphicsDevices can reside on the local machine or any number of remote machines. A GraphicsDevice instance describes, surprisingly, a graphics device such as a screen or printer.
Recall from chapter 2 that we normally reference GraphicsEnvironment to retrieve the names of all available fonts:
String fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().
This class represents an Image stored in memory providing methods for storing, interpreting, and rendering pixel data. It is used widely throughout the 2D API and we've already seen it in chapters 13 and 22. In particular, you can create a BufferedImage, retrieve its associated Graphics2D instance to render into, perform the rendering, and use the result as an image for, among other things, painting directly into another graphics context (we used this technique in the construction of our print preview component). This is also similar to how RepaintManager handles the buffering of all Swing components, as we discussed in chapter 2.
Instances of this class encapsulate information needed to correctly measure text. This includes rendering hints and target device specific information such as resolution (dots-per-inch). A FontRenderContext instance representing the current state of the 2D graphics context can be retrieved using Graphics2D's getFontRenderContext() method. FontRenderContext is usually used in association with text formatting using Fonts and TextLayouts.
Instances of this class represent an immutable graphical representation of styled text--that is, they cannot change (this class does not contain any set accessors). Only new instances can be created, and a FontRenderContext instance is required to do this. We render a TextLayout in the 2D graphcis context using that TextLayout's draw() method. This class is very powerful and supports such things as hit detection, which will return the character a mouse press occurs on, as well as support for bi-directional text and split cursors. A particularly noteworthy method is getOutline(AffineTransform tx), which returns a Shape instance outlining the text.