Java 2D API

 


 

Contents

  1. Overview
  2. Java 2D Rendering
  3. Coordinate Systems
  4. Shapes
  5. Text
  6. Images
  7. Printing
  8. Stroking and Filling Graphics Primitives
  9. Transforming Shapes, Text, and Images
  10. Clipping the Drawing Region
  11. Compositing Graphics
  12. Controlling Rendering Quality
  13. Constructing Complex Shapes from Geometry Primitives
  14. Supporting User Interaction
  15. Creating and Deriving Fonts
  16. Drawing Multiple Lines of Text
  17. Immediate-Mode Imaging with BufferedImage
  18. Filtering a BufferedImage
  19. Using a BufferedImage for Double Buffering
  20. Overview of Printing in Java
  21. Printing the Contents of a Component
  22. Displaying a Page Setup Dialog
  23. Printing a Collection of Pages
  24. Solving Common 2D Graphics Problems
  25. Improving Printing Performance

 


 

Overview

The Java 2D API provides two-dimensional graphics, text, and imaging capabilities for Java programs through extensions to the Abstract Windowing Toolkit (AWT). This rendering package supports line art, text, and images in a framework for developing user interfaces, drawing programs and image editors.

The Java 2D API provides

  1. A uniform rendering model for display devices and printers
  2. A wide range of geometric primitives, such as curves, rectangles, and ellipses and a mechanism for rendering virtually any geometric shape
  3. Mechanisms for performing hit detection on shapes, text, and images
  4. A compositing model that provides control over how overlapping objects are rendered
  5. Enhanced color support that facilitates color management
  6. Support for printing complex documents

 


 

Java 2D Rendering

The basic rendering mechanism is the same as in previous versions of the JDK, the drawing system controls when and how programs can draw. When a component needs to be displayed, its paint or update method is automatically invoked with an appropriate Graphics context.

The Java 2D API introduces java.awt.Graphics2D, a new type of Graphics object. Graphics2D extends the Graphics class to provide access to the enhanced graphics and rendering features of the Java 2D API.

To use Java 2D API features, you cast the Graphics object passed into a component's rendering method to a Graphics2D object.

public void Paint (Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    ...
}

 

 

Graphics2D Rendering Context

The collection of state attributes associated with a Graphics2D object is referred to as the Graphics2D rendering context. To display text, shapes, or images, you set up the Graphics2D rendering context and then call one of the Graphics2D rendering methods, such as draw or fill. As the following figure shows, the Graphics2D rendering context contains several attributes.

The pen style that is applied to the outline of a shape. This stroke attribute enables you to draw lines with any point size and dashing pattern and to apply end-cap and join decorations to a line.
The fill style that is applied to a shape's interior. This paint attribute enables you to fill shapes with solid colors, gradients, and patterns.
The compositing style that is used when rendered objects overlap existing objects.
The transform that is applied during rendering to convert the rendered object from user space to device-space coordinates. Optional translation, rotation, scaling, or shearing transforms can also be applied through this attribute.
The clip, which restricts rendering to the area within the outline of the Shape used to define the clipping path. Any Shape can be used to define the clip.

The font used to convert text strings to glyphs.

Rendering hints that specify preferences in the trade-offs between speed and quality. For example, you can specify whether antialiasing should be used, if it's available.

To set an attribute in the Graphics2D rendering context, you use the set Attribute methods:

  • setStroke
  • setPaint
  • setComposite
  • setTransform
  • setClip
  • setFont
  • setRenderingHints

When you set an attribute, you pass in the appropriate attribute object. For example, to change the paint attribute to a blue-green gradient fill, you would construct a GradientPaint object and then call setPaint.

gp = new GradientPaint(0f,0f,blue,0f,30f,green);
g2.setPaint(gp);

Graphics2D holds references to its attribute objects, they are not cloned. If you alter an attribute object that is part of the Graphics2D context, you need to call the appropriate set method to notify the context. Modifying an attribute object during rendering causes unpredictable behavior.

 

 

Graphics2D Rendering Methods

Graphics2D provides the following general rendering methods that can be used to draw any geometry primitive, text, or image:

draw renders the outline of any geometry primitive, using the stroke and paint attributes.
fill renders any geometry primitive by filling its interior with the color or pattern specified by the paint attribute.
drawString renders any text string. The font attribute is used to convert the string to glyphs, which are then filled with the color or pattern specified by the paint attribute.
drawImage renders the specified image.

In addition, Graphics2D supports the Graphics rendering methods for particular shapes, such as drawOval and fillRect.

 


 

Coordinate Systems

The Java 2D system maintains two coordinate spaces.

  • User space is the space in which graphics primitives are specified.
  • Device space is the coordinate system of an output device, such as a screen, window, or a printer.

User space is a device-independent logical coordinate system: the coordinate space that your program uses. All geometries passed into Java 2D rendering routines are specified in user-space coordinates.

When the default transformation from user space to device space is used, the origin of user space is the upper-left corner of the component's drawing area. The x coordinate increases to the right, and the y coordinate increases downward, as shown in the following figure.

Device space is a device-dependent coordinate system that varies according to the target rendering device. Although the coordinate system for a window or the screen might be very different from that of a printer, these differences are invisible to Java programs. The necessary conversions between user space and device space are performed automatically during rendering.

 


 

Shapes

The classes in the java.awt.geom package define common graphics primitives, such as points, lines, curves, arcs, rectangles, and ellipses.

 

 

Classes in the java.awt.geom Package

Arc2D Ellipse2D QuadCurve2D
Area GeneralPath Rectangle2D
CubicCurve2D Line2D RectangularShape
Dimension2D Point2D RoundRectangle2D

Except for Point2D and Dimension2D, each of the geometry classes (geometries) implements the Shape interface, which provides a common set of methods for describing and inspecting two-dimensional geometric objects.

With these classes you can create virtually any geometric shape and render it through Graphics2D by calling the draw method or the fill method. For example, the geometric shapes in the following ShapesDemo2D applet are defined by using basic Java 2D geometries.

If you're curious, the code for this program, is in ShapesDemo2D.java

 

 

Rectangular Shapes

The Rectangle2D, RoundRectangle2D, Arc2D, and Ellipse2D primitives are all derived from RectangularShape, which defines methods for Shape objects that can be described by a rectangular bounding box. The geometry of a RectangularShape can be extrapolated from a rectangle that completely encloses the outline of the Shape.

 

 

QuadCurve2D and CubicCurve2D

The QuadCurve2D class allows you to create quadratic parametric curve segments. A quadratic curve is defined by two endpoints and one control point.

The CubicCurve2D class allows you to create cubic parametric curve segments. A cubic curve is defined by two endpoints and two control points. The following figures demonstrate examples of quadratic and cubic curves.

 

 

GeneralPath

The GeneralPath class enables you to construct an arbitrary shape by specifying a series of positions along the shape's boundary. These positions can be connected by line segments, quadratic curves, or cubic (Bézier) curves. The shape pictured below can be created with three line segments and a cubic curve.

 

 

Areas

With the Area class you can perform boolean operations, such as union, intersection, and subtraction, on any two Shape objects. This technique, often referred to as constructive area geometry, enables you to quickly create complex Shape objects without having to describe each line segment or curve.

 


 

Text

When you need to display text, you can use one of the text-oriented components, such as the Swing How to Use Labels or Using Text Components. When you use a text component, a lot of the work is done for you, for example, JTextComponent objects provide built-in support for hit testing and displaying international text.

If you just want to draw a static text string, you can render it directly through Graphics2D by using the drawString method. To specify the font, you use the Graphics2D setFont method.

If you want to implement your own text-editing routines or need more control over the layout of the text than the text components provide, you can use the Java 2D text layout classes in java.awt.font.

 

 

Fonts

The shapes that a font uses to represent the characters in a string are called glyphs. A particular character or combination of characters might be represented as one or more glyphs. For example, á might be represented by two glyphs, whereas the ligature fi might be represented by a single glyph.

