New York
University
Computer
Science Department
Courant
Institute of Mathematical Sciences
Session 4:
Graphics and Multimedia Components – Java 2D
Course Title: Extreme Java Course
Number: g22.3033-007
Instructor: Jean-Claude Franchitti Session: 4
1. Introduction to Java2D
1.1 Overview
In Java 1.2, the paintComponent method is supplied with a Graphics2D object (a subclass of Graphics), which contains a much richer set of drawing operations. It includes pen widths, dashed lines, image and gradient color fill patterns, the use of arbitrary local fonts, a floating point coordinate system, and a number of coordinate transformation operations. However, to maintain compatibility with Swing as used in Java 1.1, the declared type of the paintComponent argument is Graphics, so you have to cast it to Graphics2D before using it.
|
Java 1.1 |
|
Java 1.2 |
|
Public void paint(Graphics g) { // Set pen parameters g.setColor(someColor); g.setFont(someLimitedFont); // Draw a shape g.drawString(...); g.drawLine(...) g.drawRect(...); // outline g.fillRect(...); // solid g.drawPolygon(...); // outline g.fillPolygon(...); // solid g.drawOval(...); // outline g.fillOval(...); // solid ... } |
|
public void paintComponent(Graphics g) { // Clear off-screen bitmap super.paintComponent(g); // Cast Graphics to Graphics2D Graphics2D g2d = (Graphics2D)g; // Set pen parameters g2d.setPaint(fillColorOrPattern); g2d.setStroke(penThicknessOrPattern); g2d.setComposite(someAlphaComposite); g2d.setFont(anyFont); g2d.translate(...); g2d.rotate(...); g2d.scale(...); g2d.shear(...); g2d.setTransform(someAffineTransform); // Allocate a shape SomeShape s = new SomeShape(...); // Draw shape g2d.draw(s); // outline g2d.fill(s); // solid } |
1.2 Main New Features
·
Colors
and patterns: gradient fills, fill patterns from tiled images, transparency
·
Local
fonts
·
Pen
thicknesses, dashing patterns, and segment connection styles
·
Coordinate
transformations
1.3 General Approach
·
Cast the Graphics object to
a Graphics2D object
public void paintComponent(Graphics g) {
super.paintComponent(g); // Typical Swing approach
Graphics2D g2d = (Graphics2D)g;
g2d.doSomeStuff(...);
...
}
·
Create a Shape object
Rectangle2D.Double rect = ...;
Ellipse2D.Double ellipse = ...;
Polygon poly = ...;
GeneralPath path = ...;
SomeShapeYouDefined shape = ...; // Satisfies Shape interface
...
·
Optional: modify drawing
parameters
g2d.setPaint(fillColorOrPattern);
g2d.setStroke(penThicknessOrPattern);
g2d.setComposite(someAlphaComposite);
g2d.setFont(someFont);
g2d.translate(...);
g2d.rotate(...);
g2d.scale(...);
g2d.shear(...);
g2d.setTransform(someAffineTransform);
·
Draw an outlined or solid
version of the Shape
g2d.draw(someShape);
g2d.fill(someShape);
2. Drawing Shapes in Java2D
2.1 Drawing Shapes: Overview
With the AWT, you generally drew a shape by calling the drawXxx or fillXxx method of the Graphics object. In Java2D, you generally create a Shape object, then call either the draw or fill method of the Graphics2D object, supplying the Shape object as an argument. For example:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
// Assume x, y, and diameter are instance variables
Ellipse2D.Double
circle =
new Ellipse2D.double(x, y, diameter, diameter);
g2d.fill(circle);
...
}
You can still call the drawXxx methods if you like, however. This is necessary for drawString and drawImage, and possibly convenient for draw3DRect. Several classes have similar versions that store coordinates as either double precision numbers (Xxx.Double) or single precision numbers (Xxx.Float). The idea is that single precision coordinates might be slightly faster to manipulate on some platforms.
2.2 Shape Classes
Arguments to the Graphics2D draw and fill methods must implement the Shape interface. You can create your own shapes, of course, but following are the major built-in ones. Except for Rectangle and Polygon, which are Java 1.1 holdovers, these appear in the java.awt.geom package.
·
Arc2D.Double,
Arc2D.Float
·
Area
(a shape built by adding/subtracting other shapes)
·
CubicCurve2D.Double,
CubicCurve2D.Float
·
Ellipse2D.Double,
Ellipse2D.Float
·
GeneralPath
(a series of connected shapes)
·
Line2D.Double,
Line2D.Float
·
Polygon
·
QuadCurve2D.Double,
QuadCurve2D.Float
·
Rectangle2D.Double,
Rectangle2D.Float, Rectangle
·
RoundRectangle2D.Double,
RoundRectangle2D.Float
2.3 Drawing Shapes: Example Code
ShapeExample.java
import javax.swing.*; // For JPanel, etc.
import java.awt.*; // For Graphics, etc.
import java.awt.geom.*; // For Ellipse2D, etc.
/** An example of drawing/filling shapes with Java2D in Java 1.2.
*/
public class ShapeExample extends JPanel {
private
Ellipse2D.Double circle =
new Ellipse2D.Double(10, 10, 350, 350);
private Rectangle2D.Double square =
new Rectangle2D.Double(10, 10, 350, 350);
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
g2d.fill(circle);
g2d.draw(square);
}
// super.paintComponent clears offscreen pixmap,
// since we're using double buffering by default.
protected void clear(Graphics g) {
super.paintComponent(g);
}
protected Ellipse2D.Double getCircle() {
return(circle);
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new ShapeExample(), 380, 400);
}
}
WindowUtilities.java
import javax.swing.*;
import java.awt.*;
/** A few utilities that simplify testing of windows in Swing.
*/
public class WindowUtilities {
/** Tell system to use native look and feel, as in previous
* releases. Metal (Java) LAF is the default otherwise.
*/
public static void setNativeLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {
System.out.println("Error setting native LAF: " + e);
}
}
/** A simplified way to see a JPanel or other Container.
* Pops up a JFrame with specified Container as the content pane.
*/
public static JFrame openInJFrame(Container content,
int width,
int height,
String title,
Color bgColor) {
JFrame frame = new JFrame(title);
frame.setBackground(bgColor);
content.setBackground(bgColor);
frame.setSize(width, height);
frame.setContentPane(content);
frame.addWindowListener(new ExitListener());
frame.setVisible(true);
return(frame);
}
/** Uses Color.white as the background color. */
public static JFrame openInJFrame(Container content,
int width,
int height,
String title) {
return(openInJFrame(content, width, height, title, Color.white));
}
/** Uses Color.white as the background color, and the
* name of the Container's class as the JFrame title.
*/
public static JFrame openInJFrame(Container content,
int width,
int height) {
return(openInJFrame(content, width, height,
content.getClass().getName(),
Color.white));
}
}
ExitListener.java
import java.awt.*;
import java.awt.event.*;
/** A listener that you attach to the top-level Frame or JFrame of
* your application, so quitting the frame exits the application.
*/
public class ExitListener extends WindowAdapter {
public void windowClosing(WindowEvent event) {
System.exit(0);
}
}
2.4 Drawing Shapes: Example Output

