New support for 2D shapes, transforms, curves, and fonts enters the core environment with Java 1.2 The Java 2D API is a core Java 1.2 platform API (see Resources for a variety of information on the API and its implementations). Implementations of the API are available as a part of the Java Foundation Classes (JFC) in the current beta releases of the Sun JDK for Windows NT/95 and Solaris. As Java 1.2 is finalized, Java 2D should become available on more platforms.Note that although Java 2D has been developed somewhat independently of the other parts of the JFC, it is nonetheless a core part of the 1.2 AWT. We will make the distinction and point out 2D-specific features for discussion, but you should remember that this functionality is just as central to 1.2 graphics as the old 1.0 and 1.1 AWT support.Java 2D extends the previous AWT mechanisms for drawing 2D graphics, manipulating text and fonts, loading and using images, and defining and dealing with colors and color spaces. We will be exploring these new mechanisms in this and future columns. A note about nomenclature and conventionsFor this column, my primary development platform will be a PC running Windows 95 or Windows NT. I hope to provide other platform-specific tips and tricks where possible, but I will focus on Windows since that’s where I will be spending most of my time.When I write a method name, it should always be of the form methodname(). The trailing parentheses are meant to set this apart as a method. The method may or may not take parameters. In practice, the context should always make this clear.Source code listings will be given with line numbers included. I plan to use the line numbers to cross-reference the article text and the code listings as appropriate. This should also make it much easier for you to annotate the column, should you chose to print a copy. Please note, however, that the source files linked from the column will be regular *.java files (sans line numbers) so that you can download and develop with them. Because I will be writing about many of the Media and Communications APIs in the coming months, I want to make sure that all of the sample code makes sense as a whole as well as in its individual parts. I will attempt to consistently name my examples and place them into sensical packages.The top of my package hierarchy will be:com.javaworld.media Each API or topic that I write about will have at least one subpackage under this top level. For instance, all of the code for this Java 2D article will be in: com.javaworld.media.j2d So, to invoke the first example application on Java 2D, you would download the code, put it in your classpath, then use:java com.javaworld.media.j2d.Example01 (If the namespace is too long for your liking or for some other reason you want to use the example code without having to use the fully qualified name, simply comment out the package line at the beginning of each source code file.)I will generate a Java Archive (jar) file for each article’s example code and class files. This archive will be made available in the Resources of each column, should you wish to download it and execute the examples from within the archive. I will also keep an up-to-date jar file containing all of the code and classes from my current and previous Media Programming columns. This all-encompassing jar file will be available on my personal Web site.One final point on the examples: I have chosen to make each example, unless I specifically note otherwise, a standalone application or applet. This will lead to some repetition of code from time to time, but I feel it best preserves the integrity of each individual example.Enough about conventions. Let’s get started programming with Java 2D! Graphics2D: A better Graphics classThe central class within the Java 2D API is the java.awt.Graphics2D abstract class, which subclasses java.awt.Graphics to extend 2D rendering functionality. Graphics2D adds more uniform support for manipulations of a variety of shapes, in effect making text, lines, and all sorts of other two-dimensional shapes comparable in their capabilities and utility.Let’s start with a simple example showing how you get and use a Graphics2d reference.001 package com.javaworld.media.j2d; 002 003 import java.awt.*; 004 import java.awt.event.*; 005 006 public class Example01 extends Frame { 007 /** 008 * Instantiates an Example01 object. 009 **/ 010 public static void main(String args[]) { 011 new Example01(); 012 } 013 014 /** 015 * Our Example01 constructor sets the frame's size, adds the 016 * visual components, and then makes them visible to the user. 017 * It uses an adapter class to deal with the user closing 018 * the frame. 019 **/ 020 public Example01() { 021 //Title our frame. 022 super("Java 2D Example01"); 023 024 //Set the size for the frame. 025 setSize(400,300); 026 027 //We need to turn on the visibility of our frame 028 //by setting the Visible parameter to true. 029 setVisible(true); 030 031 //Now, we want to be sure we properly dispose of resources 032 //this frame is using when the window is closed. We use 033 //an anonymous inner class adapter for this. 034 addWindowListener(new WindowAdapter() 035 {public void windowClosing(WindowEvent e) 036 {dispose(); System.exit(0);} 037 } 038 ); 039 } 040 041 /** 042 * The paint method provides the real magic. Here we 043 * cast the Graphics object to Graphics2D to illustrate 044 * that we may use the same old graphics capabilities with 045 * Graphics2D that we are used to using with Graphics. 046 **/ 047 public void paint(Graphics g) { 048 //Here is how we used to draw a square with width 049 //of 200, height of 200, and starting at x=50, y=50. 050 g.setColor(Color.red); 051 g.drawRect(50,50,200,200); 052 053 //Let's set the Color to blue and then use the Graphics2D 054 //object to draw a rectangle, offset from the square. 055 //So far, we've not done anything using Graphics2D that 056 //we could not also do using Graphics. (We are actually 057 //using Graphics2D methods inherited from Graphics.) 058 Graphics2D g2d = (Graphics2D)g; 059 g2d.setColor(Color.blue); 060 g2d.drawRect(75,75,300,200); 061 } 062 } When you execute Example01, you should see a red square and blue rectangle, as shown in the figure below. Note that there is a known performance problem with the Windows NT/95 version of the JDK 1.2 Beta 3 (the most current 1.2 release as of this column). If this example is painfully slow on your system, you may need to work around the bug as documented in JavaWorld Java Tip 55 (see Resources below for this tip). Note that just as you do not directly instantiate a Graphics object, you do not instantiate a Graphics2D object either. Rather, the Java runtime constructs a rendering object and passes it to paint() (line 047 in the Example01 code listing), and on Java 1.2 platforms and beyond, this object implements the Graphics2D abstract class as well.So far we haven’t done anything particularly special with our 2D graphics capabilities. Let’s add some code to the end of our previous example’s paint() method and bring in several features new to Java 2D (Example02):001 /** 002 * Here we use new Java 2D API features such as affine 003 * transforms and Shape objects (in this case a generic 004 * one, GeneralPath). 005 **/ 006 public void paint(Graphics g) { 007 g.setColor(Color.red); 008 g.drawRect(50,50,200,200); 009 010 Graphics2D g2d = (Graphics2D)g; 011 g2d.setColor(Color.blue); 012 g2d.drawRect(75,75,300,200); 013 014 //Now, let's draw another rectangle, but this time, let's 015 //use a GeneralPath to specify it segment by segment. 016 //Furthermore, we're going to translate and rotate this 017 //rectangle relative to the Device Space (and thus, to 018 //the first two quadrilaterals) using an AffineTransform. 019 //We also will change its color. 020 GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD); 021 path.moveTo(0.0f,0.0f); 022 path.lineTo(0.0f,125.0f); 023 path.lineTo(225.0f,125.0f); 024 path.lineTo(225.0f,0.0f); 025 path.closePath(); 026 027 AffineTransform at = new AffineTransform(); 028 at.setToRotation(-Math.PI/8.0); 029 g2d.transform(at); 030 at.setToTranslation(50.0f,200.0f); 031 g2d.transform(at); 032 033 g2d.setColor(Color.green); 034 g2d.fill(path); 035 } Note that since GeneralPath is located in the java.awt.geom package, we need to be sure we add an import line as well: import java.awt.geom.*; The output of Example02 is shown in the following figure.Java 2D allows for the specification of arbitrary shapes using the java.awt.Shape interface. A variety of default shapes such as rectangles, polygons, 2D lines, etc., implement this interface. One of the most interesting of these in terms of flexibility is java.awt.geom.GeneralPath.GeneralPaths let you describe a path with an arbitrary number of edges and a potentially extremely complex shape. In Example02, we have created a rectangle (lines 020-025), but we just as easily could have added another side or sides to make a pentagon, or heptagon, or some other multi-sided polygon. Also note that unlike standard Graphics code, Java 2D allows us to specify coordinates using floating-point numbers instead of integers. CAD vendors of the world, rejoice! In fact, Java 2D supports integer, double, and floating arithmetic in many places. You probably also noticed that when we created the path we passed a parameter, GeneralPath.EVEN_ODD, into the constructor (line 020). This parameter represents a winding rule that tells the renderer how to determine the inside of the shape specified by our path. Please refer to the Java 2D javadoc documentation referenced in the Resources for more on Java 2D winding rules.The other major innovation in Example02 revolves around the use of a java.awt.geom.AffineTransforms (lines 027-031). I’ll leave the specifics of such transforms to the reader (see Resources for articles that discuss this in greater detail), but suffice it to say that AffineTransforms allow you to operate on any Java 2D graphic to translate (move) it, rotate it, scale it, shear it, or perform combinations of the these manipulations.The key to AffineTransform lies in the concept of Device Space and User Space. Device Space is that area into which the graphics will be rendered on the screen. This is analogous to the coordinates that are used when one creates regular AWT-style Graphics-based 2D graphics. User Space, however, is a translatable, rotatable coordinate system that may be operated on by one or more AffineTransforms. Device Space and User Space coordinate systems initially overlap, with the origin at the upper left of the rendering surface (here, a Frame). The positive x axis moves right from the origin, while the positive y axis moves down.After the first transformation in Example02 (lines 028 and 029), the User Space coordinate system has been rotated 22.5 degrees counterclockwise relative to the Device Space. Both still share the same origin. (Note that rotations are specified in radians, with -PI/8 radians equaling -22.5 degrees, or 22.5 degrees CCW.) If we were to stop here and draw the rectangle, it would be rotated mostly out of our field of view in the application Frame.We next apply a second transformation (lines 030 and 031), this one a translation, after the rotation is complete. This moves the User Space coordinate system relative to the Device Space, shifting it down 200.0 (float) units and right 50.0 (float) units. When we fill in the green rectangle, it is translated and rotated relative to the Device Space.Of Bezier and higher-ordered curvesNow that we have examined how transforms can be used to manipulate graphical objects, let’s reexamine how we build complex and interesting arbitrary shapes.Curves are used throughout mathematics and computer graphics to approximate complex shapes using a finite, well-defined (and ideally small) number of mathematical points. Whereas the standard AWT did not directly support drawing with arbitrary curves in the past (Java 1.0 or 1.1 platforms), Java 2D adds built-in support for first-, second-, and third-order curves. You can draw curves with two end points and zero, one, or two control points. Java 2D computes first- and second-order curves using linear and quadratic formulas and cubic, or third-order, curves using Bezier curves. (Bezier curves are a type of parametric polynomial curve that have some very desirable properties related to computation of closed curves and surfaces. They are used in numerous graphics applications. Please refer to the Resources for more information on the use of parametric polynomials and Bezier curves in computer graphics.) The GeneralPath methods that draw each of these curves are:lineTo() for straight segments (specify only end points)quadTo() for quadratic curves (specify one control point)curveTo() for third-ordered curves (specify two control points, drawn using cubic Bezier curve)Each of these methods draws from the previous location of our path end point to the final pair of float parameters, our next end point. For lineTo(), this is the only pair given. For quadTo(), the first pair of floats refer to the one and only control point. For curveTo(), the first pair is the first control point and the middle pair is the second control point.Confused? A simple example should help. Example03 modifies our previous paint() method slightly, drawing two of the green rectangle’s edges as non-linear curves. 001 /** 002 * Here we use first-, second-, and third-order 003 * curves together in our GeneralPath. 004 **/ 005 public void paint(Graphics g) { 006 g.setColor(Color.red); 007 g.drawRect(50,50,200,200); 008 009 Graphics2D g2d = (Graphics2D)g; 010 g2d.setColor(Color.blue); 011 g2d.drawRect(75,75,300,200); 012 013 //This time our GeneralPath will have nonlinear 014 //segments, one second-order (quadratic) and another 015 //third-order (cubic). We translate and rotate this shape 016 //again, as we did before. 017 GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD); 018 path.moveTo(0.0f,0.0f); 019 path.lineTo(0.0f,125.0f); 020 path.quadTo(100.0f,100.0f,225.0f,125.0f); 021 path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f); 022 path.closePath(); 023 024 AffineTransform at = new AffineTransform(); 025 at.setToRotation(-Math.PI/8.0); 026 g2d.transform(at); 027 at.setToTranslation(50.0f,200.0f); 028 g2d.transform(at); 029 030 g2d.setColor(Color.green); 031 g2d.fill(path); 032 } Note the quadratic and cubic edges in the output.Please consult the Resources below if you need more information on curves in Java 2D.Fonts and text hit the big timeJava 2D puts fonts and text strings on the same footing as other 2D graphical objects. Text is transformable using AffineTransforms, just like any other 2D object. You can also get a Shape object describing the edges of an arbitrary string of text, then use this to clip other 2D objects or do anything else you can normally do with a Shape.Let’s try out some transforms on a simple text string in Example04. 001 /** 002 * In this example, I draw some text using Font and Graphics2D. 003 * You can see how Font has been extended within Java 2D 004 * with new capabilities, such as being transformable 005 * using AffineTransform. 006 **/ 007 public void paint(Graphics g) { 008 //We've cleaned out some cruft (removed the two simple 009 //rectangles) from our Frame. We are going to reuse 010 //our GeneralPath filled shape, however. We translate 011 //and rotate this shape as we did before. 012 Graphics2D g2d = (Graphics2D) g; 013 014 GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD); 015 path.moveTo(0.0f,0.0f); 016 path.lineTo(0.0f,125.0f); 017 path.quadTo(100.0f,100.0f,225.0f,125.0f); 018 path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f); 019 path.closePath(); 020 021 AffineTransform at = new AffineTransform(); 022 at.setToRotation(-Math.PI/8.0); 023 g2d.transform(at); 024 at.setToTranslation(0.0f,150.0f); 025 g2d.transform(at); 026 027 g2d.setColor(Color.green); 028 g2d.fill(path); 029 030 //Now, let's use some of the Java font and text support. 031 //Note that you need to be sure you have the same fonts I 032 //use in the example (Times New Roman True Type) if you 033 //execute this example code. 034 Font exFont = new Font("TimesRoman",Font.PLAIN,40); 035 036 //Un-comment the following diagnostic println's if you 037 //want to see what font was returned. This can be useful 038 //when you have limited font support on your system and 039 //are not sure which font the Java runtime may have 040 //substituted for your requested font. 041 //System.out.println(exFont.getFamily()); 042 //System.out.println(exFont.isPlain()); 043 //System.out.println(exFont.getSize()); 044 045 g2d.setFont(exFont); 046 g2d.setColor(Color.black); 047 g2d.drawString("JavaWorld",0.0f,0.0f); 048 } The following figure shows the output of Example04.Java 2D translates and rotates text strings using AffineTransformsNote: There is a bug on at least some Windows NT 4.0 systems running JDK 1.2 Beta 3 whereby drawing the text string is seen to erase the green shape from the output. After this paint() completes, only the text remains showing. Forcing a repaint of the Frame (by resizing the window, for instance) results in the text and shape being drawn correctly. This bug is still under investigation as of this writing, however you can see the latest information on it by reading the java2d-interest mailing list thread discussing it (see Resources).You probably noticed two things about our output:Our text string is sitting above the rectangle, which you might not expectOur text looks very blocky, or jaggyThe first problem occurs because text strings, unlike other Java 2D objects, have their positive y axis flipped relative to the User Space coordinates. This is done to ensure that when you draw text strings, you get readable (right-side up) strings when and where you expect. This flipping of the y axis for text can cause problems from time to time, and is a definite gotcha lurking within Java 2D.The second problem is slightly more complex in nature but equally well understood: The text has jagged edges because of an aliasing effect. But fear not, all is not lost. We’ll have a solution for this dilemma in next month’s continuation of our Java 2D series.ConclusionsNext month I will resume my discussion of Java 2D. I will start by presenting the aliasing workaround built into Java 2D, then show how you can get the Shape of a text string and use it to clip a second shape. I will also show you how to create and manipulate buffered images and how to use the new compositing capabilities in Java 2D to vary the opacity of an image and blend images together.My thanks to Jonathan Knudsen for reviewing drafts of this Java 2D column and to fellow JavaWorld columnist Mark Johnson for use of his line-numbering script.As always, please let me know about topics you would like to see covered. I received some wonderful feedback on the debut column, and I hope you will continue to send me your ideas and suggestions. If you have ideas for things you would like to see in a future installment of Media Programming, please let me know.Bill Day is a software engineer at Silicon Graphics Computer Systems. In addition to writing for JavaWorld, Bill is authoring a book entitled Java Media Players for O’Reilly & Associates. When Bill is not writing or programming, he loves to travel with his wife, speak French, and enjoy life. Java, c’est magnifique! JavaSoftware DevelopmentAPIsTechnology IndustryMedia and Entertainment IndustrySmall and Medium Business