A font can be thought of as a collection of glyphs. A single font might have many faces, such as heavy, medium, oblique, gothic, and regular. All of the faces in a font have similar typographic features and can be recognized as members of the same family. In other words, a collection of glyphs with a particular style form a font face; a collection of font faces forms a font family; and the collection of font families forms the set fonts available on the system.

When you're using the Java 2D API, you specify fonts by using an instance of Font. You can determine what fonts are available by calling the static method GraphicsEnvironment.getLocalGraphicsEnvironment and then querying the returned GraphicsEnvironment. The getAllFonts method returns an array that contains Font instances for all of the fonts available on the system; getAvailableFontFamilyNames returns a list of the available font families.

The GraphicsEnvironment also describes the collection of platform rendering devices, such as screens and printers, that a Java program can use. This information is used when the system performs the conversion from user space to device space during rendering.

In JDK 1.1 fonts were described by logical names that mapped onto different font faces, depending on the fonts that were available on a particular platform. For backward compatibility, Graphics2D also supports the specification of fonts by logical name. To get a list of the logical font names, call java.awt.Toolkit.getFontList.

 

 

Text Layout

Before text can be displayed, it must be laid out so that the characters are represented by the appropriate glyphs in the proper positions. If you are using Swing, you can let JLabel or JTextComponent manage text layout for you. JTextComponent supports bidirectional text and is designed to handle the needs of most international applications.

If you are not using a Swing text component to automatically display text, you can use one of two Java 2D mechanisms for managing text layout.

  • If you want to implement your own text-editing routines, you can use the TextLayout class to manage text layout, highlighting, and hit detection. The facilities provided by TextLayout handle most common cases, including strings with mixed fonts, mixed languages, and bidirectional text.
  • If you want total control over how text is shaped and positioned, you can construct your own GlyphVector objects by using Font and then render each GlyphVector through Graphics2D.

 


 

Images

The Java 2D API implements a new imaging model that supports the manipulation of fixed-resolution images stored in memory. A new Image class in the java.awt.image package, BufferedImage, can be used to hold and to manipulate image data retrieved from a file or a URL. For example, a BufferedImage can be used to implement double buffering, the graphic elements are rendered off-screen to the BufferedImage and are then copied to the screen through a call to Graphics2D drawImage. The classes BufferedImage and BufferedImageOp also enable you to perform a variety of image-filtering operations, such as blur and sharpen. The producer/consumer imaging model provided in previous versions of the JDK is supported for backward compatibility.

 


 

Printing

All of the AWT and Java 2D graphics, including composited graphics and images, can be rendered to a printer by using the Java 2D Printing API. This API also provides document composition features that enable you to perform such operations as changing the order in which pages are printed.

Rendering to a printer is like rendering to the screen. The printing system controls when pages are rendered, just like the drawing system controls when a component is painted on the screen.

Your application provides the printing system with information about the document to be printed, and the printing system determines when each page needs to be imaged. When pages need to be imaged, the printing system calls your application's print method with an appropriate Graphics context. To use Java 2D API features when you print, you cast the Graphics object to a Graphics2D, just like you do when you're rendering to the screen.

 


 

Lesson: Displaying Graphics with Graphics2D

 


 

Stroking and Filling Graphics Primitives

By changing the stroke and paint attributes in the Graphics2D context before rendering, you can easily apply fancy line styles and fill patterns to graphics primitives. For example, you can draw a dashed line by creating an appropriate Stroke object and calling setStroke to add it to the Graphics2D context before you render the line. Similarly, you can apply a gradient fill to a Shape by creating a GradientPaint object and adding it to the Graphics2D context by calling setPaint before you render the Shape.

The following applet demonstrates how you can render basic geometries by using the Graphics2D draw and fill methods.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

ShapesDemo2D.java contains the complete code for this applet.

Each of the shapes drawn by the applet is constructed from one of the geometries and is then rendered through Graphics2D. The rectHeight and rectWidth variables in this example define the dimensions of the space where each shape is drawn, in pixels. The x and y variables change for each shape so that they are drawn in a grid formation.

 // draw Line2D.Double
g2.draw(new Line2D.Double(x, y+rectHeight-1,
                          x + rectWidth, y));
 // draw Rectangle2D.Double
g2.setStroke(stroke);
g2.draw(new Rectangle2D.Double(x, y,
                               rectWidth,
                               rectHeight));
 // draw RoundRectangle2D.Double
g2.setStroke(dashed);
g2.draw(new RoundRectangle2D.Double(x, y,
                                   rectWidth,
                                   rectHeight,
                                   10, 10));
 // draw Arc2D.Double
g2.setStroke(wideStroke);
g2.draw(new Arc2D.Double(x, y,
                         rectWidth,
                         rectHeight,
                         90, 135,
                         Arc2D.OPEN));
 // draw Ellipse2D.Double
g2.setStroke(stroke);
g2.draw(new Ellipse2D.Double(x, y,
                             rectWidth,
                             rectHeight));
 // draw GeneralPath (polygon)
int x1Points[] = {x, x+rectWidth,
                  x, x+rectWidth};
int y1Points[] = {y, y+rectHeight,
                  y+rectHeight, y};
GeneralPath polygon = new
	 GeneralPath(GeneralPath.WIND_EVEN_ODD,
		     x1Points.length);
polygon.moveTo(x1Points[0], y1Points[0]);

for (int index = 1;
     index < x1Points.length;
     index++) {
        polygon.lineTo(x1Points[index],
                       y1Points[index]);
};

polygon.closePath();
g2.draw(polygon);
 // draw GeneralPath (polyline)
int x2Points[] = {x, x+rectWidth, x,
                  x+rectWidth};
int y2Points[] = {y, y+rectHeight,
                  y+rectHeight, y};
GeneralPath polyline = new
         GeneralPath(GeneralPath.WIND_EVEN_ODD,
           	     x2Points.length);

polyline.moveTo (x2Points[0], y2Points[0]);

for (int index = 1;
     index < x2Points.length;
     index++) {
 	 polyline.lineTo(x2Points[index],
         y2Points[index]);
};

g2.draw(polyline);
 // fill Rectangle2D.Double (red)
g2.setPaint(red);
g2.fill(new Rectangle2D.Double(x, y,
        rectWidth, rectHeight));
 // fill RoundRectangle2D.Double
g2.setPaint(redtowhite);
g2.fill(new RoundRectangle2D.Double(x, y,
                                   rectWidth,
                                   rectHeight,
                                   10, 10));
 // fill Arc2D
g2.setPaint(red);
g2.fill(new Arc2D.Double(x, y, rectWidth,
                         rectHeight, 90,
                         135, Arc2D.OPEN));
 // fill Ellipse2D.Double
g2.setPaint(redtowhite);
g2.fill (new Ellipse2D.Double(x, y,
                              rectWidth,
                              rectHeight));
 // fill and stroke GeneralPath
int x3Points[] = {x, x+rectWidth, x,
                  x+rectWidth};
int y3Points[] = {y, y+rectHeight,
                  y+rectHeight, y};

GeneralPath filledPolygon = new
         GeneralPath(GeneralPath.WIND_EVEN_ODD,
		     x3Points.length);
filledPolygon.moveTo(x3Points[0],
                     y3Points[0]);

for (int index = 1;
     index < x3Points.length;
     index++) 	{
	filledPolygon.lineTo(x3Points[index],
                             y3Points[index]);

};
filledPolygon.closePath();
g2.setPaint(red);
g2.fill(filledPolygon);

Note that this example uses the double-precision implementations of the geometries classes. Where applicable, float and double-precision implementations of each of the geometries are provided as inner classes.

 

 

Drawing Curves