3. Paint Styles in Java2D
3.1 Paint Styles: Overview
When you fill a Shape, the current Paint attribute of the Graphics2D object is used. This can be a Color (solid color), a GradientPaint (gradient fill gradually combining two colors), a TexturePaint (tiled image), or a new version of Paint that you write yourself. Use setPaint and getPaint to change and retrieve the Paint settings. Note that setPaint and getPaint supersede the setColor and getColor methods that were used in Graphics.
3.2 Paint Classes
Arguments to the Graphics2D setPaint method (and
return values of getPaint) must implement the Paint interface. Here are the
major built-in Paint classes:
·
Color
Has the same constants (Color.red, Color.yellow, etc.) as the AWT version, plus
some extra constructors.
·
GradientPaint
Constructors takes two points, two colors, and optionally a boolean flag that
indicates that the color pattern should cycle. The first color is used at the
first point, the second color at the second point, and points in between are
colored based on how close they are to each of the points.
·
TexturePaint
Constructor takes a BufferedImage and a Rectangle2D, maps the image to the
rectangle, then tiles the rectangle. Creating a BufferedImage from a GIF or
JPEG file is a pain. First load an Image normally, get its size, create a BufferedImage that
size with BufferedImage.TYPE_INT_ARGB as the image type, get the
BufferedImage's Graphics object via createGraphics, then draw the Image into
the BufferedImage using drawImage. An example of this process is shown later.
3.3 Transparency
Transparency is not set in the Paint object, rather separately via an AlphaComposite object that is applied via setComposite. See for details.
3.4 Gradient Fills: Example Code
import java.awt.*;
/** An example of gradient fills with Java2D in Java 1.2.
*/
public class GradientPaintExample extends ShapeExample {
// Red at (0,0), yellow at (175,175), changes gradually between.
private
GradientPaint gradient =
new GradientPaint(0, 0, Color.red, 175, 175, Color.yellow,
true); // true means to repeat pattern
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
drawGradientCircle(g2d);
}
protected void drawGradientCircle(Graphics2D g2d) {
g2d.setPaint(gradient);
g2d.fill(getCircle());
g2d.setPaint(Color.black);
g2d.draw(getCircle());
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new GradientPaintExample(),
380, 400);
}
}
3.5 Gradient Fills: Example Output

