Slick graphics can change an average program into an outstanding one. That's because most of us are visually oriented. We prefer pictures over text descriptions, maps over directions, and colorful graphs over equations. We also prefer programs that use graphics to simplify the information they present. In Chapter 7, "Working with the Canvas," you learned how to use the basic graphics capabilities of the AWT, which were present in JDK 1.1. JDK 1.2 extends the JDK 1.1 graphics capabilities with the Java 2D API, which provides comprehensive support for drawing, image processing, and text rendering. Concurrent with JDK 1.2, but separate from it, JavaSoft also released the Java 3D API. This API provides support for three-dimensional graphics used in advanced modeling and virtual reality applications.
In this chapter, you'll explore the Java 2D and Java 3D APIs. You'll learn how the Java 2D API can be used to enhance line drawing, painting, and text rendering operations. You'll then use the Java 3D API to display and manipulate three-dimensional objects. When you finish this chapter, you'll be able to use these advanced graphics capabilities in your programs.
The Java 2D API is a very large part of the JDK 1.2 AWT. It consists of the following six packages, plus numerous classes and interfaces from the java.awt package:
Because the 2D API is so large, it is impractical to learn this API by going directly to its classes and interfaces. Instead, it is easier first to learn the capabilities that it provides, and then which classes and interfaces are important in providing these capabilities.
The 2D API provides the following general capabilities:
By learning to use these capabilities, you can greatly improve the quality of the graphics in your applets and applications.
In Chapter 7, you learned how to draw line art and display text and images using the Graphics class. The Java 2D API extends the Graphics class with the Graphics2D class, which is the heart of the 2D API. It lets you do everything that you could with the AWT 1.1 Graphics class, plus a whole lot more.
You obtain a reference to a Graphics2D object in the same manner as a Graphics object. In most cases, you'll simply cast a Graphics object to a Graphics2D object. The following code shows how this is accomplished using the paint() method of the Component class:
public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; /* Now I can use the methods of Graphics and Graphics2D with g2d. */ }
The Graphics2D class uses two coordinate spaces for drawing graphics, text, and images. The user coordinate space is a device-independent logical coordinate space. The origin (0,0) is in the upper-left corner. Horizontal (x) coordinates increase toward the right of the drawing area, and vertical (y) coordinates increase toward the bottom of the drawing area.
Each target rendering device (such as a monitor screen or a printer) has its own separate device space. The dimensions, orientation, and display capabilities of the display space vary with each device. The Java 2D rendering system automatically and transparently converts between user space and device space.
The available display devices are described by the GraphicsEnvironment class. The static getLocalGraphicsEnvironment() method returns a GraphicsEnvironment object for the local system. The getAllFonts() method returns a list of all available Font objects. The getScreenDevices() and getPrinterJob() methods provide access to screen and printer devices.
The GraphicsDevice class is used to describe graphics devices, such as screens or printers. Each of these devices may have one or more configurations. For example, a screen may be capable of both 640¥480 and 800¥600 resolution. The GraphicsConfiguration class is used to describe the configuration of a GraphicsDevice object. The getConfigurations() method of the GraphicsDevice class is used to obtain access to these configurations.
The Shape interface defines the methods supported by all geometrical shapes. This interface is implemented by the following classes of the java.awt.geom and java.awt packages:
These classes are extended by other, more specific shape classes in the java.awt.geom and java.awt packages. Subclasses with the .Float and .Double extensions are used to specify shapes using floating-point coordinates.
The Java 2D API also implements fonts in terms of the Shape interface. Individual characters or character combinations (ligatures) are represented as a combination of glyphs. Glyphs represent individual shapes that are used to display text using a particular font face. The GlyphSet, GlyphMetrics, and GlyphJustificationInfo classes are used to work with glyphs. However, in most cases you won't use these classes. Instead, you'll simply draw text on the screen using a particular font. The important point to remember is that your text is actually drawn as a set of shapes. This means that you can change the pen and fill style used to draw your text. (Refer to "Pen and Fill Styles" later in this chapter.) You can also rotate, translate, and manipulate text using the same methods that you use for other geometrical objects.
THE BASIC DRAWING METHOD OF THE GRAPHICS2D CLASS IS DRAW(). YOU CAN USE IT TO DRAW ANY OBJECT THAT IMPLEMENTS THE SHAPE INTERFACE. GRAPHICS2D ALSO SUPPORTS DRAWIMAGE() AND DRAWSTRING() METHODS THAT ARE TAILORED TO IMAGE AND TEXT DRAWING. FINALLY, BECAUSE GRAPHICS2D IS A SUBCLASS OF GRAPHICS, IT SUPPORTS ALL OF THE STANDARD GRAPHICS DRAWING METHODS.
Listing 20.1 illustrates the basics of line and text drawing using Java 2D. When you run this program, it displays the five geometric objects shown in Figure 20.1. These objects are a diagonal line, a rectangle, a circle, a tetragon, and a rounded rectangle.
FIGURE 20.1. The Draw2D program displays a variety of geometric figures.
The shapes array contains the five objects shown in Figure 20.1. The elements of this array are created by the createShapes() method. These shapes consist of the following objects from the java.awt.geom package:
The paint() method of the MyCanvas class displays the five objects. It casts its Graphics parameter to a Graphics2D object and then invokes the draw() method of Graphics2D for each of the objects in the shapes array.
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class Draw2D extends Frame { static final int numShapes = 5; Shape shapes[] = new Shape[numShapes]; static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ Draw2D app = new Draw2D(); } public Draw2D() { super("Draw2D"); setupMenuBar(); add("Center",new MyCanvas()); createShapes(); setSize(width,height); addWindowListener(eh); show(); } void createShapes() { for(int i=0;i<shapes.length;++i) shapes[i] = null; shapes[0] = new Line2D.Double(0.0,0.0,100.0,100.0); shapes[1] = new Rectangle2D.Double(100.0,100.0,200.0,200.0); shapes[2] = new Ellipse2D.Double(200.0,200.0,100.0,100.0); GeneralPath path = new GeneralPath(new Line2D.Double(300.0,100.0,400.0,150.0)); path.append(new Line2D.Double(400.0,150.0,350.0,200.0),true); path.append(new Line2D.Double(350.0,200.0,325.0,175.0),true); path.append(new Line2D.Double(325.0,175.0,300.0,100.0),true); shapes[3] = path; shapes[4] = new RoundRectangle2D.Double(350.0,250,200.0,100.0,50.0,25.0); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics; for(int i=0;i<shapes.length;++i) { if(shapes[i]!=null) g.draw(shapes[i]); } } } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
When graphics and text are drawn to a particular device, they may appear to be somewhat jagged, depending on the device resolution. This effect, known as aliasing, occurs because the position of the pixels used to render the drawing differs from their ideal mathematical location. The jagged effect of aliasing can be reduced using a technique known as antialiasing, which sets the values of surrounding pixels to smooth out jagged contours. Antialiasing requires a fair amount of computational power and may slow down performance. To use antialiasing, use the setRenderingHints() method of Graphics2D to set the ANTIALIASING hint to ANTIALIAS_ON. You can also set the RENDERING hint to RENDER_QUALITY to improve the overall quality in which graphics and text are rendered. For example, the following code sets both the ANTIALIASING and RENDERING hints:
Graphics2D g = (Graphics2D) getGraphics(); g.setRenderingHints(Graphics2D.ANTIALIASING,Graphics2D.ANTIALIAS_ON); g.setRenderingHints(Graphics2D.RENDERING,Graphics2D.RENDER_QUALITY);
You can check the state of ANTIALIASING and RENDERING using the getRenderingHints() method of Graphics2D.
One of the advantages of the Java 2D API is its support for pen and fill styles. Pen styles can be used to change the type of line used to draw a Shape object. For example, you can change the thickness and pattern of a line so that it is displayed as a thin dotted line. The pen style of an object is specified by an object of a class that implements the Stroke interface. The BasicStroke class provides an implementation of this interface. It allows strokes to be defined based on the following:
Fill patterns are defined by objects that implement the Paint interface. Three types of fill patterns are defined:
The Color class provides a default implementation of Paint for providing a solid color fill pattern. The GradientPaint class defines a fill pattern as a gradient between two colors. The TexturePaint class implements Paint to define a fill pattern, using a simple image fragment that is repeated uniformly throughout the interior of the object being filled.
Listing 20.2 illustrates the basics of pen and fill styles. Figure 20.2 shows the output generated by this program. Note that it draws the same objects as the Draw2D program in Listing 20.1. It just uses different pen and fill styles.
The PenFill program is essentially the same as Draw2D. The only notable differences are in the paint() method of MyCanvas. The dashPattern array is used to create a dash pattern consisting of a ten-unit dash, followed by a ten-unit space, five-unit dash, and five-unit space. The setStroke() method of Graphics2D is used to define the overall pen style. The CAP_ROUND and JOIN_MITER constants are used to specify the ends and joins of line segments.
The setPaint() method is used to set the background color of the rectangle to blue. The fill() method is used to perform the actual filling of the rectangle.
FIGURE 20.2. The PenFill program adds pen and fill styles to Draw2D.
The setPaint() method is also used to set the fill color of the circle to a color gradient between red and green. The color gradient is specified as an object of the GradientPaint class.
The texture variable is assigned a new object of the TexturePaint class. The arguments to the TexturePaint constructor consist of a BufferedImage object containing the fill pattern, a Rectangle2D.Double object identifying the fill area, and the type of interpolation algorithm to be used in filling objects within the area.
The createBufferedImage() method returns a BufferedImage object containing the fill pattern. This fill pattern consists of a 20¥20 image that uses a red-blue-green (RGB) color model. The pattern consists of two perpendicular diagonal lines that form an "X" pattern.
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class PenFill extends Frame { static final int numShapes = 5; Shape shapes[] = new Shape[numShapes]; static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ PenFill app = new PenFill(); } public PenFill() { super("PenFill"); setupMenuBar(); add("Center",new MyCanvas()); createShapes(); setSize(width,height); addWindowListener(eh); show(); } void createShapes() { for(int i=0;i<shapes.length;++i) shapes[i] = null; shapes[0] = new Line2D.Double(0.0,0.0,100.0,100.0); shapes[1] = new Rectangle2D.Double(100.0,100.0,200.0,200.0); shapes[2] = new Ellipse2D.Double(200.0,200.0,100.0,100.0); GeneralPath path = new GeneralPath(new Line2D.Double(300.0,100.0,400.0,150.0)); path.append(new Line2D.Double(400.0,150.0,350.0,200.0),true); path.append(new Line2D.Double(350.0,200.0,325.0,175.0),true); path.append(new Line2D.Double(325.0,175.0,300.0,100.0),true); shapes[3] = path; shapes[4] = new RoundRectangle2D.Double(350.0,250,200.0,100.0,50.0,25.0); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics; float[] dashPattern = {10.0f,10.0f,5.0f,5.0f}; g.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 10.0f, dashPattern, 0.0f)); g.draw(shapes[0]); g.setPaint(Color.blue); g.draw(shapes[1]); g.fill(shapes[1]); g.setPaint(new GradientPaint(350.0f,200.0f,Color.red, 325.0f,175.0f,Color.green)); g.draw(shapes[2]); g.fill(shapes[2]); g.setColor(Color.black); g.draw(shapes[3]); TexturePaint texture = new TexturePaint( createBufferedImage(), new Rectangle2D.Double(350.0,250,200.0,100.0); g.setPaint(texture); g.draw(shapes[4]); g.fill(shapes[4]); } BufferedImage createBufferedImage() { BufferedImage image = new BufferedImage(20,20,BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.draw(new Line2D.Double(0.0,0.0,10.0,10.0)); g.draw(new Line2D.Double(0.0,10.0,10.0,0.0)); return image; } } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
In some cases, you may not want to display an entire Shape or Image object. The 2D API allows you to define a clipping path, which is a subset of the graphic or image to be displayed. Only the portions of a Shape or Image object that lies within the clipping path are displayed.
The setClip() method of the Graphics class (inherited by Graphics2D) is used to set the current clipping path. The setClip() method takes a Shape object as an argument, and any shape may be used for clipping.
Listing 20.3 illustrates how clipping is implemented. Figure 20.3 shows the output generated by this program. Note that it draws the same objects as the PenFill program in Listing 20.2. It just clips the objects to a 300¥250 rectangle. The only significant difference between Clipper and PenFill is the inclusion of the following statement in the paint() method of MyCanvas:
g.setClip(new Rectangle2D.Double(75.0,75.0,300.0,250.0));
This statement invokes the setClip() method of Graphics to set the clipping area to a Rectangle2D.Double object, consisting of a 300¥250 rectangle located at (75,75). All objects subsequently drawn to the Graphics2D object are clipped if they extend beyond this rectangle.
FIGURE 20.3. The Clipper program adds clipping to PenFill.
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class Clipper extends Frame { static final int numShapes = 5; Shape shapes[] = new Shape[numShapes]; static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ Clipper app = new Clipper(); } public Clipper() { super("Clipper"); setupMenuBar(); add("Center",new MyCanvas()); createShapes(); setSize(width,height); addWindowListener(eh); show(); } void createShapes() { for(int i=0;i<shapes.length;++i) shapes[i] = null; shapes[0] = new Line2D.Double(0.0,0.0,100.0,100.0); shapes[1] = new Rectangle2D.Double(100.0,100.0,200.0,200.0); shapes[2] = new Ellipse2D.Double(200.0,200.0,100.0,100.0); GeneralPath path = new GeneralPath(new Line2D.Double(300.0,100.0,400.0,150.0)); path.append(new Line2D.Double(400.0,150.0,350.0,200.0),true); path.append(new Line2D.Double(350.0,200.0,325.0,175.0),true); path.append(new Line2D.Double(325.0,175.0,300.0,100.0),true); shapes[3] = path; shapes[4] = new RoundRectangle2D.Double(350.0,250,200.0,100.0,50.0,25.0); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics; g.setClip(new Rectangle2D.Double(75.0,75.0,300.0,250.0)); float[] dashPattern = {10.0f,10.0f,5.0f,5.0f}; g.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 10.0f, dashPattern, 0.0f)); g.draw(shapes[0]); g.setPaint(Color.blue); g.draw(shapes[1]); g.fill(shapes[1]); g.setPaint(new GradientPaint(350.0f,200.0f,Color.red, 325.0f,175.0f,Color.green)); g.draw(shapes[2]); g.fill(shapes[2]); g.setColor(Color.black); g.draw(shapes[3]); TexturePaint texture = new TexturePaint( createBufferedImage(), new Rectangle2D.Double(350.0,250,200.0,100.0); g.setPaint(texture); g.draw(shapes[4]); g.fill(shapes[4]); } BufferedImage createBufferedImage() { BufferedImage image = new BufferedImage(20,20,BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.draw(new Line2D.Double(0.0,0.0,10.0,10.0)); g.draw(new Line2D.Double(0.0,10.0,10.0,0.0)); return image; } } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
As mentioned earlier in this chapter in the "Drawing Graphics" section, Java font faces are comprised of glyphs, which are drawn in the same manner as other Shape objects. This means that text can be drawn using different pen and fill styles, and it can also be clipped and rotated. You'll learn how to rotate graphics and text in the "Using Transforms" section later in this chapter.
The Font class provides several methods for accessing information about how glyphs are drawn. The getGlyphOutline() method returns a Shape object that describes a glyph code. The getGlyphSet() returns a GlyphSet object that describes a text string. The GlyphSet object provides access to the glyph codes (int values) that define individual glyphs. The getGlyphMetrics() and getGlyphJustificationInfo() methods return metric and justification information about a glyph code.
The Java 2D API also provides extensive support for text formatting and layout. The TextAttribute class is used to specify the style attributes of text.
The TextLayout class is used to format and layout stylized text. The TextLayout class takes care of nearly all of your text processing needs and provides the following capabilities:
Support of bidirectional text (required to display text in some foreign languages)
TextLayout objects are constructed using String objects and displayed using the draw() method. TextLayout also provides numerous methods for obtaining information about the text that it processes.
Listing 20.4 shows how the TextLayout class is used to create text using multiple fonts. The StyledText program displays three lines of text, as shown in Figure 20.4.
The paint() method of MyCanvas invokes the createStyledStrings() method to create an array of TextLayout objects. The yOffset variable specifies the vertical position where the next string should be displayed. A for statement iterates through the TextLayout array and draws each string at a horizontal position of 100 and a vertical position of 100 plus the value of yOffset. The getAscent(), getDescent(), and getLeading() methods of TextLayout are used to calculate the new value of yOffset.
FIGURE 20.4. The StyledText program uses the TextLayout class to create and display text using multiple fonts.
The createStyledStrings() method creates a three-element array of TextLayout, which it uses as a return value. Three fonts are used to provide variety to the text contained in these three strings.
import java.awt.*; import java.awt.event.*; import java.awt.font.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class StyledText extends Frame { static final int width = 600; static final int height = 400; MyMenuBar menuBar; FontRenderContext frc;EventHandler eh = new EventHandler(); public static void main(String args[]){ StyledText app = new StyledText(); } public StyledText() { super("StyledText"); setupMenuBar(); add("Center",new MyCanvas()); setSize(width,height); addWindowListener(eh); show(); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics;frc=g.getFontRenderContext(); TextLayout[] s = createStyledStrings(); int yOffset = 0; for(int i=0;i<s.length;++i) { s[i].draw(g,100,100+yOffset); yOffset += s[i].getAscent()+s[i].getDescent()+s[i].getLeading(); } } } TextLayout[] createStyledStrings() { TextLayout[] s = new TextLayout[3]; Font f1 = new Font("Helvetica",Font.BOLD,24); Font f2 = new Font("TimesRoman",Font.ITALIC,14); Font f3 = new Font("Helvetica",Font.PLAIN,12); s[0] = new TextLayout("This is the first sentence.",f1,frc); s[1] = new TextLayout("This is the second sentence.",f2,frc); s[2] = new TextLayout("This is the third sentence.",f3.frc); return s; } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
Displaying Images
In addition to its extensive support for drawing graphics and text, the Java 2D API provides additional capabilities for image processing. Images are two-dimensional arrays of pixels. These arrays are often referred to as raster images or rasters. The color of each pixel is specified by a value that is either a color value or an index to a table of color values. In the first case, the image is said to use a direct color model. In the second case, the image uses an indexed color model. The Java 2D API supports both types of color models via the DirectColorModel and IndexedColorModel classes, which contain several subclasses that provide support for a variety of specific color models.
The Java 2D API also provides support for image compositing. Compositing is the process of rendering an image, graphic, or text based on the colors of objects that have already been rendered. For example, consider displaying a yellow sun on a blue sky. The yellow image can simply replace the pixels of the blue image, or it can blend in with the blue image. Compositing defines this blending process. The Composite interface defines methods that are implemented by classes that support compositing. Composite objects are used by Graphics2D objects when they draw graphics or text, or display images.
The AlphaComposite class provides a default implementation of the Composite interface. AlphaComposite supports the blending of new pixel data with existing pixel data using alpha color values in the new and existing pixel data. The alpha color values identify the transparency of a color using a scale of 0.0 to 1.0. If a value of 1.0 is used, the new pixel data completely replaces existing pixel data. If a value of 0.0 is used, any existing pixel data is used instead of new pixel data. If a number between 0.0 and 1.0 is used, the new and existing pixel data are used in proportion to this value. For example, a value of 0.6 causes the color of the new pixel data to contribute 60% of the value of the composite color and the existing pixel data to contribute 40% of the value. In addition to the alpha values of the new and existing pixel data, a separate alpha scale factor may also be specified. This scale factor ranges from 0.0 to 1.0 and is multiplied by the alpha values of the new and existing colors. In addition to this scale factor, the AlphaComposite class provides additional compositing rules. These rules are implemented as class constants and may be used to specify a particular compositing approach.
Listing 20.5 illustrates how the AlphaComposite class is used to implement compositing. The output of the Composite program is shown in Figure 20.5. It consists of a purple rectangle and a red rectangle. The red rectangle is displayed over the purple rectangle. Compositing is used to blend the colors of the two images together at their point of intersection. The Composite program uses a transparency value of 0.5 by default. You can change this value by supplying a new transparency value between 0.0 and 1.0 as a command-line argument.
The Composite program uses the alpha field variable to store the transparency value that is used to composite the two images. This value is modified if a command-line argument is supplied.
The paint() method of the MyCanvas class creates an AlphaComposite object using the static getInstance() method of the AlphaComposite class. This object uses a source-over-compositing approach and the alpha value specified by the alpha field variable. The setComposite() method of Graphics2D is used to specify the use of the AlphaComposite object.
FIGURE 20.5. The Composite program shows how images can be blended together using the AlphaComposite class.
Having set up compositing, the images contained in the image0.gif and image1.gif files are displayed using the drawImage() method of the Graphics class. The first image is the purple rectangle, and the second image is the red rectangle.
import java.awt.*; import java.awt.event.*; import java.awt.image.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class Composite extends Frame { static float alpha = 0.5f; static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ if(args.length>0) alpha = (new Float(args[0])).floatValue(); Composite app = new Composite(); } public Composite() { super("Composite"); setupMenuBar(); add("Center",new MyCanvas()); setSize(width,height); addWindowListener(eh); show(); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics; AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,alpha); g.setComposite(composite); Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image0 = toolkit.getImage("image0.gif"); Image image1 = toolkit.getImage("image1.gif"); g.drawImage(image0,200,100,this); g.drawImage(image1,100,150,this); } } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
One of the unique features of the Java 2D API is that it provides a uniform coordinate transformation model. This model includes the AffineTransformation class, which supports linear transformations between sets of 2D coordinates. A linear transformation calculates new coordinates by multiplying existing coordinates by a scaling factor and then adding an offset value. The AffineTransform class can be used to translate (move), scale, rotate, flip, and shear graphics, text, and images.
AffineTransformation objects are created by specifying a transformation matrix as an argument to the AffineTransformation() constructor. This matrix is defined as follows:
m00 | m01 | m02 |
m10 | m11 | m12 |
0 | 0 | 1 |
THE MATRIX IS USED TO TRANSFORM A POINT (X,Y) TO A NEW POINT (X',Y') BY FIRST EXTENDING (X,Y) TO (X,Y,1) AND THEN MULTIPLYING THE MATRIX BY THIS POINT:
m00 | m01 | m02 | x = m00*x + m01*y + m02 |
m10 | m11 | m12 | y = m10*x + m11*y + m12 |
0 | 0 | 1 | 1 = 1 |
(x',y') is then set to (m00*x + m01*y + m02,m10*x + m11*y + m12).
The AffineTransformation class provides a set of static convenience methods that can be used instead of specifying a transformation matrix. These methods are as follows:
An AffineTransformation object is used by invoking the transform() method of the Graphics2D class. Multiple transforms can be used by repeatedly invoking transform(). The last transformation specified is the first transformation that is used. Once a transformation has been specified, it is used with all graphics, text, and images that are drawn to the Graphics2D object.
Listing 20.6 shows how the AffineTransformation class is used to transform graphics, text, and images. Figure 20.6 shows the output of the Transform program. It rotates the display of a line, a red rectangular image, and text by p/16 radians.
FIGURE 20.6. The Transform program shows how graphics, images, and text can be rotated using the AffineTransforma- tion class.
The paint() method of MyCanvas creates an AffineTransform object using the static getRotateInstance() method of the AffineTransform class. The rotation is specified as p/16 radians, which is about 11 degrees. The setTransform() method of Graphics2D is used to put the transform into effect. The following objects are then drawn to the Graphics2D object:
These objects are rotated by p/16 radians when they are rendered.
import java.awt.*; import java.awt.event.*; import java.awt.font.*; import java.awt.geom.*; import java.awt.image.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar; public class Transform extends Frame { static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ Transform app = new Transform(); } public Transform() { super("Transform"); setupMenuBar(); add("Center",new MyCanvas()); setSize(width,height); addWindowListener(eh); show(); } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class MyCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D) graphics; AffineTransform transform = AffineTransform.getRotateInstance(Math.PI/16.0d); g.setTransform(transform); Line2D.Double shape = new Line2D.Double(0.0,0.0,300.0,300.0); g.draw(shape); g.getFont(new Font("Helvetica",Font.BOLD,24)); String text = ("Java 2D API"); g.drawString(text,300,50); Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image = toolkit.getImage("image1.gif"); g.drawImage(image,100,150,this); } } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
NOTE: The rotate(), scale(), shear(), and translate() methods of the Graphics2D class can be used in lieu of working with AffineTransformation objects.
NOTE: An affine transformation always translates straight lines into straight lines and parallel lines into parallel lines.
The Java 3D API is a separate standard extension API that is used to create three- dimensional graphics, applets, and applications. It can be downloaded from JavaSoft's Web site at http://java.sun.com:80/products/java-media/3D/index.html. It consists of a self-installing executable file. After installing this file, follow the directions in the Readme.txt file for setting up your CLASSPATH. The Java 3D API is supported on Windows, UNIX, Macintosh, and JavaOS platforms. It uses the graphics APIs provided by these operating systems, such as OpenGL, Direct3D, and QuickDraw3D.
The 3D API consists of the two packages, javax.media.j3d and javax.vecmath, and supporting classes and interfaces from the com.sun.j3d.utils package. The javax.media.j3d package provides the basic classes and interfaces that implement the Java 3D API. The javax.vecmath class provides support for vector-based mathematics. The com.sun.j3d.utils package is not documented as part of the Java 3D API, but it contains classes and interfaces that simplify the building of 3D applications and applets.
The javax.media.j3d package consists of three interfaces and over 100 classes that support basic 3D operations. The features provided by these classes and interfaces include the following:
The classes and interfaces that support these operations were designed using the best features of the OpenGL, QuickDraw3D, Direct3D, and XGL graphics libraries.
The javax.vecmath package consists of a number of classes that implement 3D objects.
The Tuple3b class represents a point in 3 space. It is extended by Color3b, which implements a 3-byte vector that is used for colors. The Tuple3f and Tuple3d classes provide floating-point and double-precision 3-space points. The Tuple4b class represents a point in 4 space. The Tuple4f and Tuple4d classes provide floating-point and double-precision representations. The Color3f, Color4b, and Color4f classes are used to represent three- and four-dimensional color values.
The Point2f, Point3f, Point3d, Point4f, and Point4d classes represent points in 2, 3, and 4 space.
The Vector2f, Vector3f, Vector4f, Vector3d, and Vector4d classes provide floating-point and double-precision vectors in 2, 3, and 4 space. The GVector class provides a general vector implementation.
The Matrix3f, Matrix3d, Matrix4f, and Matrix4d classes represent three- and four-dimensional matrices. The GMatrix class provides a general matrix implementation.
The TexCoord2f and TexCoord3f classes represent coordinates in 2 and 3 space.
The Quat4f and Quat4d classes represent four-dimensional quaternion values.
The AxisAngle4d and AxisAngle4f classes represent an angle about a vector. AxisAngle4d uses double values, and AxisAngle4f uses floating-point values.
A complete treatment of 3D programming with the Java 3D API could fill an entire book by itself. Rather than wading through all the classes of the Java 3D API, I'll shown you a simple 3D application and describe the classes and interfaces that make it work. You can then use this program as a basis for developing your own Java 3D applications and applets.
Listing 20.7 contains the source code of the Draw3D program. This program draws a 3D colored cube in the center of the application window and rotates the cube about the y-axis, as shown in Figure 20.7. In Java 3D, the default coordinate system is a right-handed system, with +y being up, +x horizontal to the right, and +z being outward toward the viewer.
FIGURE 20.7. The Draw3D program displays a moving 3D cube.
The Draw3D program defines a Canvas3D object and assigns it to the canvas3D field variable. The Draw3D() constructor adds the Canvas3D object to the center of the application window. The Canvas3D class provides a canvas for 3D rendering. It is analogous to the Canvas class of java.awt.
The setup3DGraphics() method is invoked by the Draw3D constructor to set up the 3D scene to be rendered. This method creates a SimpleUniverse object. The SimpleUniverse class (from the com.sun.j3d.utils.universe package) simplifies the creation of a virtual 3D world. The getViewingPlatform() method is invoked to retrieve a ViewPlatform object for the universe. The setNominalViewingTransform() method sets up a simple default view of the universe. The Java 3D API provides the capability to view a 3D world from a variety of perspectives. The addBranchGraph() method adds a BranchGraph object to the universe. A BranchGraph is used to define a 3D scene.
The createSceneGraph() method creates the BranchGraph object that specifies the 3D scene to be displayed. It creates a BranchGraph object to be used as a return value and adds a TransformGroup object to it. The TransformGroup is used to define a 3D transformation on objects in the scene. In this case, it is used to rotate a colored cube. The setCapability() method is used to allow the TransformGroup object to be updated during program execution.
The ColorCube class of the com.sun.j3d.utils.geometry package provides a quick and easy way to create a colored 3D cube. This is an undocumented part of the Java 3D API. Without this class, we would have to construct and color our own 3D object. The relative size of the ColorCube object is set to 0.25, and the object is added to the TransformGroup.
A RotationInterpolator object is created to rotate the cube. It is constructed using a default Alpha object and the TransformGroup object. The Alpha object defines a default timing to be used in the rotation. A BoundingSphere object is constructed to define the bounds of the rotation. This object is centered at the origin and has a radius of 100. The setSchedulingBounds() method is used to associate the BoundingSphere object with the RotationInterpolator object. The RotationInterpolator object is then added to the TransformGroup object.
Note that no paint() method is needed to render the 3D scene. The Java 3D rendering engine automatically paints the Canvas3D object.
import java.awt.*; import java.awt.event.*; import javax.media.j3d.*; import javax.vecmath.*; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.universe.*; import ju.ch09.MyMenu; import ju.ch09.MyMenuBar;
public class Draw3D extends Frame { Canvas3D canvas3D = new Canvas3D(null); static final int width = 600; static final int height = 400; MyMenuBar menuBar; EventHandler eh = new EventHandler(); public static void main(String args[]){ Draw3D app = new Draw3D(); } public Draw3D() { super("Draw3D"); setupMenuBar(); add("Center",canvas3D); setSize(width,height); setup3DGraphics(); addWindowListener(eh); show(); } void setup3DGraphics() { // Create a simple universe that is used for the 3D world. SimpleUniverse universe = new SimpleUniverse(canvas3D); // Get the ViewPlatform for this universe and set its view. universe.getViewingPlatform().setNominalViewingTransform(); // Create a scene and add it to the universe. universe.addBranchGraph(createSceneGraph()); } BranchGroup createSceneGraph() { // Create the return object BranchGroup branchGroup = new BranchGroup(); // Create a transform group for creating a 3D transformation. TransformGroup transGroup = new TransformGroup(); // Allow the transform group to be updated during execution. transGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // Add the transform group to the branch group branchGroup.addChild(transGroup); // Add a color cube to the transform group transGroup.addChild(new ColorCube(0.25)); // Create a 3D transformation object. RotationInterpolator ri = new RotationInterpolator( new Alpha(), transGroup); BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0); ri.setSchedulingBounds(bounds); transGroup.addChild(ri); return branchGroup; } void setupMenuBar(){ Object menuItems[][] = {{"File","Exit"}}; menuBar = new MyMenuBar(menuItems,eh,eh); setMenuBar(menuBar); } class EventHandler extends WindowAdapter implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e){ String selection=e.getActionCommand(); if("Exit".equals(selection)){ System.exit(0); } } public void itemStateChanged(ItemEvent e){ } public void windowClosing(WindowEvent e){ System.exit(0); } } }
In this chapter, you explored the Java 2D and Java 3D APIs. You learned how the Java 2D API can be used to enhance line drawing, painting, and text rendering, and you created applications that demonstrated these capabilities. You also learned about the Java 3D API and used it to display and manipulate three-dimensional objects. In the next chapter, you'll learn how to add multimedia capabilities to your applets and applications.
© Copyright, Macmillan Computer Publishing. All rights reserved.