The Cubic and Quad applets demonstrate how to create cubic and quadratic curves using CubicCurve2D and QuadCurve2D respectively. These applets also demonstrate how the curves are drawn with respect to the positioning of the control points by allowing you to interactively move both the control points and the end points.

 

 

Example: Quad

The Quad applet demonstrates a quadratic curve, which is a curved segment that has two endpoints and only one control point. The control point determines the shape of the curve by controlling both of the endpoint tangent vectors.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

Quad.java contains the complete code for this applet.

First, a new quadratic curve is created with two endpoints and a control point and the locations of the points are set with respect to the size of the window.

QuadCurve2D.Double quad = new QuadCurve2D.Double();

Point2D.Double start, end, control;
start = new Point2D.Double();
one = new Point2D.Double();
control = new Point2D.Double();

quad.setCurve(start, one, control);

start.setLocation(w/2-50, h/2);
end.setLocation(w/2+50, h/2);
control.setLocation((int)(start.x)+50, (int)(start.y)-50);
Every time the user moves one of the points, the curve is reset.
quad.setCurve(start, control, end);

 

 

Example: Cubic

The Cubic sample demonstrates a cubic curve, which is a curved segment that has two endpoints and two control points. Each control point determines the shape of the curve by controlling one of the endpoint tangent vectors. In the Cubic sample, colored squares are drawn where the control points and endpoints are located. The blue control point controls the tangent vector of the red endpoint and the green control point controls the tangent vector of the magenta endpoint.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

Cubic.java contains the complete code for this applet.

A new cubic curve is created with two endpoints and a two control points and the locations of the points are set with respect to the size of the window.

        
CubicCurve2D.Double cubic = new CubicCurve2D.Double();
        
Point2D.Double start, end, one, two;
start = new Point2D.Double();
one = new Point2D.Double();
two = new Point2D.Double();
end = new Point2D.Double();

cubic.setCurve(start, one, two, end);

...

start.setLocation(w/2-50, h/2);
end.setLocation(w/2+50, h/2);
one.setLocation((int)(start.x)+25, (int)(start.y)-25);
two.setLocation((int)(end.x)-25, (int)(end.y)+25);
As in the Quad example, the curve is reset every time the points are moved.
cubic.setCurve(start, one, two, end); 

 

 

Drawing Arbitrary Shapes

The ShapesDemo example uses GeneralPath to make the hourglass-shaped polygons, but you can also use GeneralPath to make arbitrary shapes with both straight and curved lines.

 

 

Example: Odd_Shape

The Odd_Shape sample uses GeneralPath to create the arbitrary shape shown in the Shapes section.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

Odd_Shape.java contains the complete code for this applet.

The following code creates a new GeneralPath and adds the first point to the path.

GeneralPath oddShape = new GeneralPath();
...

x = w/2 + 50;
y = h/2 - 25;

x2 = x; 
y2 = y;

oddShape.moveTo(x, y);
After the first point is added to the path, three straight lines are added to the path.
x -= 100;
oddShape.lineTo(x, y);
y += 50;
oddShape.lineTo(x, y);
x += 100;
oddShape.lineTo(x, y);
Finally, a cubic curve is added to the path.
x += 10;
y -= 10;
x1 = x - 20;
y1 = y - 20;
oddShape.curveTo(x, y, x1, y1, x2, y2);

 

 

Defining Fancy Line Styles and Fill Patterns

You probably noticed that in the previous example some of the shapes have thicker outlines or are filled with a two-color gradient. Using the Java 2D Stroke and Paint classes, you can easily define fancy line styles and fill patterns.

 

 

Line Styles

Line styles are defined by the stroke attribute in the Graphics2D rendering context. To set the stroke attribute, you create a BasicStroke object and pass it into the Graphics2D setStroke method.

A BasicStroke object holds information about the line width, join style, end-cap style, and dash style. This information is used when a Shape is rendered with the draw method.

The line width is the thickness of the line measured perpendicular to its trajectory. The line width is specified as a float value in user coordinate units, which are roughly equivalent to 1/72 inch when the default transform is used.

The join style is the decoration that is applied where two line segments meet. BasicStroke supports three join styles:

JOIN_BEVEL

JOIN_MITER

JOIN_ROUND

The end-cap style is the decoration that is applied where a line segment ends. BasicStroke supports three end-cap styles:

CAP_BUTT

CAP_ROUND

CAP_SQUARE

The dash style defines the pattern of opaque and transparent sections applied along the length of the line. The dash style is defined by a dash array and a dash phase. The dash array defines the dash pattern. Alternating elements in the array represent the dash length and the length of the space between dashes in user coordinate units. Element 0 represents the first dash, element 1 the first space, and so on. The dash phase is an offset into the dash pattern, also specified in user coordinate units. The dash phase indicates what part of the dash pattern is applied to the beginning of the line.

 

 

Fill Patterns

Fill patterns are defined by the paint attribute in the Graphics2D rendering context. To set the paint attribute, you create an instance of an object that implements the Paint interface and pass it into the Graphics2D setPaint method.

Three classes implement the Paint interface: Color, GradientPaint, and TexturePaint. GradientPaint and TexturePaint are new in JDK 1.2.

To create a GradientPaint, you specify a beginning position and color and an ending position and color. The gradient changes proportionally from one color to the other along the line connecting the two positions.

The pattern for a TexturePaint is defined by a BufferedImage. To create a TexturePaint, you specify the image that contains the pattern and a rectangle that is used to replicate and anchor the pattern.

 

 

Example: StrokeAndFill

The StrokeAndFill applet allows the user to select a graphics primitive, a line style, and a paint style and to either stroke the object's outline, fill it with the selected paint, or stroke the object in black and then fill it with the selected paint.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

StrokeAndFill.java contains the complete code for this applet.

The primitives are initialized and entered into an array of Shape objects. The following code creates a Rectangle and an Ellipse2D.Double and enters them into the shapes array.

shapes[0] = new Rectangle(0, 0, 100, 100);
shapes[1] = new Ellipse2D.Double(0.0, 0.0, 100.0, 100.0);

To create a Shape object from a text string, you must first create a TextLayout object from the text string.

TextLayout textTl = new TextLayout("Text", 
	  new Font("Helvetica", 1, 96), 
	  new FontRenderContext(null, false, false));

The following lines transform the TextLayout so that it is centered on the origin and then enter the Shape object resulting from the call to getOutline into the shapes array.

AffineTransform textAt = new AffineTransform();
textAt.translate(0,
    (float)textTl.getBounds().getHeight());
shapes[2] = textTl.getOutline(textAt);

You can choose a primitive by accessing the appropriate index into the shapes array.

Shape shape = 
    shapes[Transform.primitive.getSelectedIndex()];

How rendering is performed depends on which rendering option is chosen.

  • When the user chooses stroke, Graphics2D.draw is called to perform the rendering. If text is chosen as the primitive, the glyph outlines are retrieved and then rendered with the draw method.
  • When the user chooses fill, Graphics2D.fill or Graphics2D.drawString is called to perform the rendering.
  • When the user chooses stroke and fill, fill or drawString is called to fill the Shape, and then draw is called to stroke its outline.
  To both fill and stroke a graphics primitive, you need to make two separate method calls: fill or drawString to fill its interior and draw to stroke its outline.

The three line styles used in this example, thin, thick, and dashed, are instances of BasicStroke.

// Sets the Stroke.
...
case 0 : g2.setStroke(new BasicStroke(3.0f)); break;
case 1 : g2.setStroke(new BasicStroke(8.0f)); break;
case 2 : float dash[] = {10.0f};
         g2.setStroke(new BasicStroke(3.0f,
             BasicStroke.CAP_BUTT,
             BasicStroke.JOIN_MITER,
             10.0f, dash, 0.0f));
         break;

The dash style in this example has 10 unit dashes alternating with 10 unit spaces. The beginning of the dash pattern is applied to the beginning of the line, the dash phase is set to 0.0.