3.6 Tiled Images as Fill Patterns -- Overview
To use tiled images, you create a TexturePaint
object and specify its use via the setPaint method of Graphics2D, just as with
solid colors and gradient fills. The TexturePaint constructor takes a
BufferedImage and a Rectangle2D as arguments. The BufferedImage specifies what
to draw, and the Rectangle2D specifies where the tiling starts. Creating a
BufferedImage to hold custom drawing is relatively straightforward: call the
BufferedImage constructor with a width, a height, and a type of
BufferedImage.TYPE_INT_RGB, then call createGraphics on that to get a
Graphics2D with which to draw. It is a bit harder to create one from an image
file. First load an Image from an image file, then use MediaTracker to be sure
it is done loading, then create an empty BufferedImage using the Image width
and height, then get the Graphics2D via createGraphics, then draw the Image
onto the BufferedImage. This process has been wrapped up in the getBufferedImage
method of the ImageUtilities class.
Note, however,
that as of JDK1.2beta4, tiled images fail when used in conjunction with
rotation transformations.
3.7 Tiled Images as Fill Patterns: Example Code
TiledImages.java
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
/** An example of using TexturePaint to fill objects with tiled
* images. Uses the getBufferedImage method of ImageUtilities to
* load an Image from a file and turn that into a BufferedImage.
*/
public class TiledImages extends JPanel {
private String dir = System.getProperty("user.dir");
private String imageFile1 = dir + "/images/xyz.jpg";
private TexturePaint imagePaint1;
private Rectangle imageRect;
private String imageFile2 = dir + "/images/bluedrop.gif";
private TexturePaint imagePaint2;
private int[] xPoints = { 30, 700, 400 };
private int[] yPoints = { 30, 30, 600 };
private Polygon imageTriangle = new Polygon(xPoints, yPoints, 3);
public TiledImages() {
BufferedImage image =
ImageUtilities.getBufferedImage(imageFile1, this);
imageRect =
new Rectangle(235, 70, image.getWidth(), image.getHeight());
imagePaint1
=
new TexturePaint(image, imageRect);
image = ImageUtilities.getBufferedImage(imageFile2, this);
imagePaint2
=
new TexturePaint(image, new Rectangle(0, 0, 32, 32));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setPaint(imagePaint2);
g2d.fill(imageTriangle);
g2d.setPaint(Color.blue);
g2d.setStroke(new BasicStroke(5));
g2d.draw(imageTriangle);
g2d.setPaint(imagePaint1);
g2d.fill(imageRect);
g2d.setPaint(Color.black);
g2d.draw(imageRect);
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new TiledImages(), 750, 650);
}
}
ImageUtilities.java
import java.awt.*;
import java.awt.image.*;
/** A class that simplifies a few common image operations, in
* particular creating a BufferedImage from an image file, and
* using MediaTracker to wait until an image or several images are
* done loading.
*/
public class ImageUtilities {
/** Create Image from a file, then turn that into a BufferedImage.
*/
public
static BufferedImage getBufferedImage(String imageFile,
Component c) {
Image image = c.getToolkit().getImage(imageFile);
waitForImage(image, c);
BufferedImage bufferedImage =
new BufferedImage(image.getWidth(c), image.getHeight(c),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.drawImage(image, 0, 0, c);
return(bufferedImage);
}
/** Take an Image associated with a file, and wait until it is
* done loading. Just a simple application of MediaTracker.
* If you are loading multiple images, don't use this
* consecutive times; instead use the version that takes
* an array of images.
*/
public static boolean waitForImage(Image image, Component c) {
MediaTracker tracker = new MediaTracker(c);
tracker.addImage(image, 0);
try {
tracker.waitForAll();
} catch(InterruptedException ie) {}
return(!tracker.isErrorAny());
}
/** Take some Images associated with files, and wait until they
* are done loading. Just a simple application of MediaTracker.
*/
public static boolean waitForImages(Image[] images, Component c) {
MediaTracker tracker = new MediaTracker(c);
for(int i=0; i<images.length; i++)
tracker.addImage(images[i], 0);
try {
tracker.waitForAll();
} catch(InterruptedException ie) {}
return(!tracker.isErrorAny());
}
}
3.8 Tiled Images as Fill Patterns: Example Output

4. Transparency in Java2D
4.1 Transparency: Overview
Java2D permits you to assign transparency (alpha)
values to drawing operations so that the underlying graphics partially shows
through when you draw shapes or images. You set transparency by creating an
AlphaComposite object then passing it to the setComposite method of the
Graphics2D object. You create an AlphaComposite by calling AlphaComposite.getInstance
with a mixing rule designator and a transparency (or "alpha") value.
There are 8 built-in mixing rules (see the AlphaComposite
API for details), but the one normally used for drawing with transparency
settings is AlphaComposite.SRC_OVER. Alpha values range from 0.0F (completely
transparent) to 1.0F (completely opaque).
4.2 Transparency: Example Code
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
/** An illustration of the use of AlphaComposite to make partially
* transparent drawings.
*/
public class TransparencyExample extends JPanel {
private static int gap=10, width=60, offset=20,
deltaX=gap+width+offset;
private Rectangle
blueSquare = new Rectangle(gap+offset, gap+offset, width, width),
redSquare = new Rectangle(gap, gap, width, width);
private
AlphaComposite makeComposite(float alpha) {
int type = AlphaComposite.SRC_OVER;
return(AlphaComposite.getInstance(type, alpha));
}
private void drawSquares(Graphics2D g2d, float alpha) {
Composite originalComposite = g2d.getComposite();
g2d.setPaint(Color.blue);
g2d.fill(blueSquare);
g2d.setComposite(makeComposite(alpha));
g2d.setPaint(Color.red);
g2d.fill(redSquare);
g2d.setComposite(originalComposite);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
for(int i=0; i<11; i++) {
drawSquares(g2d, i*0.1F);
g2d.translate(deltaX, 0);
}
}
public static void main(String[] args) {
String title = "Transparency example: alpha of the top (red) " +
"square ranges from 0.0 at the left to 1.0 at " +
"the right. Bottom (blue) square is opaque.";
WindowUtilities.openInJFrame(new TransparencyExample(),
11*deltaX + 2*gap, deltaX + 3*gap,
title, Color.lightGray);
}
}
4.3 Transparency: Example Output

5. Using Local Fonts in Java2D
5.1 Local Fonts: Overview
You can use the same logical font names as in Java 1.1, namely Serif (e.g. Times), SansSerif (e.g. Helvetica or Arial), Monospaced (e.g. Courier), Dialog, and DialogInput.
You can also use arbitrary local fonts if you first look up the entire list,
which may take a few seconds. Lookup the fonts via the
getAvailableFontFamilyNames or getAllFonts methods of GraphicsEnvironment.
E.g.:
GraphicsEnvironment env =
GrapicsEnvironment.getLocalGraphicsEnvironment();
env.getAvailableFontFamilyNames();
or
env.getAllFonts(); // Much slower!
Despite a
misleading description in the API, trying to use an available local font in JDK
1.2 without first looking up the fonts as above gives the same result as asking
for an unavailable font: a default font instead of the actual one. Note that getAllFonts returns an array of real Font objects that you can use
like any other Font, but is much slower. If all you need to do is
tell Java to make all local fonts available, always use getAvailableFontFamilyNames.
The best approach would be to loop down
getAvailableFontFamilyNames, checking for your name, having several backup
names to use if the first choice is not available. If you pass an unavailable
family name to the Font constructor, a default font (SansSerif) will be used.
5.2 Example 1 -- Printing Out All Local Font Names
import java.awt.*;
/** Lists the names of all available fonts with Java2D in Java 1.2.
*/
public class ListFonts {
public static void main(String[] args) {
GraphicsEnvironment
env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontNames = env.getAvailableFontFamilyNames();
System.out.println("Available Fonts:");
for(int i=0; i<fontNames.length; i++)
System.out.println(" " + fontNames[i]);
}
}
5.3 Example 2 -- Drawing with Local Fonts
import java.awt.*;
/** An example of using local fonts with Java2D in Java 1.2.
*/
public class FontExample extends GradientPaintExample {
public FontExample() {
GraphicsEnvironment
env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
env.getAvailableFontFamilyNames();
setFont(new Font("Goudy Handtooled BT", Font.PLAIN, 100));
}
protected void drawBigString(Graphics2D g2d) {
g2d.setPaint(Color.black);
g2d.drawString("Java 2D", 25, 215);
}
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
drawGradientCircle(g2d);
drawBigString(g2d);
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new FontExample(), 380, 400);
}
}
5.4 Drawing with Local Fonts: Example Output

