Graphics Programming with the Java 2D API
- The Basic Java 2D Recipe
- Set the Graphics2D Context...
- ...and Render Something
- Rendering on Components
- Shape Primitives
- Graphics Stroking
- Fill Attributes and Painting
- Transparency and Compositing
- Text
- Clipping
- Coordinate Space Transformations
- Techniques for Graphical User Input
- Double Buffering
- Comprehensive Example: Kspace Visualization
- Summary
The Java 2D API extends the Java Advanced Windowing Toolkit (AWT) to provide classes for professional 2D graphics, text, and imaging. The subject of this chapter is the use of Java 2D for graphics and text. Java 2D imaging is the subject of Chapter 4, "The Immediate Mode Imaging Model."
Keep in mind that, for the most part, all discussion referring to shapes will apply equally to text because for all intents and purposes, text is represented as shapes. Operations such as texture mapping, stroking, and alpha composting can be applied equally to shapes and text.
The key to using Java 2D for graphics is to understand a simple basic programming paradigm that we will refer to as the Basic Java 2D Recipe.
The Basic Java 2D Recipe
As stated previously, there is a basic three-step recipe for writing a graphics program in Java:
Get a graphics context.
Set the context.
Render something.
Getting the graphics context is pretty straightforward. Cast the Graphics object as a Graphics2D object as follows:
public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; }
The result of making this cast is that the programmer has access to the increased functionality of the methods, classes, and interfaces of the Graphics2D object. These extended capabilities enable the advanced graphics operations described in the next several chapters. The Graphics2D object is covered in detail in the section "Set the Graphics2D Context...."
Step 2 of the recipe, setting the graphics context, is also pretty straightforward once you understand what a graphics context is. For now, let's say that the graphics context is a collection of properties (also known as state attributes) that affect the appearance of the graphics output. The most common example of changing the graphics context is to set the color used for drawing. Most of this chapter deals with changing the myriad state attributes to achieve the desired effect.
The final step in this paradigm is to render something. This refers to the action of outputting graphics to a device. The most obvious graphics output device is a monitor; however, printers, files, and other devices are equally valid output targets for graphics.
Let's examine the recipe in the simplest possible example (see Listing 3.1). In this case, our goal is to draw a square on the screen, as shown in Figure 3.1. Keep in mind, however, that this same recipe can be applied in more complex applications.
Listing 3.1 BasicRecipeJ2D.java
// BasicRecipeJ2D.java //Part 1 of the recipe, general program setup. import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; public class BasicRecipeJ2D extends Frame { public BasicRecipeJ2D() { //constructor super("Java 2D basic recipe"); this.add(new myCustomCanvas()); this.setSize(500,500); this.show(); addWindowListener(new WindowEventHandler()); } class WindowEventHandler extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String[] args) { new BasicRecipeJ2D(); } } //Part 2; Java 2D specific-extend the drawing Component -Canvas- // and override it's paint method. class myCustomCanvas extends Canvas { public void paint(Graphics g) { System.out.println("in paint"); // step one of the recipe; cast Graphics object as Graphics2D Graphics2D g2d = (Graphics2D) g; // step two-set the graphics context g2d.setColor(Color.red); //setting context //step three-render something g2d.fill(new Rectangle2D.Float(200.0f,200.0f,75.0f,75.0f)); } }
Figure 3.1 Output from BasicRecipeJ2D.
By modifying this recipe, it is possible to realize most of the projects you would want to do with Java 2D. Many of the examples that follow will simply modify the paint() method to add whatever functionality is needed.
Because the basic recipe is central to our discussion of Java 2D, let's examine the pieces in more detail.
Part 1 of Listing 3.1 is a basic skeleton for any Java program. The appropriate classes are imported; JFrame is extended and an eventListener is added for exiting the frame. Note that we imported java.awt.geom. This will be necessary to have access to shapes for drawing. The other important thing to notice in part 1 is the following line:
this.add(new myCustomCanvas());
In this case, we add myCustomCanvas, a class extending Canvas to the main application frame. Note that Canvas extends Component and is the most common graphics component for display of graphics. It should be emphasized that any of the many objects extending Component (such as JButton and JPanel) can be used in the same fashion (see the section "Drawing on Components").
Part 2 of Listing 3.1 is the part of the program that most relates to Java 2D. The Component class Canvas is extended (subclassed), and its paint() method is overridden. This is the fundamental use of Canvas, and you will see this time and time again. Within the overridden paint() method, the three necessary parts of the Java 2D recipe are realizedwe get a graphics context by casting the Graphics object as Graphics2D. Steps 2 and 3 of the recipe are then achieved by calling two methods of the Graphics2D object. First, there is a change to the rendering attributes of the Graphics2D object by calling setColor(). Second, a Shape object (in this case, a Rectange2D) is created and drawn using the Graphics2D object's draw() method.
You are encouraged to run the BasicRecipeJ2D now.
Differences Between paint(), repaint(), and update()
After taking a look at the basic recipe, you might have noticed that even though our Java 2D code is contained within the paint() method, we never actually call this method. This underscores an important point that often becomes a source of frustration to the uninitiated. The paint() method is called automatically whenever the window needs to be refreshed. The programmer never calls paint() directly, but instead calls repaint() in order to obtain a rendering. It is repaint() that calls paint(). The rendering is then made at the next convenient time.
It becomes even more confusing when you consider that in actuality, paint() doesn't do all the drawing, another method called update() also participates. The drawing in update() includes an additional step in which the screen is first filled with the Component's foreground color, effectively clearing the screen. The update() method then finally calls the Component's paint() method to output the graphics. There are often cases in which the programmer doesn't want to clear the screen before drawing (see the section "Comprehensive Example: Kspace Visualization" at the end of this chapter). In this case, the programmer will need to override the update() method to eliminate the filling of the background.
As an aside, we note that the statement "The programmer never calls paint() directly" is perhaps a little too strong. Many animation applets do indeed call paint() directly in order to avoid the automatic queing process that results from calling repaint(). These cases tend to be rare and are only recommended in special circumstances.
All Rendering Should Occur in paint()
A general rule to follow is that unless there is a compelling reason not to, all drawing for a Component should be done in that Component's paint() method. In our basic recipe example from Listing 3.1, the Component object that we want to draw on is an instance of the class myCustomCanvas (which extends Canvas).
What might constitute a compelling reason not to place the drawing of objects in the paint method? For most complex applications, the paint() method can become unwieldy and should be broken down into smaller methods. Grouping the steps into methods is functionally equivalent to having the code directly in the paint() method, so this really isn't a major departure from the rule of doing all drawing in the paint() method.
Another case in which you would render outside of paint() is when a BufferedImage is used. Still, the final rendering occurs in the paint() method. This is shown later in PDExamples.java and TexturePaint.java.
Other Methods Similar to paint()
Two additional methods are commonly encountered. The paintAll() method is often useful and is used in a similar fashion to the paint() method except that paintAll() will request a paint() of the Component and all of its subcomponents. For Swing components, paint() is often replaced by paintComponent() in order to not invoke the paintChildren() and paintBorder() methods. This is frequently necessary when developing an interface with a custom look and feel.