Three paint styles are used in this example, solid, gradient, and polka. The solid-color paint style is an instance of Color, the gradient an instance of GradientPaint, and the pattern an instance of TexturePaint.

// Sets the Paint.
...
case 0 : g2.setPaint(Color.blue); break;
case 1 : g2.setPaint(new GradientPaint(0, 0,
                          Color.lightGray,
                          w-250, h, Color.blue, false));
         break;
case 2 : BufferedImage bi = new BufferedImage(5, 5,
                                BufferedImage.TYPE_INT_RGB);
         Graphics2D big = bi.createGraphics();
         big.setColor(Color.blue);
         big.fillRect(0, 0, 5, 5);
         big.setColor(Color.lightGray);
         big.fillOval(0, 0, 5, 5);
         Rectangle r = new Rectangle(0,0,5,5);
         g2.setPaint(new TexturePaint(bi, r));
         break;

 


 

Transforming Shapes, Text, and Images

You can modify the transform attribute in the Graphics2D context to move, rotate, scale, and shear graphics primitives when they are rendered. The transform attribute is defined by an instance of AffineTransform. (An affine transform is a transformation such as translate, rotate, scale, or shear in which parallel lines remain parallel even after being transformed.)

Graphics2D provides several methods for changing the transform attribute. You can construct a new AffineTransform and change the Graphics2D transform attribute by calling transform.

AffineTransform defines the following factory methods to make it easier to construct new transforms:

  • getRotateInstance
  • getScaleInstance
  • getShearInstance
  • getTranslateInstance

Alternatively you can use one of the Graphics2D transformation methods to modify the current transform. When you call one of these convenience methods, the resulting transform is concatenated with the current transform and is applied during rendering:

rotate to specify an angle of rotation in radians
scale to specify a scaling factor in the x and y directions
shear to specify a shearing factor in the x and y directions
translate to specify a translation offset in the x and y directions

You can also construct an AffineTransform directly and concatenate it with the current transform by calling the transform method.

The drawImage method is also overloaded to allow you to specify an AffineTransform that is applied to the image as it is rendered. Specifying a transform when you call drawImage does not affect the Graphics2D transform attribute.

 

 

Example: Transform

The following program is the same as StrokeandFill, but also allows the user to choose a transformation to apply to the selected object when it is rendered.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

Transform.java contains the complete code for this applet.

When a transform is chosen from the Transform menu, the transform is concatenated onto the AffineTransform at:

public void setTrans(int transIndex) {
    // Sets the AffineTransform.
    switch ( transIndex ) {
    case 0 : at.setToIdentity();
             at.translate(w/2, h/2); break;
    case 1 : at.rotate(Math.toRadians(45)); break;
    case 2 : at.scale(0.5, 0.5); break;
    case 3 : at.shear(0.5, 0.0); break;
    }
}
Before displaying the shape corresponding to the menu choices, the application first retrieves the current transform from the Graphics2D object:

AffineTransform saveXform = g2.getTransform();
This transform will be restored to the Graphics2D after rendering.

After retrieving the current transform, another AffineTransform, toCenterAt, is created that causes shapes to be rendered in the center of the panel. The at AffineTransform is concatenated onto toCenterAt:

AffineTransform toCenterAt = new AffineTransform();
toCenterAt.concatenate(at);
toCenterAt.translate(-(r.width/2), -(r.height/2));
The toCenterAt transform is concatenated onto the Graphics2D transform with the transform method:

g2.transform(toCenterAt);
After rendering is completed, the original transform is restored using the setTransform method:

g2.setTransform(saveXform);
 Never use setTransform to concatenate a coordinate transform onto an existing transform. The setTransform method overwrites the Graphics2D object's current transform, which might be needed for other reasons, such as positioning Swing and lightweight components in a window. Use these steps to perform transformations:
  1. Use getTransform to get the current transform.
  2. Use transform, translate, scale, shear, or rotate to concatenate a transform.
  3. Perform the rendering.
  4. Restore the original transform using setTransform.

 


 

Clipping the Drawing Region

Any Shape can be used as a clipping path that restricts the portion of the drawing area that will rendered. The clipping path is part of the Graphics2D context; to set the clip attribute, you call Graphics2D.setClip and pass in the Shape that defines the clipping path you want to use. You can shrink the clipping path by calling the clip method and passing in another Shape; the clip is set to the intersection of the current clip and the specified Shape.

 

 

Example: ClipImage

This example animates a clipping path to reveal different portions of an image.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

ClipImage.java contains the complete code for this applet. The applet requires the clouds.jpg image file.

The clipping path is defined by the intersection of an ellipse and a rectangle whose dimensions are set randomly. The ellipse is passed to the setClip method, and then clip is called to set the clipping path to the intersection of the ellipse and the rectangle.

private Ellipse2D ellipse = new Ellipse2D.Float();
private Rectangle2D rect = new Rectangle2D.Float();
...
ellipse.setFrame(x, y, ew, eh);
g2.setClip(ellipse);
rect.setRect(x+5, y+5, ew-10, eh-10);
g2.clip(rect);

 

 

Example: Starry

A clipping area can also be created from a text string. The following example creates a TextLayout with the string The Starry Night. Then, it gets the outline of the TextLayout. The TextLayout.getOutline method returns a Shape object and a Rectangle is created from the bounds of this Shape object. The bounds contains all the pixels the layout can draw. The color in the graphics context is set to blue and the outline shape is drawn, as illustrated by the following image and code snippet.

FontRenderContext frc = g2.getFontRenderContext();
Font f = new Font("Helvetica", 1, w/10);
String s = new String("The Starry Night");
TextLayout tl = new TextLayout(s, f, frc);
AffineTransform transform = new AffineTransform();
Shape outline = textTl.getOutline(null);
Rectangle r = outline.getBounds();
transform = g2.getTransform();
transform.translate(w/2-(r.width/2), h/2+(r.height/2));
g2.transform(transform);
g2.setColor(Color.blue);
g2.draw(outline);   

Next, a clipping area is set on the graphics context using the Shape object created from getOutline. The starry.gif image, which is Van Gogh's famous painting, The Starry Night, is drawn into this clipping area starting at the lower left corner of the Rectangle object.

g2.setClip(outline);
g2.drawImage(img, r.x, r.y, r.width, r.height, this);

Click this figure to run the applet.
This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

Starry.java contains the complete code for this program.

 


 

Compositing Graphics

The AlphaComposite class encapsulates various compositing styles, which determine how overlapping objects are rendered. An AlphaComposite can also have an alpha value that specifies the degree of transparency: alpha = 1.0 is totally opaque, alpha = 0.0 totally transparent (clear). AlphaComposite supports most of the standard Porter-Duff compositing rules shown in the following table.

Source-over (SRC_OVER)
If pixels in the object being rendered (the source) have the same location as previously rendered pixels (the destination), the source pixels are rendered over the destination pixels.
Source-in (SRC_IN)
If pixels in the source and the destination overlap, only the source pixels in the overlapping area are rendered.
Source-out (SRC_OUT)
If pixels in the source and the destination overlap, only the source pixels outside of the overlapping area are rendered. The pixels in the overlapping area are cleared.
Destination-over (DST_OVER)
If pixels in the source and the destination overlap, only the source pixels outside of the overlapping area are rendered. The pixels in the overlapping area are not changed.
Destination-in (DST_IN)
If pixels in the source and the destination overlap, the alpha from the source is applied to the destination pixels in the overlapping area. If the alpha = 1.0, the pixels in the overlapping area are unchanged; if the alpha is 0.0, pixels in the overlapping area are cleared.
Destination-out (DST_OUT)
If pixels in the source and the destination overlap, the alpha from the source is applied to the destination pixels in the overlapping area. If the alpha = 1.0, the pixels in the overlapping area are cleared; if the alpha is 0.0, the pixels in the overlapping area are unchanged.
Clear (CLEAR)
If the pixels in the source and the destination overlap, the pixels in the overlapping area are cleared.