6. Stroke Styles in Java2D
6.1 Stroke Styles: Overview
In the AWT, the drawXxx methods of Graphics resulted in solid, 1-pixel wide lines. Furthermore, drawing commands that consisted of multiple line segments (e.g. drawRect and drawPolygon) had a predefined way of joining the line segments together and terminating segments that do not join to others. Java2D gives you much more flexibility. In addition to setting the pen color or pattern (via setPaint, as discussed in the previous section), Java2D permits you to set the pen thickness and dashing pattern, and to specify the way line segments end and are joined together. You do this by creating a BasicStroke object, then telling the Graphics2D object to use it via the setStroke method.
6.2 Stroke Attributes
Arguments to setStroke must implement the Stroke
interface, and the BasicStroke class is the sole builtin class that implements
Stroke. Here are the BasicStroke constructors:
·
BasicStroke()
Creates a BasicStroke with a pen width of 1.0, the default cap style of
CAP_SQUARE, and the default join style of JOIN_MITER. See the following
examples of pen widths and cap/join styles.
·
BasicStroke(float penWidth)
Uses the specified pen width and the default cap/join styles (CAP_SQUARE and JOIN_MITER).
·
BasicStroke(float penWidth,
int capStyle, int joinStyle)
Uses the specified pen width, cap style, and join style. The cap style can be
one of CAP_SQUARE (make a square cap that extends past the end point by half
the pen width -- this is the default), CAP_BUTT (cut off segment exactly at end
point -- use this one for dashed lines), or CAP_ROUND (make a circular cap
centered on the end point, with a diameter of the pen width). The join style
can be one of JOIN_MITER (extend outside edges of lines until they meet -- this
is the default), JOIN_BEVEL (connect outside corners of outlines with straight
line), or JOIN_ROUND (round off corner with circle with diameter equal to the
pen width).
·
BasicStroke(float penWidth,
int capStyle, int joinStyle, float miterLimit)
Same as above but you can limit how far up the miter join can go (default is
10.0). Stay away from this.
·
BasicStroke(float penWidth,
int capStyle, int joinStyle, float miterLimit, float[] dashPattern, float
dashOffset)
Lets you make dashed lines by specifying an array of opaque (entries at even
array indices) and transparent (odd indices) segments. The offset, which is
often 0.0, specifies where to start in the dashing pattern.
6.3 Stroke Thickness: Example Code
import java.awt.*;
/** An example of Stroke (pen) widths with Java2D in Java 1.2.
*/
public class StrokeThicknessExample extends FontExample {
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
drawGradientCircle(g2d);
drawBigString(g2d);
drawThickCircleOutline(g2d);
}
protected void drawThickCircleOutline(Graphics2D g2d) {
g2d.setPaint(Color.blue);
g2d.setStroke(new BasicStroke(8)); // 8-pixel wide pen
g2d.draw(getCircle());
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new StrokeThicknessExample(),
380, 400);
}
}
6.4 Stroke Thickness: Example Output

6.5 Dashed Lines: Example Code
import java.awt.*;
/** An example of dashed lines with Java2D in Java 1.2.
*/
public class DashedStrokeExample extends FontExample {
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
drawGradientCircle(g2d);
drawBigString(g2d);
drawDashedCircleOutline(g2d);
}
protected void drawDashedCircleOutline(Graphics2D g2d) {
g2d.setPaint(Color.blue);
// 30 pixel line, 10 pixel gap, 10 pixel line, 10 pixel gap
float[]
dashPattern = { 30, 10, 10, 10 };
g2d.setStroke(new BasicStroke(8, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10,
dashPattern, 0));
g2d.draw(getCircle());
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new DashedStrokeExample(),
380, 400);
}
}
6.6 Dashed Lines: Example Output

6.7 Line Cap and Join Styles: Example Code
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
/** An example of line cap and join styles with Java2D in Java 1.2.
*/
public class LineStyles extends JPanel {
private GeneralPath path;
private static int x = 30, deltaX = 150, y = 300, deltaY = 250,
thickness = 40;
private Circle p1Large, p1Small, p2Large, p2Small, p3Large, p3Small;
private int compositeType = AlphaComposite.SRC_OVER;
private AlphaComposite transparentComposite =
AlphaComposite.getInstance(compositeType, 0.4F);
private
int[] caps =
{ BasicStroke.CAP_SQUARE, BasicStroke.CAP_BUTT,
BasicStroke.CAP_ROUND };
private String[] capNames =
{ "CAP_SQUARE", "CAP_BUTT", "CAP_ROUND" };
private
int[] joins =
{ BasicStroke.JOIN_MITER, BasicStroke.JOIN_BEVEL,
BasicStroke.JOIN_ROUND };
private String[] joinNames =
{ "JOIN_MITER", "JOIN_BEVEL", "JOIN_ROUND" };
public LineStyles() {
path = new GeneralPath();
path.moveTo(x, y);
p1Large = new Circle(x, y, thickness/2);
p1Small = new Circle(x, y, 2);
path.lineTo(x + deltaX, y - deltaY);
p2Large = new Circle(x + deltaX, y - deltaY, thickness/2);
p2Small = new Circle(x + deltaX, y - deltaY, 2);
path.lineTo(x + 2*deltaX, y);
p3Large = new Circle(x + 2*deltaX, y, thickness/2);
p3Small = new Circle(x + 2*deltaX, y, 2);
setForeground(Color.blue);
setFont(new Font("SansSerif", Font.BOLD, 20));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.blue);
for(int i=0; i>caps.length; i++) {
BasicStroke
stroke =
new BasicStroke(thickness, caps[i], joins[i]);
g2d.setStroke(stroke);
g2d.draw(path);
labelEndPoints(g2d, capNames[i], joinNames[i]);
g2d.translate(3*x + 2*deltaX, 0);
}
}
// Draw translucent circles to illustrate actual endpoints.
// Include text labels to shold cap/join style.
private void labelEndPoints(Graphics2D g2d,
String capLabel, String joinLabel) {
Paint origPaint = g2d.getPaint();
Composite origComposite = g2d.getComposite();
g2d.setPaint(Color.red);
g2d.setComposite(transparentComposite);
g2d.fill(p1Large);
g2d.fill(p2Large);
g2d.fill(p3Large);
g2d.setPaint(Color.yellow);
g2d.setComposite(origComposite);
g2d.fill(p1Small);
g2d.fill(p2Small);
g2d.fill(p3Small);
g2d.setPaint(Color.black);
g2d.drawString(capLabel, x + thickness - 5, y + 5);
g2d.drawString(joinLabel, x + deltaX + thickness - 5, y - deltaY);
g2d.setPaint(origPaint);
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new LineStyles(),
9*x + 6*deltaX, y + 60);
}
}
class Circle extends Ellipse2D.Double {
public Circle(double centerX, double centerY, double radius) {
super(centerX - radius, centerY - radius, 2.0*radius, 2.0*radius);
}
}
6.8 Line Cap and Join Styles: Example Output