To change the compositing style used by Graphics2D, you create an AlphaComposite object and pass it into the setComposite method.

 

 

Example: Composite

This program illustrates the effects of various compositing style and alpha combinations.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

Composite.java contains the full code for this applet.

A new AlphaComposite object ac is constructed by calling AlphaComposite.getInstance and specifying the desired compositing rule.

AlphaComposite ac =
               AlphaComposite.getInstance(AlphaComposite.SRC);

When a different compositing rule or alpha value is selected, AlphaComposite.getInstance is called again, and the new AlphaComposite is assigned to ac. The selected alpha is applied in addition to the per-pixel alpha value and is passed as a second parameter to AlphaComposite.getInstance.

ac = AlphaComposite.getInstance(getRule(rule), alpha);

The composite attribute is modified by passing the AlphaComposite object to Graphics 2D setComposite. The objects are rendered into a BufferedImage and are later copied to the screen, so the composite attribute is set on the Graphics2D context for the BufferedImage:

BufferedImage buffImg = new BufferedImage(w, h,
                            BufferedImage.TYPE_INT_ARGB);
Graphics2D gbi = buffImg.createGraphics();
...
gbi.setComposite(ac);

 


 

Controlling Rendering Quality

You can use the Graphics2D rendering hints attribute to specify whether you want objects to be rendered as quickly as possible or whether you prefer that the rendering quality be as high as possible.

To set or change the rendering hints attribute in the Graphics2D context, you can construct a RenderingHints object and pass it into Graphics2D setRenderingHints. If you just want to set one hint, you can call Graphics2D setRenderingHint and specify the key-value pair for the hint you want to set. (The key-value pairs are defined in the RenderingHints class.)

For example, to set a preference for antialiasing to be used if possible, you could use setRenderingHint:

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,aliasing);
  Not all platforms support modification of the rendering mode, so specifying rendering hints does not guarantee that they will be used.

RenderingHints supports the following types of hints:

Alpha interpolation default, quality, or speed
Antialiasing default, on, or off
Color rendering default, quality, or speed
Dithering default, disable, or enable
Fractional metrics default, on, or off
Interpolation nearest-neighbor, bilinear, or bicubic
Rendering default, quality, or speed
Text antialiasing default, on, or off

When a hint is set to default, the platform rendering default is used is used.

 


 

Constructing Complex Shapes from Geometry Primitives

Constructive area geometry (CAG) is the process of creating new geometric shapes by performing boolean operations on existing ones. In the Java 2D API a special type of Shape called an Area supports boolean operations. You can construct an Area from any Shape .

Areas support the following boolean operations:

Union Subtraction
Intersection Exclusive-or (XOR)

 

 

Example: Areas

In this example Area objects construct a pear shape from several ellipses.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

Pear.java contains the complete code for this applet.

The leaves are each created by performing an intersection on two overlapping circles.

leaf = new Ellipse2D.Double();
...
leaf1 = new Area(leaf);
leaf2 = new Area(leaf);
...
leaf.setFrame(ew-16, eh-29, 15.0, 15.0);
leaf1 = new Area(leaf);
leaf.setFrame(ew-14, eh-47, 30.0, 30.0);
leaf2 = new Area(leaf);
leaf1.intersect(leaf2);
g2.fill(leaf1);
...
leaf.setFrame(ew+1, eh-29, 15.0, 15.0);
leaf1 = new Area(leaf);
leaf2.intersect(leaf1);
g2.fill(leaf2);

Overlapping circles are also used to construct the stem through a subtraction operation.

stem = new Ellipse2D.Double();
...
stem.setFrame(ew, eh-42, 40.0, 40.0);
st1 = new Area(stem);
stem.setFrame(ew+3, eh-47, 50.0, 50.0);
st2 = new Area(stem);
st1.subtract(st2);
g2.fill(st1);

The body of the pear is constructed by performing a union operation on a circle and an oval.

circle = new Ellipse2D.Double();
oval = new Ellipse2D.Double();
circ = new Area(circle);
ov = new Area(oval);
...
circle.setFrame(ew-25, eh, 50.0, 50.0);
oval.setFrame(ew-19, eh-20, 40.0, 70.0);
circ = new Area(circle);
ov = new Area(oval);
circ.add(ov);
g2.fill(circ);

 


 

Supporting User Interaction

To allow the user to interact with the graphics you display, you need to be able to determine when the user clicks on one of them. The Graphics2D hit method provides a way for you to easily determine whether a mouse click occurred over a particular Shape. Alternatively you can get the location of the mouse click and call contains on the Shape to determine whether the click was within the bounds of the Shape.

If you are using primitive text, you can perform simple hit testing by getting the outline Shape that corresponds to the text and then calling hit or contains with that Shape. Supporting text editing requires much more sophisticated hit testing. If you want to allow the user to edit text, you should generally use one of the Swing editable text components. If you are working with primitive text and are using TextLayout to manage the shaping and positioning of the text, you can also use TextLayout to perform hit testing for text editing.

 

 

Example: ShapeMover

This applet allows the user to drag a Shape around within the applet window. The Shape is redrawn at every mouse location to provide feedback as the user drags it.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture. The applet will appear in a new browser window.

ShapeMover.java contains the complete code for this applet.

The contains method is called to determine whether the cursor is within the bounds of the rectangle when the mouse is pressed. If it is, the location of the rectangle is updated.