7. Coordinate Transformations in Java2D
7.1 Coordinate Transformations: Overview
Java2D allows you to easily translate, rotate,
scale, or shear the coordinate system. This is very convenient: it is often
much easier to move the coordinate system than to calculate new coordinates for
each of your points. Besides, for some data structures like ellipses and
strings there is no other way to get rotated or stretched versions. The
meanings of translate, rotate, and scale are clear: to move, to spin, or to
stretch/shrink evenly in the x and/or y direction. Shear means to stretch unevenly: an x shear moves points to the
right based on how far they are from the y axis;
a y shear moves points down based on how far they are from the x axis.
The easiest way to picture what is happening is to
imagine that the person doing the drawing has a picture frame that he lays down
on top of a sheet of paper. The drawer always sits at the bottom of the frame.
To apply a translation, you move the frame (moving the drawer with it), and do
the drawing in the new location. You then move the frame back to its original
location, and what you now see is the final result. Similarly, for a rotation,
you spin the frame (and the drawer), draw, then spin back to see the result.
Similarly for scaling and shears; modify the frame without touching the
underlying sheet of paper, draw, then reverse the process to see the final
result.
An outside observer watching this process would see the frame move in the direction specified by the transformation, but see the sheet of paper stay fixed. This is illustrated in the second column in the diagram below. The dotted rectangle represents the frame, while the gray rectangle represents the sheet of paper. On the other hand, to the person doing the drawing it would appear that the sheet of paper moved in the opposite way from that specified in the transformation, but that he didn't move at all. This is illustrated in the third column in the following diagram. The first column illustrates the starting configuration, and the fourth illustrates the final result.

Figure 7-1: Visualizing Transformations
You can also perform more complex transformations
(e.g. creating a mirror image by flipping around a line) by directly manipulating
the underlying arrays that control the transformations. This is a bit more
complicated to envision than the basic translatation, rotation, scaling, and
shear transformations. The idea is that a new point (x2,y2)
can be derived from an original point (x1,y1) as follows:
[ x2] [ m00 m01 m02 ] [ x1
] [ m00x1 + m01y1
+ m02 ]
[ y2] = [ m10 m11 m12 ] [ y1 ] = [ m10x1
+ m11y1 + m12 ]
[ 1 ] [
0 0 1 ] [ 1 ]
[ 1 ]
Note that you can only supply six of the nine values
in the transformation array (the mxx values). The bottom row is
fixed at [ 0 0
1 ] to
guarantee that the transformations preserve "straightness" and
"parallelness" of lines. There are several ways of supplying this
array to the AffineTransform constructor; see the Affine Transform API for
details.
There are two basic ways to use transformations. You
can create an AffineTransform object, set its parameters, and then assign that
AffineTransform to the Graphics2D object via setTransform. This is your only
choice if you want to do the more complex transformations permitted by setting
explicit transformation matrices. Alternatively, for the basic transformations
you can call translate, rotate, scale, and shear directly on the Graphics2D
object.
7.2 Coordinate Translations and Rotations: Example Code
import java.awt.*;
/** An example of coordinate translations and
* rotations with Java2D in Java 1.2.
*/
public class RotationExample extends StrokeThicknessExample {
private Color[] colors = { Color.white, Color.black };
public void paintComponent(Graphics g) {
clear(g);
Graphics2D g2d = (Graphics2D)g;
drawGradientCircle(g2d);
drawThickCircleOutline(g2d);
// Move the origin to the center of the circle.
g2d.translate(185.0, 185.0);
for (int i=0; i<16; i++) {
// Rotate the coordinate system around current
// origin, which is at the center of the circle.
g2d.rotate(Math.PI/8.0);
g2d.setPaint(colors[i%2]);
g2d.drawString("Java", 0, 0);
}
}
public static void main(String[] args) {
WindowUtilities.openInJFrame(new RotationExample(), 380, 400);
}
}
7.3 Coordinate Translations and Rotations: Example Output