public void mousePressed(MouseEvent e){
    last_x = rect.x - e.getX();
    last_y = rect.y - e.getY();
    if(rect.contains(e.getX(), e.getY())) updateLocation(e);
...

public void updateLocation(MouseEvent e){
    rect.setLocation(last_x + e.getX(), last_y + e.getY());
    ...
    repaint();

You might notice that redrawing the Shape at every mouse location is slow, because the filled rectangle is rerendered every time it is moved. Using double buffering can eliminate this problem. If you use Swing, the drawing will be double buffered automatically; you don't have to change the rendering code at all. The code for a Swing version of this program is SwingShapeMover.java.
To run the Swing version, visit Run SwingShapeMover.

 

 

Example: HitTestSample

This application illustrates hit testing by drawing the default caret wherever the user clicks on the TextLayout, as shown in the following figure.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

HitTestSample.java contains the complete code for this applet.

The mouseClicked method uses TextLayout.hitTestChar to return a java.awt.font.TextHitInfo object that contains the mouse click location (the insertion index) in the TextLayout object.

Information returned by the TextLayout getAscent, getDescent, and getAdvance methods is used to compute the location of the origin for the TextLayout object so it is horizontally and vertically centered.

...

private Point2D computeLayoutOrigin() {
  Dimension size = getPreferredSize();
  Point2D.Float origin = new Point2D.Float();
     
  origin.x = (float) (size.width - textLayout.getAdvance()) / 2;   
  origin.y = 
    (float) (size.height - textLayout.getDescent()
             + textLayout.getAscent())/2;
  return origin;
}

...

public void paintComponent(Graphics g) {
  super.paintComponent(g);
  setBackground(Color.white);
  Graphics2D graphics2D = (Graphics2D) g;                
  Point2D origin = computeLayoutOrigin();
  graphics2D.translate(origin.getX(), origin.getY());
                
  // Draw textLayout.
  textLayout.draw(graphics2D, 0, 0);
     
  // Retrieve caret Shapes for insertionIndex.
  Shape[] carets = textLayout.getCaretShapes(insertionIndex);
       
  // Draw the carets.  carets[0] is the strong caret and 
  // carets[1] is the weak caret.   
  graphics2D.setColor(STRONG_CARET_COLOR);
  graphics2D.draw(carets[0]);                
  if (carets[1] != null) {
    graphics2D.setColor(WEAK_CARET_COLOR);
    graphics2D.draw(carets[1]);
  }       
}

...

private class HitTestMouseListener extends MouseAdapter {
                
    /**
     * Compute the character position of the mouse click.
     */     
    public void mouseClicked(MouseEvent e) {
                
      Point2D origin = computeLayoutOrigin();
                
      // Compute the mouse click location relative to  
      // textLayout's origin.
      float clickX = (float) (e.getX() - origin.getX());
      float clickY = (float) (e.getY() - origin.getY());
         
      // Get the character position of the mouse click.
      TextHitInfo currentHit = textLayout.hitTestChar(clickX, clickY);
      insertionIndex = currentHit.getInsertionIndex();
            
      // Repaint the Component so the new caret(s) will be displayed.
      hitPane.repaint();
    }

 


 

Creating and Deriving Fonts

You can display a text string with any font available on your system in any size and style that you choose. To determine what fonts are available on your system, you can call the GraphicsEnvironment.getAvailableFontFamilyNames method. This method returns an array of strings that contains the family names of the available fonts. Any of the strings, along with a size and style argument, can be used to create a new Font object. After creating a Font object, you can reset its font family name, size or style to create a custom font.

 

 

Example: FontSelection

The following applet allows you to change the font, size, and style of the displayed text.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

The complete code for this applet is in FontSelection.java.

The getAvailableFontFamilyNames method of GraphicsEnvironment returns the font family names of the fonts available on your system.

GraphicsEnvironment gEnv =
    GraphicsEnvironment.getLocalGraphicsEnvironment();
String envfonts[] = gEnv.getAvailableFontFamilyNames();
Vector vector = new Vector();
for ( int i = 1; i < envfonts.length; i++ ) {
   vector.addElement(envfonts[i]);
}
The initial Font object is created with style Font.PLAIN and size 10. The other available styles are ITALIC, BOLD and BOLD+ITALIC.
Font thisFont;
...

thisFont = new Font("Arial", Font.PLAIN, 10);
A new Font is created from the specified font name, style and size.
public void changeFont(String f, int st, String si){
  Integer newSize = new Integer(si);
  int size = newSize.intValue();
  thisFont = new Font(f, st, size);
  repaint();
}
To use the same font family but change one or both of the style and size attributes, you can call one of the deriveFont methods.

To control the font used to render text, you set the font attribute in the Graphics2D context before rendering. The font attribute is set by passing a Font object to the setFont method. In this example, the font attribute is set to the newly constructed Font object and then the string is drawn in the center of the Component using the specified font. In the paint method, the font attribute of the Graphics2D context is set to the new Font. The string is drawn in the middle of the component with the new font.

g2.setFont(thisFont);
String change = "Pick a font, size, and style to change me";
FontMetrics metrics = g2.getFontMetrics();
int width = metrics.stringWidth( change );
int height = metrics.getHeight();
g2.drawString( change, w/2-width/2, h/2-height/2 );

Due to bug # 4155852, FontSelection might not work properly for all font names returned from the call to getFontFamilyNames. The sample might not respond to changes in size or style and the text might not show up at all when some fontnames are chosen. In general, Courier and Helvetica work fine. In the meantime, check back periodically to see if these problems have been fixed.

 


 

Drawing Multiple Lines of Text

If you have a paragraph of styled text that you would like to fit within a specific width, you can use LineBreakMeasurer, which allows styled text to be broken into lines that fit within a particular visual advance.

A TextLayout object represents unchangeable, styled character data, but it also allows access to layout information. The getAscent and getDescent methods of TextLayout return information about the font that is used to position the lines in the component. The text is stored as an AttributedCharacterIterator so the font and point size attributes can be stored with the text.

 

 

Example: LineBreakSample

The following applet positions a paragraph of styled text within a component, using LineBreakMeasurer, TextLayout and AttributedCharacterIterator.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

The complete code for this program is in LineBreakSample.java.

The following code creates an iterator with the string vanGogh. The start and end of the iterator is retrieved and a new LineBreakMeasurer is created from the iterator.

AttributedCharacterIterator paragraph = vanGogh.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
        
lineMeasurer = new LineBreakMeasurer(paragraph,
                            new FontRenderContext(null, false, false));
The size of the window is used to determine where the line should break and a TextLayout object is created for each line in the paragraph.

Dimension size = getSize();
float formatWidth = (float) size.width;    
float drawPosY = 0;
lineMeasurer.setPosition(paragraphStart);
        
while (lineMeasurer.getPosition() < paragraphEnd) {    
   TextLayout layout = lineMeasurer.nextLayout(formatWidth);

   // Move y-coordinate by the ascent of the layout.
   drawPosY += layout.getAscent();
        
   /* Compute pen x position.  If the paragraph is
      right-to-left, we want to align the TextLayouts
      to the right edge of the panel. 
    */
   float drawPosX;
   if (layout.isLeftToRight()) {
       drawPosX = 0;
   }
   else {
     drawPosX = formatWidth - layout.getAdvance();
   }
            
   // Draw the TextLayout at (drawPosX, drawPosY). 
   layout.draw(graphics2D, drawPosX, drawPosY);
            
   // Move y-coordinate in preparation for next layout.
   drawPosY += layout.getDescent() + layout.getLeading();
}

 


 

Immediate-Mode Imaging with BufferedImage

The immediate-mode imaging model enables you to manipulate and display pixel-mapped images whose data is stored in memory. You can access image data in a variety of formats and use several types of filtering operations to manipulate the data.

BufferedImage is the key class in the immediate-mode imaging API. This class manages an image in memory and provides methods for storing, interpreting, and rendering the pixel data. A BufferedImage can be rendered through either a Graphics or a Graphics2D rendering context.

A BufferedImage is essentially an Image with an accessible data buffer. A BufferedImage has a ColorModel and a Raster of image data.

The ColorModel provides a color interpretation of the image's pixel data. The Raster represents the rectangular coordinates of the image, maintains image data in memory, and provides a mechanism for creating multiple subimages from a single image data buffer. The Raster also provides methods for accessing specific pixels within the image. For information about directly manipulating pixel data and writing filters for BufferedImage objects, see the chapter Imaging in the Java 2D Programmer's Guide.

 


 

Filtering a BufferedImage

The Java 2D API defines several filtering operations for BufferedImage objects. Each image-processing operation is embodied in a class that implements the BufferedImageOp interface. The image manipulation is performed in the image operation's filter method. The BufferedImageOp classes in the Java 2D API support

  • Affine transformation
  • Amplitude scaling
  • Lookup-table modification
  • Linear combination of bands
  • Color conversion
  • Convolution

To filter a BufferedImage using one of the image operation classes, you

  1. Construct an instance of one of the BufferedImageOp classes: AffineTransformOp, BandCombineOp, ColorConvertOp, ConvolveOp, LookupOp, or RescaleOp.
  2. Call the image operation's filter method, passing in the BufferedImage that you want to filter and the BufferedImage where you want to store the results.

 

 

Example: ImageOps

The following applet illustrates the use of four image-filter operations: low-pass, sharpen, lookup, and rescale.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

You can see the complete code for this applet in ImageOps.java. The applet uses these two image files: bld.jpg and boat.gif.

The sharpen filter is performed by using a ConvolveOp. Convolution is the process of weighting or averaging the value of each pixel in an image with the values of neighboring pixels. Most spatial-filtering algorithms are based on convolution operations.

To construct and apply the sharpen filter to the BufferedImage, this sample uses code similar to the following snippet.

                            0.f, -1.f, 0.f,
                            -1.f, 5.0f, -1.f,
                            0.f, -1.f, 0.f};
BufferedImage dstbimg = new 
              BufferedImage(iw,ih,BufferedImage.TYPE_INT_RGB);
Kernel kernel = new Kernel(3,3,SHARPEN3x3);
ConvolveOp cop = new ConvolveOp(kernel,
                                ConvolveOp.EDGE_NO_OP,
                                null);
cop.filter(srcbimg,dstbimg);

The Kernel object mathematically defines how each output pixel is affected by pixels in its immediate area.The definition of the Kernel determines the results of the filter.

 


 

Using a BufferedImage for Double Buffering

When a graphic is complex or is used repeatedly, you can reduce the time it takes to display it by first rendering it to an off-screen buffer and then copying the buffer to the screen. This technique, called double buffering, is often used for animations.   When you are rendering into a Swing component, Swing automatically double-buffers the display.

A BufferedImage can easily be used as an off-screen buffer. To create a BufferedImage whose color space, depth, and pixel layout exactly match the window into which you're drawing, call the Component createImage method. If you need control over the off-screen image's type or transparency, you can construct a BufferedImage object directly and use it as an off-screen buffer.

To draw into the buffered image, you call the BufferedImage createGraphics method to get a Graphics2D object; then you call the appropriate rendering methods on the Graphics2D. All of the Java 2D API rendering features can be used when you're rendering to a BufferedImage that's being used as an off-screen buffer.

When you're ready to copy the BufferedImage to the screen, you simply call drawImage on your component's Graphics2D and pass in the BufferedImage.

 

 

Example: BufferedShapeMover

The following applet allows the user to drag a rectangle around within the applet window. Instead of rendering the rectangle at every mouse location to provide feedback as the user drags it, a BufferedImage is used as an off-screen buffer. As the rectangle is dragged, it is re-rendered into the BufferedImage at each new location and the BufferedImage is blitted to the screen.

Click this figure to run the applet.

This is a picture of the applet's GUI.
To run the applet, click the picture.
The applet will appear in a new browser window.

The source code for the applet is in BufferedShapeMover.java.

Here is the code used to render into the BufferedImage and display the image on the screen:

public void updateLocation(MouseEvent e){
    rect.setLocation(last_x + e.getX(),
                     last_y + e.getY());
    ...
    repaint();
    ...
    // In the update method...
    if(firstTime) {
        Dimension dim = getSize();
        int w = dim.width;
        int h = dim.height;
        area = new Rectangle(dim);
        bi = (BufferedImage)createImage(w, h);
        big = bi.createGraphics();
        rect.setLocation(w/2-50, h/2-25);
        big.setStroke(new BasicStroke(8.0f));
        firstTime = false;
    }

    // Clears the rectangle that was previously drawn.
    big.setColor(Color.white);
    big.clearRect(0, 0, area.width, area.height);

    // Draws and fills the newly positioned rectangle
    // to the buffer.
    big.setPaint(strokePolka);
    big.draw(rect);
    big.setPaint(fillPolka);
    big.fill(rect);

    // Draws the buffered image to the screen.
    g2.drawImage(bi, 0, 0, this);
}

 


 

Overview of Printing in Java

The system controls the overall printing process, just like it controls when and how a program can draw. Your application provides information about the document to be printed, and the printing system determines when each page needs to be rendered.

This callback printing model enables printing to be supported on a wide range of printer and systems. It even allows users to print to a bitmap printer from a computer that doesn't have enough memory or disk space to hold the bitmap of an entire page. In this situation the printing system will ask your application to render the page repeatedly so that it can be printed as a series of smaller images. (These smaller images are typically referred to as bands, and this process is commonly called banded printing.)

To support printing, an application needs to perform two tasks:

Job control managing the print job
Imaging rendering the pages to be printed

 

 

Job Control

Although the system controls the overall printing process, your application has to get the ball rolling by setting up a PrinterJob. The PrinterJob , the key point of control for the printing process, stores the print job properties, controls the display of print dialogs, and is used to initiate printing.

To steer the PrinterJob through the printing process, your application needs to

  1. Get a PrinterJob by calling PrinterJob.getPrinterJob
  2. Tell the PrinterJob where the rendering code is by calling setPrintable or setPageable
  3. If desired, display the Page Setup and Print dialogs by calling pageDialog and printDialog
  4. Initiate printing by calling print

The rendering of pages is controlled by the printing system through calls to the application's imaging code.

 

 

Imaging

Your application must be able to render any page when the printing system requests it. This rendering code is contained in the print method of a page painter, a class that implements the Printable interface. You implement print to render page contents by using a Graphics or a Graphics2D rendering context. You can use either one page painter to render all of the pages in a print job or different page painters for different types of pages. When the printing system needs to render a page, it calls print on the appropriate page painter.

When you use a single page painter, the print job is called a printable job. Using a printable job is the simplest way to support printing. More complex printing operations that use multiple page painters are referred to as pageable jobs. In a pageable job an instance of a class that implements the Pageable interface is used to manage the page painters.

 

 

Printable Jobs

In a printable job all pages use the same page painter and PageFormat, which defines the size and orientation of the page to be printed. The page painter is asked to render each page in indexed order, starting the page at index 0. The page painter might be asked to render a page multiple times before the next page is requested, but no pages are skipped. For example, if a user prints pages 2 and 3 of a document, the page painter is asked to render the pages at indices 0, 1, and 2 even though the page at index 0 will not be printed.

If a print dialog is presented, it will not display the number of pages, because that information is not available to the printing system. The page painter informs the printing system when the end of the document is reached.

 

 

Pageable Jobs

Pageable jobs are useful if your application builds an explicit representation of a document, page by page. In a pageable job different pages can use different page painters and PageFormats. The printing system can ask the page painters to render pages in any order, and pages can be skipped. For example, if a user prints pages 2 and 3 of a document, the page painter will be asked to render only the pages at indices 1 and 2.

The multiple page painters in a pageable job are coordinated by a class that implements the Pageable interface, such as Book. A Book represents a collection of pages that can use different page painters and that can vary in size and orientation. You can also use your own implementation of the Pageable interface if Book does not meet your application's needs.

 


 

Printing the Contents of a Component

Anything that you render to the screen can also be printed. You can easily use a Printable job to print the contents of a component.

 

 

Example: ShapesPrint

In this example we use the same rendering code to both display and print the contents of a component. When the user clicks the Print button, a print job is created, and printDialog is called to display the print dialog. If the user continues with the job, the printing process is initiated, and the printing system calls print as necessary to render the job to the printer.

ShapesPrint is the page painter. Its print method calls drawShapes to perform the imaging for the print job. (The drawShapes method is also called by paintComponent to render to the screen.)

public class ShapesPrint extends JPanel
                         implements Printable, ActionListener {
...
public int print(Graphics g, PageFormat pf, int pi)
                          throws PrinterException {
    if (pi >= 1) {
        return Printable.NO_SUCH_PAGE;
    }
    drawShapes((Graphics2D) g);
    return Printable.PAGE_EXISTS;
}
...
public void drawShapes Graphics2D(g2) {
    Dimension d = getSize();
    int gridWidth = 400/6;
    int gridHeight = 300/2;
    int rowspacing = 5;
    int columnspacing = 7;
    int rectWidth = gridWidth - columnspacing;
    int rectHeight = gridHeight - rowspacing;
    ...

    int x = 85;
    int y = 87;
    ...
    g2.draw(new Rectangle2D.Double(x,y,rectWidth,rectHeight));
    ...

The job control code is in the ShapesPrint actionPerformed method.

public void actionPerformed(ActionEvent e) {
    if (e.getSource() instanceof JButton) {
        PrinterJob printJob = PrinterJob.getPrinterJob();
        printJob.setPrintable(this);
        if (printJob.printDialog()) {
            try {
                printJob.print();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}

You can find the complete code for this program in ShapesPrint.java.

 


 

Displaying a Page Setup Dialog

You can allow the user to specify page characteristics, such as the paper size and orientation, by displaying a Page Setup dialog. The page information is stored in a PageFormat object. Like the Print dialog, the Page Setup dialog is displayed by calling a method on the PrinterJob object, pageDialog.

The Page Setup dialog is initialized by using the PageFormat passed to the pageDialog method. If the user clicks the OK button in the dialog, the PageFormat is cloned, altered to reflect the user's selections, and then returned. If the user cancels the dialog, pageDialog returns the original, unaltered PageFormat.

ShapesPrint could easily be modified to display a Page Setup dialog by adding a call to pageDialog after you get the PrinterJob.

// Get a PrinterJob
PrinterJob job = PrinterJob.getPrinterJob();
// Ask user for page format (e.g., portrait/landscape)
PageFormat pf = job.pageDialog(job.defaultPage());

 


 

Printing a Collection of Pages

When you need more control over the individual pages in a print job, you can use a pageable job instead of a printable job. The simplest way to manage a pageable job is to use the Book class, which represents a collection of pages.

 

 

Example: SimpleBook

The SimpleBook program uses a Book to manage two page painters: PaintCover is used for the cover page, and PaintContent is used for the content page. The cover page is printed in landscape mode, whereas the content page is printed in portrait mode.

Once the Book is created, pages are added to it with the append method. When you add a page to a Book, you need to specify the Printable and PageFormat to use for that page.

// In the program's job control code...
// Get a PrinterJob
PrinterJob job = PrinterJob.getPrinterJob();

// Create a landscape page format
PageFormat landscape = job.defaultPage();
landscape.setOrientation(PageFormat.LANDSCAPE);

// Set up a book
Book bk = new Book();
bk.append(new PaintCover(), job.defaultPage());
bk.append(new PaintContent(), landscape);

// Pass the book to the PrinterJob
job.setPageable(bk);

The setPageable method is called on the PrinterJob to tell the printing system to use the Book to locate the appropriate rendering code.

You can find the full program in SimpleBook.java.

 


 

Solving Common 2D Graphics Problems

Also see Improving Printing Performance, which has tips for solving printing performance problems.

Problem: I can run my Java2D applets with appletviewer, but they do not run in my browser. The Java console of the browser says: defn not found for java.awt.Graphics2D.


Problem: How can you write over a previous image? Our problem is that our applet displays an image of a map, but when we draw a line on top of the map, the line overwrites the map.

  • You should try drawing your image into a BufferedImage. Then, draw the BufferedImage into the Graphics2D context and then draw the line to the Graphics2D context. Here's a sample that does this: Map_Line.java Just substitute the name of your image for bld.jpg.

Problem: How do you create a BufferedImage from a gif or jpeg file?

  • To create a BufferedImage from a gif or jpeg, you load your gif or jpeg into an Image object and then draw the Image to the BufferedImage object. The following snippet illustrates this:

    Image img = getImage("picture.gif");
    int width = img.getWidth(this);
    int height = img.getHeight(this);
    
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics2D biContext = bi.createGraphics();
    biContext.drawImage(img, 0, 0, null);
    
    getImage is an Applet method. If you have an application, you can use:

    Image img = Toolkit.getDefaultToolkit().getImage("picture.gif");
    try {
        MediaTracker tracker = new MediaTracker(this);
        tracker.addImage(img, 0);
                    tracker.waitForID(0);
    } catch (Exception e) {}
    int width = img.getWidth(this);
    int height = img.getHeight(this);
    BufferedImage bi = new BufferedImage(width, height,
    BufferedImage.TYPE_INT_RGB);
    Graphics2D biContext = bi.createGraphics();
    biContext.drawImage(img, 0, 0, null);
    
    BufferedImage.TYPE_INT_RGB is one of many BufferedImage types. You need to create a Graphics2D context for the BufferedImage by using the createGraphics method. Then, you can use the drawImage method from the Graphics2D class to draw the image into the buffered image. In the ImageOps sample on the "Filtering a BufferedImage" page, images are drawn into buffered images. You could also look at java.sun.com/products/java-media/2D/samples/index.html, which has links to samples that use gifs, jpegs and BufferedImage objects.

Problem: I can't compile the source code for StrokeAndFill.java and Transform.java with jdk1.2beta4.

  • The TextLayout.getOutline implementation was changed between beta4 and the current JDK. The new implementation takes only an AffineTransform as an argument. You need to download the new JDK to run the sample.

Problem: Is there a way to specify a formula for a line and draw a picture according to it?

  • Using line segments would be the easiest way. You can represent the line segments either by filling a GeneralPath with them, or by implementing Shape and PathIterator and reading back the line segments "on demand" to save the intermediate storage of the GeneralPath object. Note that you could analyze your formula and determine how to match it with cubic or quadratic bezier curves, but this is probably overkill.

Problem: How do I add text to a graphic field at a certain location?

  • A class called Graphics2D was added with JDK 1.2 (now called Java 2 SDK). This class extends Graphics. There are drawString methods in Graphics2D that you can use. If you will be rotating the text, you should use Graphics2D rather than Graphics so that you can perform rotations and other transformations on your Graphics2D context.

    The Transform sample in the 2D tutorial doesn't use drawString to render the text. What happens is that a TextLayout is created from the string "Text." The TextLayout allows us to create a Shape object from the String by getting it's outline. We enter this Shape into the shapes array, along with the rectangle and ellipse shapes. When we draw or fill the selected Shape from the shapes array, we call g2.draw(Shape) or g2.fill(Shape).

    You could use drawString to render the text to the Graphics2D context and then call g2.rotate (angle of rotation). This will rotate everything that you've already rendered into the Graphics2D context, however. So, you could reset the g2 context transform each time you want to transform a particular image or piece of text in the context separately from other things that have already been rendered into the g2 context.

    There are other 2D samples you can look at here: http://java.sun.com/products/java-media/2D/samples/index.html

    They are a little more advanced than the tutorial samples. If you will be transforming text, I would recommend looking at the sample, "Transformation of Characters" from the Fonts group of samples. There is another one in the Transforms group of samples that performs animation of both text and images rotating, scaling and translating. It is the third sample in this group. This sample does use g2.drawString. It uses g2.setTransform to reset the transformation for each object to be transformed. If you will be using Swing components in your interface, keep in mind that these components are affected when you use g2.setTransform. To fix this problem, you can perform all of your drawing in a BufferedImage and then draw this BufferedImage to the g2 context when you are finished drawing. This sample solves the problem in this way.


Problem: I noticed your comment on the bottom of Creating and Deriving Fonts regarding bug 4155852. This bug has been closed with no action taken. Is it true that you can't apply a style to fonts such as Arial?

    The problem is that font-to-style matching is not working properly for physical fonts (such as Arial or Palatino). You can only apply styles to logical fonts at this point (such as Dialog or SansSerif). As a workaround until the bug is fixed, you could do the following:

     Font f = new Font("Palatino Bold", Font.PLAIN, 12); 
    
    instead of:

                       
     Font f = new Font("Palatino", Font.BOLD, 12);
    

 


 

Improving Printing Performance

Starting in 1.3, two strategies should help reduce the size of printer spool files, improve printing performance, and even improve print quality:

  • Avoid printing with alpha colors. Consider drawing such rendering into an opaque offscreen image and printing that.
  • Try to use printer fonts on Solaris and Linux. The logical fonts are safe, as are the standard Postscript font names.

Starting in 1.4.1, you can greatly reduce printer file sizes when printing to black-and-white printers on Solaris and Linux. The trick is to explicitly make sure that everything you send to the printer is some shade of gray (which includes black and white). This takes advantage of better compression for grayscale.

To do this, make sure that all colors and paints use gray only, and that all images are first converted to grayscale. One way of doing this is to redraw an original color image into a grayscale image of the same dimensions, and then send the new grayscale image to the printer.

We cannot automatically do this conversion as then you would be unable to print in color at all. Since Postscript printers are predominantly B&W, you might decide that you can benefit from this tip when you know the nature of the output device. The tip doesn't apply when printing to printers attached to Windows machines because they use GDI, not our own Postscript generator, to generate printer files.

 

Home