7.4 Shear Transformations
If you specify a non-zero x shear, then x values will be more and more shifted to the right the farther they are away from the y axis. For example, an x shear of 0.1 means that the x value will be shifted 10% of the distance the point is away from the y axis. Y shears are similar: points are shifted down in proportion to the distance they are away from the x axis.
7.5 Shear Transformations: Example Code
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
/** An example of shear transformations with Java2D in Java 1.2.
*/
public class ShearExample extends JPanel {
private static int gap=10, width=100;
private Rectangle rect = new Rectangle(gap, gap, 100, 100);
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
for (int i=0; i<5; i++) {
g2d.setPaint(Color.red);
g2d.fill(rect);
// Each new square gets 0.2 more x shear
g2d.shear(0.2, 0.0);
g2d.translate(2*gap + width, 0);
}
}
public static void main(String[] args) {
String title =
"Shear: x shear ranges from 0.0 for the leftmost 'square' " +
"to 0.8 for the rightmost one.";
WindowUtilities.openInJFrame(new ShearExample(),
20*gap + 5*width, 5*gap + width,
title);
}
}
7.6 Shear Transformations: Example Output

8. Conclusions
8.1 Requesting More Accurate Drawing: Rendering Hints
Since Java2D already does a lot of calculations
compared to the old AWT, there are several optional features that the designers
chose to disable by default in order to improve performance. Turning them on
results in crisper drawing, especially for rotated text. For example, the Jtable on envisioning transformations
resulted in excessively jagged text using the default settings. The most
important two settings are to turn on antialiasing (smooth jagged lines by
blending colors) and to simply request the highest-quality rendering. This
approach is illustrated below:
RenderingHints renderHints =
new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
...
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHints(renderHints);
...
}
8.2 Summary of Using Java2D
·
Cast
Graphics to Graphics2D wherever you have a Graphics object
·
Create
Shape objects, then call Graphics2D's draw and fill methods with shapes as
args.
·
Use
setPaint to specify a solid color (Color), a gradient fill (GradientPaint), or
tiled image (TexturePaint). TexturePaint requires a BufferedImage, which you
can create from an image file by creating empty BufferedImage then drawing
image into it.
·
Use
AlphaComposite for transparency. Create one via AlphaComposite.getInstance with
a type of AlphaComposite.SRC_OVER.
·
Local
fonts: before using them you must call
GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts() or
GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames().
Then supply name to Font constructor and specify font via setFont, as in Java
1.1.
·
BasicStroke
lets you set pen thickness, dashing pattern, and line cap/join styles. Supply
it to Graphics2D via setStroke.
·
Coordinate
transforms let you move the coordinate system (e.g. the piece of paper you're
drawing on) rather than changing what you draw. Do simple transforms via the
translate, rotate, scale, and shear methods of Graphics2D. Do more complex
transforms by supplying transformation matrix to AffineTransform constructor,
then calling Graphics2D's setTransform method.
·
Improve
drawing quality or enable antialiasing through use of rendering hints.
8.3 Other Capabilities of Java2D
·
High-quality
printing
·
Improved
XOR mode
·
Custom
color mixing (implement Composite
and CompositeContext
interfaces)
·
Bounds/hit
testing (see contains and intersects methods of Shape)
·
Fancy
text operations
·
Create
new fonts by transforming old ones (use Font.deriveFont)
·
Draw
multi-font or multi-color strings (use the draw method of TextLayout)
·
Draw
outlines of fonts, or fill fonts with images or gradient colors (use the
getOutline method of TextLayout)
· Low-level image processing and color model manipulation
8.4 Learning More About Java2D
· Tutorial on printing in Java2D