performing custom painting(drawing)

68
1039 Lesson: Performing Custom Painting This lesson describes custom painting in Swing. Many programs will get by just fine without writing their own painting code; they will simply use the standard GUI components that are already available in the Swing API. But if you need specific control over how your graphics are drawn, then this lesson is for you. We will explore custom painting by creating a simple GUI application that draws a shape in response to the user's mouse activity. By intentionally keeping its design simple, we can focus on the underlying painting concepts, which in turn will relate to other GUI applications that you develop in the future. This lesson explains each concept in steps as you construct the demo application. It presents the code as soon as possible with a minimum amount of background reading. Custom painting in Swing is similar to custom painting in AWT, but since we do not recommend writing your applications entirely with the AWT, its painting mechanism is not specifically discussed here. You may find it useful to read this lesson followed by the in-depth discussion of Painting in AWT and Swing on the Sun Developer Network (SDN). Creating the Demo Application (Step 1) All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance of javax.swing.JFrame. Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT). This will prevent potential race conditions that could lead to deadlock. The following code listing shows how this is done. An Instance of javax.swing.JFrame Click the Launch button to run SwingPaintDemo1 using Java™ Web Start (download JDK 6 ). Alternatively, to compile and run the example yourself, consult the example index . package painting; import javax.swing.SwingUtilities; import javax.swing.JFrame;

Upload: api-3792621

Post on 16-Nov-2014

619 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Performing Custom Painting(Drawing)

1039

Lesson: Performing Custom Painting This lesson describes custom painting in Swing. Many programs will get by just fine without writing their own painting code; they will simply use the standard GUI components that are already available in the Swing API. But if you need specific control over how your graphics are drawn, then this lesson is for you. We will explore custom painting by creating a simple GUI application that draws a shape in response to the user's mouse activity. By intentionally keeping its design simple, we can focus on the underlying painting concepts, which in turn will relate to other GUI applications that you develop in the future.

This lesson explains each concept in steps as you construct the demo application. It presents the code as soon as possible with a minimum amount of background reading. Custom painting in Swing is similar to custom painting in AWT, but since we do not recommend writing your applications entirely with the AWT, its painting mechanism is not specifically discussed here. You may find it useful to read this lesson followed by the in-depth discussion of Painting in AWT and Swing on the Sun Developer Network (SDN).

Creating the Demo Application (Step 1) All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance of javax.swing.JFrame. Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT). This will prevent potential race conditions that could lead to deadlock. The following code listing shows how this is done.

An Instance of javax.swing.JFrame

Click the Launch button to run SwingPaintDemo1 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

package painting; import javax.swing.SwingUtilities; import javax.swing.JFrame;

Page 2: Performing Custom Painting(Drawing)

1040

public class SwingPaintDemo1 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); JFrame f = new JFrame("Swing Paint Demo"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(250,250); f.setVisible(true); } }

This creates the frame, sets its title, and makes everything visible. We have used the SwingUtilities helper class to construct this GUI on the Event Dispatch Thread. Note that by default, a JFrame does not exit the application when the user clicks its "close" button. We provide this behavior by invoking the setDefaultCloseOperation method, passing in the appropriate argument. Also, we are explicity setting the frame's size to 250 x 250 pixels. This step will not be necessary once we start adding components to the frame.

Exercises:

1. Compile and run the application. 2. Test the minimize and maximize buttons. 3. Click the close button (the application should exit.)

Creating the Demo Application (Step 2) Next, we will add a custom drawing surface to the frame. For this we will create a subclass of javax.swing.JPanel (a generic lightweight container) which will supply the code for rendering our custom painting.

Page 3: Performing Custom Painting(Drawing)

1041

A javax.swing.JPanel Subclass

Click the Launch button to run SwingPaintDemo2 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

package painting; import javax.swing.SwingUtilities; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.BorderFactory; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; public class SwingPaintDemo2 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); JFrame f = new JFrame("Swing Paint Demo"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(new MyPanel()); f.pack(); f.setVisible(true); } } class MyPanel extends JPanel { public MyPanel() { setBorder(BorderFactory.createLineBorder(Color.black)); } public Dimension getPreferredSize() { return new Dimension(250,200); } public void paintComponent(Graphics g) { super.paintComponent(g); // Draw Text g.drawString("This is my custom Panel!",10,20); } }

The first change you will notice is that we are now importing a number of additional classes, such as JPanel, Color, and Graphics. Since some of the older AWT classes are still used in modern Swing applications, it is normal to see the java.awt package in a few of the import statements. We have also defined a custom JPanel subclass, called MyPanel, which comprises the majority of the new code.

Page 4: Performing Custom Painting(Drawing)

1042

The MyPanel class definition has a constructor that sets a black border around its edges. This is a subtle detail that might be difficult to see at first (if it is, just comment out the invocation of setBorder and then recompile.) MyPanel also overrides getPreferredSize, which returns the desired width and height of the panel (in this case 250 is the width, 200 is the height.) Because of this, the SwingPaintDemo class no longer needs to specify the size of the frame in pixels. It simply adds the panel to the frame and then invokes pack.

The paintComponent method is where all of your custom painting takes place. This method is defined by javax.swing.JComponent and then overridden by your subclasses to provide their custom behavior. Its sole parameter, a java.awt.Graphics object, exposes a number of methods for drawing 2D shapes and obtaining information about the application's graphics environment. In most cases the object that is actually received by this method will be an instance of java.awt.Graphics2D (a Graphics subclass), which provides support for sophisticated 2D graphics rendering.

Most of the standard Swing components have their look and feel implemented by separate "UI Delegate" objects. The invocation of super.paintComponent(g) passes the graphics context off to the component's UI delegate, which paints the panel's background. For a closer look at this process, see the section entitled "Painting and the UI Delegate" in the aforementioned SDN article.

Exercises:

1. Now that you have drawn some custom text to the screen, try minimizing and restoring the application as you did before.

2. Obscure a part of the text with another window, then move that window out of the way to re-expose the custom text. In both cases, the painting subsystem will determine that the component is damaged and will ensure that your paintComponent method is invoked.

Creating the Demo Application (Step 3) Finally, we will add the event-handling code to programatically repaint the component whenever the user clicks or drags the mouse. To keep our custom painting as efficient as possible, we will track the mouse coordinates and repaint only the areas of the screen that have changed. This is a recommended best practice that will keep your application running as efficiently as possible.

The Completed Application

Page 5: Performing Custom Painting(Drawing)

1043

Figure 3: The Completed Application

Click the Launch button to run SwingPaintDemo3 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

package painting; import javax.swing.SwingUtilities; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.BorderFactory; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionAdapter; public class SwingPaintDemo3 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); JFrame f = new JFrame("Swing Paint Demo"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(new MyPanel()); f.pack(); f.setVisible(true); } } class MyPanel extends JPanel { private int squareX = 50; private int squareY = 50; private int squareW = 20; private int squareH = 20; public MyPanel() { setBorder(BorderFactory.createLineBorder(Color.black)); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { moveSquare(e.getX(),e.getY()); } }); addMouseMotionListener(new MouseAdapter() { public void mouseDragged(MouseEvent e) { moveSquare(e.getX(),e.getY());

Page 6: Performing Custom Painting(Drawing)

1044

} }); } private void moveSquare(int x, int y) { int OFFSET = 1; if ((squareX!=x) || (squareY!=y)) { repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET); squareX=x; squareY=y; repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET); } } public Dimension getPreferredSize() { return new Dimension(250,200); } protected void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("This is my custom Panel!",10,20); g.setColor(Color.RED); g.fillRect(squareX,squareY,squareW,squareH); g.setColor(Color.BLACK); g.drawRect(squareX,squareY,squareW,squareH); } }

This change first imports the various mouse classes from the java.awt.event package, making the application capable of responding to the user's mouse activity. The constructor has been updated to register event listeners for mouse presses and drags. Whenever a MouseEvent received, it is forwarded to the moveSquare method, which updates the square's coordinates and repaints the component in an intelligent manner. Note that by default, any code that is placed within these event handlers will be executed on the Event Dispatch Thread.

But the most important change is the invocation of the repaint method. This method is defined by java.awt.Component and is the mechanism that allows you to programatically repaint the surface of any given component. It has a no-arg version (which repaints the entire component) and a multi-arg version (which repaints only the specified area.) This area is also known as the clip. Invoking the multi-arg version of repaint takes a little extra effort, but guarantees that your painting code will not waste cycles repainting areas of the screen that have not changed.

Because we are manually setting the clip, our moveSquare method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the square previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the square currently is. An important point worth noting is that although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing.

Exercises:

Page 7: Performing Custom Painting(Drawing)

1045

1. Comment out the first invocation of repaint and note what happens when you click or drag the mouse. Because that line is responsible for filling in the background, you should notice that all squares remain on screen after they are painted.

2. With multiple squares on screen, minimize and restore the application frame. What happens? You should notice that the act of maximizing the screen causes the system to entirely repaint the components surface, which will erase all squares except the current one.

3. Comment out both invocations of repaint, and add a line at the end of the paintComponent method to invoke the zero-arg version of repaint instead. The application will appear to be restored to its original behavior, but painting will now be less efficient since the entire surface area of the component is now being painted. You may notice slower performace, especially if the application is maximized.

Refining the Design For demonstration purposes it makes sense to keep the painting logic entirely contained within the MyPanel class. But if your application will need to track multiple instances, one pattern that you could use is to factor that code out into a separate class so that each square can be treated as an individual object. This technique is common in 2D game programming and is sometimes referred to as "sprite animation."

Click the Launch button to run SwingPaintDemo4 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

package painting; import javax.swing.SwingUtilities; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.BorderFactory; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionAdapter; public class SwingPaintDemo4 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); JFrame f = new JFrame("Swing Paint Demo"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(new MyPanel()); f.setSize(250,250);

Page 8: Performing Custom Painting(Drawing)

1046

f.setVisible(true); } } class MyPanel extends JPanel { RedSquare redSquare = new RedSquare(); public MyPanel() { setBorder(BorderFactory.createLineBorder(Color.black)); addMouseListener(new MouseAdapter(){ public void mousePressed(MouseEvent e){ moveSquare(e.getX(),e.getY()); } }); addMouseMotionListener(new MouseAdapter(){ public void mouseDragged(MouseEvent e){ moveSquare(e.getX(),e.getY()); } }); } private void moveSquare(int x, int y){ // Current square state, stored as final variables // to avoid repeat invocations of the same methods. final int CURR_X = redSquare.getX(); final int CURR_Y = redSquare.getY(); final int CURR_W = redSquare.getWidth(); final int CURR_H = redSquare.getHeight(); final int OFFSET = 1; if ((CURR_X!=x) || (CURR_Y!=y)) { // The square is moving, repaint background // over the old square location. repaint(CURR_X,CURR_Y,CURR_W+OFFSET,CURR_H+OFFSET); // Update coordinates. redSquare.setX(x); redSquare.setY(y); // Repaint the square at the new location. repaint(redSquare.getX(), redSquare.getY(), redSquare.getWidth()+OFFSET, redSquare.getHeight()+OFFSET); } } public Dimension getPreferredSize() { return new Dimension(250,200); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("This is my custom Panel!",10,20); redSquare.paintSquare(g); }

Page 9: Performing Custom Painting(Drawing)

1047

} class RedSquare{ private int xPos = 50; private int yPos = 50; private int width = 20; private int height = 20; public void setX(int xPos){ this.xPos = xPos; } public int getX(){ return xPos; } public void setY(int yPos){ this.yPos = yPos; } public int getY(){ return yPos; } public int getWidth(){ return width; } public int getHeight(){ return height; } public void paintSquare(Graphics g){ g.setColor(Color.RED); g.fillRect(xPos,yPos,width,height); g.setColor(Color.BLACK); g.drawRect(xPos,yPos,width,height); } }

In this particular implementation we have created a RedSquare class entirely from scratch. Another approach would be to resuse the functionlity of java.awt.Rectangle by making the RedSquare a subclass of it. Regardless of how RedSquare is implemented, the important point is that we have given the class a method that accepts a Graphics object, and that method is invoked from the panel's paintComponent method. This separation keeps your code clean because it essentially tells each red square to paint itself.

A Closer Look at the Paint Mechanism By now you know that the paintComponent method is where all of your painting code should be placed. It is true that this method will be invoked when it is time to paint, but painting actually begins higher up the class heirarchy, with the paint method (defined by java.awt.Component.) This method will be executed by the painting subsystem whenever you component needs to be rendered. Its signature is:

public void paint(Graphics g)

Page 10: Performing Custom Painting(Drawing)

1048

javax.swing.JComponent extends this class and further factors the paint method into three separate methods, which are invoked in the following order:

protected void paintComponent(Graphics g) protected void paintBorder(Graphics g) protected void paintChildren(Graphics g)

The API does nothing to prevent your code from overriding paintBorder and paintChildren, but generally speaking, there is no reason for you to do so. For all practical purposes paintComponent will be the only method that you will ever need to override.

As previously mentioned, most of the standard Swing components have their look and feel implemented by separate UI Delegates. This means that most (or all) of the painting for the standard Swing components proceeds as follows.

1. paint() invokes paintComponent(). 2. If the ui property is non-null, paintComponent() invokes ui.update(). 3. If the component's opaque property is true, ui.udpate() fills the component's background

with the background color and invokes ui.paint(). 4. ui.paint() renders the content of the component.

This is why our SwingPaintDemo code invokes super.paintComponent(g). We could add an additional comment to make this more clear: public void paintComponent(Graphics g) { // Let UI Delegate paint first, which // includes background filling since // this component is opaque. super.paintComponent(g); g.drawString("This is my custom Panel!",10,20); redSquare.paintSquare(g); } If you have understood all of the demo code provided in this lesson, congratulations! You have enough practical knowledge to write efficient painting code in your own applications. If however you want a closer look "under the hood", please refer to the SDN article linked to from the first page of this lesson. Summary

In Swing, painting begins with the paint method, which then invokes paintComponent, paintBorder, and paintChildren. The system will invoke this automatically when a component is first painted, is resized, or becomes exposed after being hidden by another window.

Programatic repaints are accomplished by invoking a component's repaint method; do not invoke its paintComponent directly. Invoking repaint causes the painting subsystem to take the necessary steps to ensure that your paintComponent method is invoked at an appropriate time.

The multi-arg version of repaint allows you to shrink the component's clip rectangle (the section of the screen that is affected by painting operations) so that painting can

Page 11: Performing Custom Painting(Drawing)

1049

become more efficient. We utilized this technique in the moveSquare method to avoid repainting sections of the screen that have not changed. There is also a no-arg version of this method that will repaint the component's entire surface area.

Because we have shrunk the clip rectangle, our moveSquare method invokes repaint not once, but twice. The first invocation repaints the area of the component where the square previously was (the inherited behavior is to fill the area with the current background color.) The second invocation paints the area of the component where the square currently is.

You can invoke repaint multiple times from within the same event handler, but Swing will take that information and repaint the component in just one operation.

For components with a UI Delegate, you should pass the Graphics paramater with the line super.paintComponent(g) as the first line of code in your paintComponent override. If you do not, then your component will be responsible for manually painting its background. You can experiment with this by commenting out that line and recompiling to see that the background is no longer painted.

By factoring out our new code into a separate RedSquare class, the application maintains an object-oriented design, which keeps the paintComponent method of the MyPanel class free of clutter. Painting still works because we have passed the Graphics object off to the red square by invoking its paintSquare(Graphics g) method. Keep in mind that the name of this method is one that we have created from scratch; we are not overriding paintSquare from anywhere higher up in the Swing API.

Solving Common Painting Problems Problem: I don't know where to put my painting code.

Painting code belongs in the paintComponent method of any component descended from JComponent.

Problem: The stuff I paint doesn't show up.

Check whether your component is showing up at all. Solving Common Component Problems should help you with this.

Check whether repaint is invoked on your component whenever its appearance needs to be updated.

Problem: My component's foreground shows up, but its background is invisible. The result is that one or more components directly behind my component are unexpectedly visible.

Make sure your component is opaque. JPanels, for example, are opaque by default in many but not all look and feels. To make components such as JLabels and GTK+ JPanels opaque, you must invoke setOpaque(true) on them.

If your custom component extends JPanel or a more specialized JComponent descendant, then you can paint the background by invoking super.paintComponent before painting the contents of your component.

You can paint the background yourself using this code at the top of a custom component's paintComponent method:

g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight());

Page 12: Performing Custom Painting(Drawing)

1050

g.setColor(getForeground());

Problem: I used setBackground to set my component's background color, but it seemed to have no effect.

Most likely, your component isn't painting its background, either because it's not opaque or your custom painting code doesn't paint the background. If you set the background color for a JLabel, for example, you must also invoke setOpaque(true) on the label to make the label's background be painted.

Problem: I'm using the exact same code as a tutorial example, but it doesn't work. Why?

Is the code executed in the exact same method as the tutorial example? For example, if the tutorial example has the code in the example's paintComponent method, then this method might be the only place where the code is guaranteed to work.

Problem: How do I paint thick lines? patterns?

The JavaTM 2D API provides extensive support for implementing line widths and styles, as well as patterns for use in filling and stroking shapes. See the 2D Graphics trail for more information on using the Java 2D API.

Problem: The edges of a particular component look odd.

Because components often update their borders to reflect component state, you generally should avoid invoking setBorder except on JPanels and custom subclasses of JComponent.

Is the component painted by a look and feel such as GTK+ or Windows XP that uses UI-painted borders instead of Border objects? If so, don't invoke setBorder on the component.

Does the component have custom painting code? If so, does the painting code take the component's insets into account?

Problem: Visual artifacts appear in my GUI.

If you set the background color of a component, be sure the color has no transparency if the component is supposed to be opaque.

Use the setOpaque method to set component opacity if necessary. For example, the content pane must be opaque, but components with transparent backgrounds must not be opaque.

Make sure your custom component fills its painting area completely if it's opaque.

Problem: The performance of my custom painting code is poor.

If you can paint part of your component, use the getClip or getClipBounds method of Graphics to determine which area you need to paint. The less you paint, the faster it will be.

If only part of your component needs to be updated, make paint requests using a version of repaint that specifies the painting region.

For help on choosing efficient painting techniques, look for the string "performance" in the Java 2D API home page.

Page 13: Performing Custom Painting(Drawing)

1051

Problem: The same transforms applied to seemingly identical Graphics objects sometimes have slightly different effects.

Because the Swing painting code sets the transform (using the Graphics method translate) before invoking paintComponent, any transforms that you apply have a cumulative effect. This doesn't matter when doing a simple translation, but a more complex AffineTransform, for example, might have unexpected results.

If you don't see your problem in this list, see Solving Common Component Problems and Solving Common Layout Problems.

Page 14: Performing Custom Painting(Drawing)

1052

Trail: 2D Graphics

This trail introduces you to the Java 2D™ API and shows you how to display and print 2D graphics in your Java programs. The trial is intended for developers who want to enrich their knowledge of the Java 2D API, as well as for beginners in computer graphics. Almost every section contains relevant examples to illustrate specific capabilities. The Java 2D API enables you to easily perform the following tasks:

Draw lines, rectangles and any other geometric shape. Fill those shapes with solid colors or gradients and textures. Draw text with options for fine control over the font and rendering process. Draw images, optionally applying filtering operations. Apply operations such as compositing and transforming during any of the above

rendering operations.

This chapter also explains less familiar concepts such as compositing.

Using 2D Graphics API to display complex charts

Using image-filtering operations

This chapter describes the concept of drawing on-screen and off-screen images, as well as surfaces and printer devices. This trail covers the most common uses of the Java 2D APIs and briefly describes some of the more advanced features.

Overview of the Java 2D Graphics API introduces the key Java 2D concepts and describes the Java 2D rendering model. This lesson is more conceptual than other lessons of this trail, it enables you to get deep into basic notions and classes descriptions.

Getting started with Graphics uses a developed example to show you how to obtain a Graphics object and use it for common graphics rendering tasks.

Working with Geometry teaches you how to use APIs to draw graphic primitives and arbitrary shapes, and how to apply fancy strokes and fill styles.

Page 15: Performing Custom Painting(Drawing)

1053

Working with Text APIs shows you how to effectively use text APIs, including how to create a Font object with desired attributes, measure text, and determine the names of the fonts available on your system.

Working with Images explains how to create a BufferedImage object, perform image-filtering operations, and draw on an image.

Printing teaches you how to render 2D graphics to a printer, print complex documents, and use Print Services.

Advanced topics in Java 2D explains how to perform transformations, clip the drawing region, composite overlapping graphics, specify rendering preferences, and control rendering quality.

Lesson: Overview of the Java 2D API Concepts The Java 2D™ API provides two-dimensional graphics, text, and imaging capabilities for Java™ programs through extensions to the Abstract Windowing Toolkit (AWT). This comprehensive rendering package supports line art, text, and images in a flexible, full-featured framework for developing richer user interfaces, sophisticated drawing programs, and image editors. Java 2D objects exist on a plane called user coordinate space, or just user space. When objects are rendered on a screen or a printer, user space coordinates are transformed to device space coordinates. The following links are useful to start learning about the Java 2D API:

Graphics class Graphics2D class

The Java 2D API provides following capabilities:

A uniform rendering model for display devices and printers A wide range of geometric primitives, such as curves, rectangles, and ellipses, as well as a

mechanism for rendering virtually any geometric shape Mechanisms for performing hit detection on shapes, text, and images A compositing model that provides control over how overlapping objects are rendered Enhanced color support that facilitates color management Support for printing complex documents Control of the quality of the rendering through the use of rendering hints

These topics are discussed in the following sections:

Java 2D Rendering Geometric Primitives Text Images Printing

Coordinates The Java 2D™ API maintains two coordinate spaces.

Page 16: Performing Custom Painting(Drawing)

1054

User space – The space in which graphics primitives are specified Device space – 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. The top-left corner of a window is 0,0. All coordinates are specified using integers, which is usually sufficient. However, some cases require floating point or even double precision which are also supported.

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

Java 2D Rendering The Java 2D™ API provides a uniform rendering model across different types of devices. At the application level, the rendering process is the same whether the target rendering device is a screen or a printer. When a component needs to be displayed, its paint or update method is automatically invoked with the appropriate Graphics context.

The Java 2D API includes the java.awt.Graphics2D class, which extends the Graphics class to provide access to the enhanced graphics and rendering features of the Java 2D API. These features include:

Rendering the outline of any geometric primitive, using the stroke and paint attributes (draw method).

Rendering any geometric primitive by filling its interior with the color or pattern specified by the paint attributes (fill method).

Rendering any text string (the drawString method). The font attribute is used to convert the string to glyphs, which are then filled with the color or pattern specified by the paint attributes.

Rendering the specified image (the drawImage method).

In addition, the Graphics2D class supports the Graphics rendering methods for particular shapes, such as drawOval and fillRect. All methods that are represented above can be divided into two groups:

1. Methods to draw a shape

Page 17: Performing Custom Painting(Drawing)

1055

2. Methods that affect rendering

The second group of the methods uses the state attributes that form the Graphics2D context for following purposes:

To vary the stroke width To change how strokes are joined together To set a clipping path to limit the area that is rendered To translate, rotate, scale, or shear objects when they are rendered To define colors and patterns to fill shapes with To specify how to compose multiple graphics objects

To employ Java 2D API features in the application, cast the Graphics object passed into a component’s rendering method to a Graphics2D object. For example:

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

As the following figure shows, the Graphics2D class rendering context contains several attributes.

The pen attribute is applied to the outline of a shape. This stroke attribute enables you to draw lines with any point size and dashing pattern and apply end-cap and join decorations to a line.

The fill attribute is applied to a shape's interior. This paint attribute enables you to fill shapes with solid colors, gradients, and patterns.

The compositing attribute is used when rendered objects overlap existing objects.

The transform attribute 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, type restricts rendering to the area within the outline of the Shape object used to define the clipping path. Any Shape object that is used to define the clip.

The font attribute is used to convert text strings to glyphs.

Page 18: Performing Custom Painting(Drawing)

1056

Rendering hints specify preferences in the trade-offs between speed and quality. For example, you can specify whether antialiasing should be used, if this feature available. See also Controlling Rendering Quality.

To learn more about transforming and compositing see the Advanced Topics in Java2D lesson.

When an attribute is set, the appropriate attribute object is passed. As the following example shows, to change the paint attribute to a blue-green gradient fill, you construct a GradientPaint object and then call the setPaint method.

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

Geometric Primitives The Java 2D™ API provides a useful set of standard shapes such as points, lines, rectangles, arcs, ellipses, and curves. The most important package to define common geometric primitives is the java.awt.geom package. Arbitrary shapes can be represented by combinations of straight geometric primitives.

The Shape interface represents a geometric shape, which has an outline and an interior. This interface provides a common set of methods for describing and inspecting two-dimensional geometric objects and supports curved line segments and multiple sub-shapes. The Graphics class supports only straight line segments. The Shape interface can support curves segments.

For more details about how to draw and fill shapes, see the Working with Geometry lesson.

Points

The Point2D class defines a point representing a location in (x, y) coordinate space. The term “point” in the Java 2D API is not the same as a pixel. A point has no area, does not contain a color, and cannot be rendered.

Points are used to create other shapes. ThePoint2D class also includes a method for calculating the distance between two points.

Lines

The Line2D class is an abstract class that represents a line. A line’s coordinates can be retrieved as double. The Line2D class includes several methods for setting a line’s endpoints.

Also, you can create a straight line segment by using the GeneralPath class described below.

Rectangular Shapes

The Rectangle2D, RoundRectangle2D, Arc2D, and Ellipse2D primitives are all derived from the RectangularShape class. This class defines methods for Shape objects that can be described by a

Page 19: Performing Custom Painting(Drawing)

1057

rectangular bounding box. The geometry of a RectangularShape object can be extrapolated from a rectangle that completely encloses the outline of the Shape.

Quadratic and Cubic Curves

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

The CubicCurve2D class enables you to create cubic parametric curve segments. A cubic curve is defined by two endpoints and two control points. The following are examples of quadratic and cubic curves. See Stroking and Filling for implementations of cubic and quadratic curves.

This figure represents a quadratic curve.

This figure represents a cubic curve.

Arbitrary Shapes

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 following shape can be created with three line segments and a cubic curve. See Stroking and Filling for more information about the implementation of this shape.

Page 20: Performing Custom Painting(Drawing)

1058

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 The Java 2D™ API has various text rendering capabilities including methods for rendering strings and entire classes for setting font attributes and performing text layout.

If you just want to draw a static text string, the most direct way to render it directly through the Graphics class by using the drawString method. To specify the font, you use the setFont method of the Graphics class.

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 the java.awt.font package.

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 italic 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. The collection of font families forms the set of fonts that are available on the system.

When you are 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. The getAvailableFontFamilyNames method returns a list of the available font families.

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. The following are two Java 2D mechanisms for managing text layout:

The TextLayout class manages text layout, highlighting, and hit detection. The facilities provided by TextLayout handle the most common cases, including strings with mixed fonts, mixed languages, and bidirectional text. .

Page 21: Performing Custom Painting(Drawing)

1059

You can create the own GlyphVector objects by using the Font class and then rendering each GlyphVector object through the Graphics2D class. Thus, you can completely control how text is shaped and positioned. .

Rendering Hints for Text

The Java 2D API enables you to control the quality of shapes and text rendering by using rendering hints. Rendering hints are encapsulated by the java.awt.RenderingHints class.

As applied to text, this capability is used for antialiasing (which is also known as an smooth edges). For example, the KEY_TEXT_ANTIALIASING hint enables you to control the antialiasing of text separately from the antialiasing of other shapes. To learn more about rendering hints see the Controlling Rendering Quality lesson.

Images In the Java 2D™ API an image is typically a rectangular two-dimensional array of pixels, where each pixel represents the color at that position of the image and where the dimensions represent the horizontal extent (width) and vertical extent (height) of the image as it is displayed.

The most important image class for representing such images is the java.awt.image.BufferedImage class. The Java 2D API stores the contents of such images in memory so that they can be directly accessed.

Applications can directly create a BufferedImage object or obtain an image from an external image format such as PNG or GIF.

In either case, the application can then draw on to image by using Java 2D API graphics calls. So, images are not limited to displaying photographic type images. Different objects such as line art, text, and other graphics and even other images can be drawn onto an image (as shown on the following images).

The Java 2D API enables you to apply image filtering operations to BufferedImage and includes several built-in filters. For example, the ConvolveOp filter can be used to blur or sharpen images.

The resulting image can then be drawn to a screen, sent to a printer, or saved in a graphics format such as PNG, GIF etc. To learn more about images see the Working with Images lesson lesson.

Page 22: Performing Custom Painting(Drawing)

1060

Printing All of the Swing 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 a screen. The printing system controls when pages are rendered, just like the drawing system controls when a component is painted on the screen.

The Java 2D Printing API is based on a callback model in which the printing system, not the application, controls when pages are printed. The 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.

The following two features are important to support printing:

Job control – Initiating and managing the print job including displaying the standard print and setup dialog boxes

Pagination – Rendering each page when the printing system requests it

When pages need to be imaged, the printing system calls the 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 class, just like you do when you are rendering to the screen.

Page 23: Performing Custom Painting(Drawing)

1061

Lesson: Getting Started with Graphics The Java 2D™ API is powerful and complex. However, the vast majority of uses for the Java 2D API utilize a small subset of its capabilities encapsulated in the java.awt.Graphics class. This lesson covers the most common needs of applications developers. Less common needs are described later in the Advanced topics in the Java 2D API lesson.

Most methods of the Graphics class can be divided into two basic groups:

Draw and fill methods, enabling you to render basic shapes, text, and images Attributes setting methods, which affect how that drawing and filling appears

Methods such as setFont and setColor define how draw and fill methods render.

This figure illustrates how these methods relate to graphic objects:

Drawing methods include:

drawString – For drawing text

g.drawString("Hello", 10, 10);

drawImage – For drawing images

g.drawImage(img, 0, 0, width, height, 0, 0, imageWidth, imageHeight, null);

drawLine, drawArc, drawRect, drawOval, drawPolygon – For drawing geometric shapes

g2.draw(new Line2D.Double(0, 0, 30, 40));

Depending on your current need, you can choose one of several methods in the Graphics class based on the following criteria:

Page 24: Performing Custom Painting(Drawing)

1062

Whether you want to render the image at the specified location in its original size or scale it to fit inside the given rectangle

Whether you prefer to fill the transparent areas of the image with color or keep them transparent

Fill methods apply to geometric shapes and include fillArc, fillRect, fillOval, fillPolygon.

Whether you draw a line of text or an image, remember that in 2D graphics every point is determined by its x and y coordinates. All of the draw and fill methods need this information which determines where the text or image should be rendered.

For example, to draw a line, an application calls the following:

java.awt.Graphics.drawLine(int x1, int y1, int x2, int y2)

In this code (x1, y1) is the start point of the line, and (x2, y2) is the end point of the line.

So the code to draw a horizontal line is as follows:

Graphics.drawLine(20, 100, 120, 100);

The demo below accumulates all mentioned techniques. Move the slider to display various weather types.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

The WeatherWizard demo uses the JSlider component as well as various graphics capabilities to generate and display a specified weather type. For more information about the JSlider class see the How to Use Sliders section of the Swing Tutorial.

The paint method of the WeatherPainter class implements graphics features. The following code draws an image determined by using the setupWeatherReport() method.

... origComposite = g2.getComposite(); if (alpha0 != null) g2.setComposite(alpha0); g2.drawImage(img0, 0, 0, size.width, size.height, 0, 0, img0.getWidth(null), img0.getHeight(null), null); if (img1 != null) { if (alpha1 != null) g2.setComposite(alpha1); g2.drawImage(img1, 0, 0, size.width, size.height, 0, 0, img1.getWidth(null), img1.getHeight(null), null); } ...

The setFont and drawString methods render the temperature and the weather condition.

...

Page 25: Performing Custom Painting(Drawing)

1063

// Freezing, Cold, Cool, Warm, Hot, // Blue, Green, Yellow, Orange, Red Font font = new Font("Serif", Font.PLAIN, 36); g.setFont(font); String tempString = feels + " " + temperature+"F"; FontRenderContext frc = ((Graphics2D)g).getFontRenderContext(); ... g.setColor(textColor); int xTextTemp = rX-(int)boundsTemp.getX(); int yTextTemp = rY-(int)boundsTemp.getY(); g.drawString(tempString, xTextTemp, yTextTemp); int xTextCond = rX-(int)boundsCond.getX(); int yTextCond = rY-(int)boundsCond.getY() + (int)boundsTemp.getHeight(); g.drawString(condStr, xTextCond, yTextCond);

The fillRect method allows you to draw a rectangle filled with the specified color.

... Rectangle2D boundsTemp = font.getStringBounds(tempString, frc); Rectangle2D boundsCond = font.getStringBounds(condStr, frc); int wText = Math.max((int)boundsTemp.getWidth(), (int)boundsCond.getWidth()); int hText = (int)boundsTemp.getHeight() + (int)boundsCond.getHeight(); int rX = (size.width-wText)/2; int rY = (size.height-hText)/2; g.setColor(Color.LIGHT_GRAY); g2.fillRect(rX, rY, wText, hText); ... Try to modify the WeatherWizard demo to alter the graphical content. For example, use the fillRoundRect method instead of fillRect or apply another font size in the setFont method. Find the complete code for this applet in the WeatherWizard.java file. The demo also requires the following images: weather-cloud.png, weather-rain.png, weather-snow.png, and weather-sun.png located in the images directory.

Lesson: Working with Geometry In prior lessons, you have learned the graphics concept, including basic information about the coordinate system and graphic object creation. Now, you will progress to more detailed lessons about the 2D graphics classes. This lesson shows you how to use the Graphics2D class to draw graphic primitives as well as arbitrary shapes, and how to display graphics with fancy outline and fill styles. These topics are discussed in the following sections.

Drawing Geometric Primitives

This section explains how to create standard shapes such as points, lines, curves, arcs, rectangles, and ellipses.

Drawing Arbitrary Shapes

This section explains how to draw shapes represented by combinations of straight geometric primitives by using the GeneralPath class.

Page 26: Performing Custom Painting(Drawing)

1064

Filling and Stroking

This section explains how to set the stroke and paint attributes to control the outline and fill styles applied to Shape objects and text.

Drawing Geometric Primitives he Java 2D™ API provides several classes that define common geometric objects such as points, lines, curves, and rectangles. These geometry classes are part of the java.awt.geom package. The PathIterator interface defines methods for retrieving elements from a path. The Shape interface provides a set of methods for describing and inspecting geometric path objects. This interface is implemented by the GeneralPath class and other geometry classes.

All examples represented in this section create geometries by using java.awt.geom and then render them by using the Graphics2D class. To begin you obtain a Graphics2D object, for example by casting the Graphics parameter of the paint() method.

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

Point

The Point class creates a point representing a location in (x,y) coordinate space. The subclasses Point2D.Float and Point2D.Double provide correspondingly float and double precision for storing the coordinates of the point. //Create Point2D.Double Point2D.Double point = new Point2D.Double(x, y); To create a point with the coordinates 0,0 you use the default constructor, Point2D.Double(). You can use the setLocation method to set the position of the point as follows:

setLocation(double x, double y) – To set the location of the point- defining coordinates as double values

setLocation(Point2D p) – To set the location of the point using the coordinates of another point.

Also, the Point2D class has methods to calculate the distance between the current point and a point with given coordinates, or the distance between two points.

Line

The Line2D class represents a line segment in (x, y) coordinate space. The Line2D.Float and Line2D.Double subclasses specify lines in float and double precision. For example:

// draw Line2D.Double g2.draw(new Line2D.Double(x1, y1, x2, y2));

Page 27: Performing Custom Painting(Drawing)

1065

This class includes several setLine() methods to define the endpoints of the line. Aternatively, the endpoints of the line could be specified by using the constructor for the Line2D.Float class as follows:

Line2D.Float(float X1, float Y1, float X2, float Y2) Line2D.Float(Point2D p1, Point2D p2)

Use the Stroke object in the Graphics2D class to define the stroke for the line path.

Curves

The java.awt.geom package enables you to create a quadratic or cubic curve segment.

Quadratic Curve Segment The QuadCurve2D class implements the Shape interface. This class represents a quadratic parametric curve segment in (x, y) coordinate space. The QuadCurve2D.Float and QuadCurve2D.Double subclasses specify a quadratic curve in float and double precision.

Several setCurve methods are used to specify two endpoints and a control point of the curve, whose coordinates can be defined directly, by the coordinates of other points and by using a given array. A very useful method, setCurve(QuadCurve2D c), sets the quadratic curve with the same endpoints and the control point as a supplied curve. For example:

// create new QuadCurve2D.Float QuadCurve2D q = new QuadCurve2D.Float(); // draw QuadCurve2D.Float with set coordinates q.setCurve(x1, y1, ctrlx, ctrly, x2, y2); g2.draw(q);

Cubic Curve Segment The CubicCurve2D class also implements the Shape interface. This class represents a cubic parametric curve segment in (x, y) coordinate space. CubicCurve2D.Float and CubicCurve2D.Double subclasses specify a cubic curve in float and double precision.

The CubicCurve2D class has similar methods for setting the curve as the QuadraticCurve2Dclass, except with a second control point. For example:

// create new CubicCurve2D.Double CubicCurve2D c = new CubicCurve2D.Double(); // draw CubicCurve2D.Double with set coordinates c.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); g2.draw(c);

Rectangle

Classes that specify primitives represented in the following example extend the RectangularShape class, which implements the Shape interface and adds a few methods of its own.

These methods enables you to get information about a shape’s location and size, to examine the center point of a rectangle, and to set the bounds of the shape.

Page 28: Performing Custom Painting(Drawing)

1066

The Rectangle2D class represents a rectangle defined by a location (x, y) and dimension (w x h). The Rectangle2D.Float and Rectangle2D.Double subclasses specify a rectangle in float and double precision. For example:

// draw Rectangle2D.Double g2.draw(new Rectangle2D.Double(x, y, rectwidth, rectheight));

The RoundRectangle2D class represents a rectangle with rounded corners defined by a location (x, y), a dimension (w x h), and the width and height of the corner arc. The RoundRectangle2D.Float and RoundRectangle2D.Double subclasses specify a round rectangle in float and double precision.

The rounded rectangle is specified with following parameters:

Location Width Height Width of the corner arc Height of the corner acr

To set the location, size, and arcs of a RoundRectangle2D object, use the method setRoundRect(double a, double y, double w, double h, double arcWidth, double arcHeight). For example: // draw RoundRectangle2D.Double g2.draw(new RoundRectangle2D.Double(x, y, rectwidth, rectheight, 10, 10));

Ellipse

The Ellipse2d class represents an ellipse defined by a bounding rectangle. The Ellipse2D.Float and Ellipse2D.Double subclasses specify an ellipse in float and double precision.

Ellipse is fully defined by a location, a width and a height. For example:

// draw Ellipse2D.Double g2.draw(new Ellipse2D.Double(x, y, rectwidth, rectheight));

Arc

To draw a piece of an ellipse, you use the Arc2D class. This class represents an arc defined by a bounding rectangle, a start angle, an angular extent, and a closure type. The Arc2D.Float and Arc2D.Double subclasses specify an ellipse in float and double precision.

The Arc2D class defines the following three types of arcs, represented by corresponding constants in this class: OPEN, PIE and CHORD.

Page 29: Performing Custom Painting(Drawing)

1067

Several methods set the size and parameters of the arc:

Directly, by coordinates By supplied Point2D and Dimension2D By copying an existing Arc2D

Also, you can use the setArcByCenter method to specify an arc from a center point, given by its coordinates and a radius. // draw Arc2D.Double g2.draw(new Arc2D.Double(x, y, rectwidth, rectheight, 90, 135, Arc2D.OPEN));

The ShapesDemo2D.java code example contains implementations off all described geometric primitives. For more information about classes and methods represented in this section see the java.awt.geom specification.

Drawing Arbitrary Shapes You have already learned how to draw most of shapes represented in the java.awt.geom package. To create more complicated geometry, such as polygons, polylines, or stars you use another class from this package, GeneralPath.

This class implements the Shape interface and represents a geometric path constructed from lines, and quadratic and cubic curves. The three constructors in this class can create the GeneralPath object with the default winding rule (WIND_NON_ZERO), the given winding rule (WIND_NON_ZERO or WIND_EVEN_ODD), or the specified initial coordinate capacity. The winding rule specifies how the interior of a path is determined.

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

To create an empty GeneralPath instance call new GeneralPath() and then add segments to the shape by using the following methods:

moveTo(float x, float y) – Moves the current point of the path to the given point lineTo(float x, float y) – Adds a line segment to the current path quadTo(float ctrlx, float ctrly, float x2, floaty2) – Adds a quadratic curve

segment to the current path curveTo(float ctrlx1, float ctrly1, float ctrlx2, float ctrly2, float x3,

floaty3) – Adds a cubic curve segment to the current path closePath() – Closes the current path

The following example illustrates how to draw a polyline by using GeneralPath:

Page 30: Performing Custom Painting(Drawing)

1068

// draw GeneralPath (polyline) int x2Points[] = {0, 100, 0, 100}; int y2Points[] = {0, 50, 50, 0}; 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);

This example illustrates how to draw a polygon by using GeneralPath:

// draw GeneralPath (polygon) int x1Points[] = {0, 100, 0, 100}; int y1Points[] = {0, 50, 50, 0}; 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);

Note that the only difference between two last code examples is the closePath() method. This method makes a polygon from a polyline by drawing a straight line back to the coordinates of the last moveTo.

To add a specific path to the end of your GeneralPath object you use one of the append() methods. The ShapesDemo2D.java code example contains additional implementations of arbitrary shapes.

Stroking and Filling Graphics Primitives

You already know how to create different geometric primitives and more complicated shapes. This lesson teaches how to add some color and fancy outlines to your graphics and represents filling and stroking:

Filling – is a process of painting the shape’s interior with solid color or a color gradient, or a texture pattern

Stroking – is a process of drawing a shape’s outline applying stroke width, line style, and color attribute

To apply fancy line styles and fill patterns to geometric primitives change the stroke and paint attributes in the Graphics2D context before rendering. For example, draw a dashed line by creating an appropriate Stroke object. To add this stroke to the Graphics2D context before you render the

Page 31: Performing Custom Painting(Drawing)

1069

line call the setStroke method . Similarly, you apply a gradient fill to a Shape object by creating a GradientPaint object and adding it to the Graphics2D context.

The following code lines enrich geometric primitives with filling and stroking context:

// draw RoundRectangle2D.Double final static float dash1[] = {10.0f}; final static BasicStroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); g2.setStroke(dashed); g2.draw(new RoundRectangle2D.Double(x, y, rectWidth, rectHeight, 10, 10));

// fill Ellipse2D.Double redtowhite = new GradientPaint(0,0,color.RED,100, 0,color.WHITE); g2.setPaint(redtowhite); g2.fill (new Ellipse2D.Double(0, 0, 100, 50));

The ShapesDemo2D.java code example represents additional implementations of stoking and filling.

Defining Fancy Line Styles and Fill Patterns

Using the Java 2D™ Stroke and Paint classes, you can 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 of an inch when the default transform is used.

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

JOIN_BEVEL

JOIN_MITER

Page 32: Performing Custom Painting(Drawing)

1070

JOIN_ROUND

The end-cap style is the decoration that is applied where a line segment ends. BasicStroke supports the following 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.

The following three classes implement the Paint interface: Color, GradientPaint, and TexturePaint.

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 color along the line connecting the two positions. For example:

The pattern for a TexturePaint class is defined by a BufferedImage class. To create a TexturePaint object, you specify the image that contains the pattern and a rectangle that is used to replicate and anchor the pattern. The following image represents this feature:

Page 33: Performing Custom Painting(Drawing)

1071

Lesson: Working with Text APIs

This lesson introduces you to the concept of working with text API’s to apply text rendering capabilities. Already in this trail, you have used basic Java 2D™ text APIs and know how to set a font and position, and how to draw text.

This lesson expands on that material to help you develop an understanding of how to use those APIs and moves further into the capabilities of Java 2D text display.

These topics are discussed in the following sections.

Selecting a font

This section explains how to use the methods of the Font class to determine which fonts are available on your system, to create a Font object, and obtain information about a font family.

Measuring Text

This section explains how to properly measure text by using an instance of the FontMetrics class.

Advanced Text Display

This section explains how to position and render a paragraph of styled text, how to display antialiased text, and how to use text attributes to style text.

Selecting a Font Java 2D™ defines the following five logical font families:

Dialog DialogInput Monospaced Serif SansSerif

These fonts are available on any Java platform and can be thought of as aliases for some underlying font that has the properties implied by its name. A Serif font is a font similar to Times New Roman, which is commonly used in print. A Sans Serif font is more typical for onscreen use.

These fonts can be customized for the locale of the user. In addition these fonts support the widest range of code points (unicode characters).

Apart from the family, fonts have other attributes, the most important of which are style and size. Styles are Bold and Italic.

Page 34: Performing Custom Painting(Drawing)

1072

The default Java 2D font is 12 pt Dialog. This font is a typical point size for reading text on a normal 72–120 DPI display device. An application can create an instance of this font directly by specifying the following:

Font font = new Font("Dialog", Font.PLAIN, 12);

In addition to the logical fonts, Java software provides access to other fonts that are installed on your system. The names of all available font families can be found by calling the following:

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); String []fontFamilies = ge.getAvailableFontFamilyNames();

The FontSelector sample program (available in FontSelector.java) illustrates how to locate and select these fonts. You can use this example to view how Sans Serif appears on your system. These other fonts are called physical fonts.

Note: Applications should not assume that any particular physical font is present. However, the logical fonts are a safe choice because they are always present.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

Sometimes, an application cannot depend on a font being installed on the system, usually because the font is a custom font that is not otherwise available. In this case, the application must include the font. This lesson shows how to obtain a TrueType font, the most commonly used font type on modern operating systems, to a java.awt.Font object.

You can use either of these methods:

Font java.awt.Font.createFont(int fontFormat, InputStream in); Font java.awt.Font.createFont(int fontFormat, File fontFile); To identify a TrueType font, fontFormat must be the constant Font.TRUETYPE_FONT. Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));

Accessing the font directly from a file must be more convenient for some cases. However, an InputStream might be needed if your code is unable to access file system resources, or if the font is packaged in a Java Archive (JAR) file along with the rest of the application or applet.

The returned Font instance can then be used with the Font.deriveFont(..) methods to derive a version that is the required size. For example:

try { /* Returned font is of pt size 1 */ Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")); /* derive and return a 12 pt version : need to use float otherwise * it would be interpreted as style */ return font.deriveFont(12f); } catch (IOException ioe);

Page 35: Performing Custom Painting(Drawing)

1073

} catch (FontFormatException ffe); }

It is important to use deriveFont() because fonts which are created by application are not part of the set of fonts known to the underlying font system. Because deriveFont works from the original created font it does not have this limitation.

The solution for this problem is to register the created font with the graphics environment. For example:

try { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")); } catch (IOException ioe); } catch (FontFormatException ffe); } After this step is done, the font is available in calls to getAvailableFontFamilyNames() and can be used in font constructors.

Measuring Text

To properly measure text, you need to learn a few methods and some mistakes to avoid. Font metrics are measurements of text rendered by a Font object such as the height of a line of text in the font. The most common way to measure text is to use a FontMetrics instance which encapsulates this metrics information. For example:

// get metrics from the graphics FontMetrics metrics = graphics.getFontMetrics(font); // get the height of a line of text in this font and render context int hgt = metrics.getHeight(); // get the advance of my text in this font and render context int adv = metrics.stringWidth(text); // calculate the size of a box to hold the text with some padding. Dimension size = new Dimension(adv+2, hgt+2); This way is sufficient for many applications to evenly space lines of text or to size Swing components.

Note the following:

The metrics are obtained from the Graphics class, because this class encapsulates the FontRenderContext, which is needed to accurately measure text. At screen resolutions, fonts are adjusted for ease of reading. As text size increases, this adjustment does not scale linearly. So, at 20 pt, a font will not display text exactly twice the length as it would at 10 pt. Besides the text itself and the font, the other important piece of information needed to measure text is the FontRenderContext. This method includes the transform from user space to device pixels that is used in measuring text.

The height is reported without reference to any particular string of text. It is useful, for example in, a text editor where you want the same line spacing between each line of text.

Page 36: Performing Custom Painting(Drawing)

1074

stringWidth() returns the advance width of the text. Advance width is the distance from the origin of the text to the position of a subsequently rendered string.

When using these methods to measure text, note that the text can extend in any direction outside of a rectangle, defined by the font height and the advance of the string.

Typically, the simplest solution is to ensure that the text is not clipped, for example, by components that surround the text. Add padding in cases where the text might otherwise be clipped.

If this solution is insufficient, other text measurement APIs in the Java 2D™ software can return rectangular bounding boxes. These boxes account for the height of the specific text to be measured and for pixelization effects.

Advanced Text Display The Java 2D™ API provides mechanisms to support sophisticated text layout. This section describes following features of advanced text display.

Displaying Antialiased Text by Using Rendering Hints

This section explains how to control the rendering quality through the use of rendering hints.

Using Text Attributes to Style Text

This section explains how to use the TextAttribute class to underline or strikethough text.

Drawing Multiple Lines of Text

This section explains how to position and render a paragraph of styled text by using the TextLayout and LineBreakMeasurer classes.

Displaying Antialiased Text by Using Rendering Hints

Java 2D™ text rendering can be affected by rendering hints.

Recall that the most important text drawing method is the following:

Page 37: Performing Custom Painting(Drawing)

1075

Graphics.drawString(String s, int x, int y); Usually, this method draws each glyph in a string of text with a solid color and each pixel that is “on” in that glyph is set to that colour. This type of drawing produces the highest contrast text, but sometimes with jagged (aliased) edges.

Text antialiasing is a technique used to smooth the edges of text on a screen. The Java 2D API enables applications to specify whether this technique should be used and what algorithm to use by applying a text rendering hint to the Graphics.

The most common rendering hint blends the foreground (text) color with the onscreen background pixels at the edges of the text. To request this hint an application must invoke the following:

graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

The following figure illustrates the antialiasing capability.

If used inappropriately this method can make the text appear overly fuzzy. In such cases, a better hint to use is the following:

graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);

This method automatically uses information in the font itself to decide whether to use antialiasing or to use solid colors.

LCD displays have a property that the Java 2D API can use to produce text that isn't as fuzzy as typical antialiasing but is more legible at small sizes. To request that text be drawn using the sub-pixel LCD text mode for a typical LCD display, an application must invoke the following:

graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); The code example represented below illustrates the antialiasing capability in the following order:

1. Antialiasing is off. 2. Antialiasing is on. 3. Antialiasing using the TEXT_ANTIALIAS_GASP hint.

Note: Consequently the GASP table specifies to use only hinting at those sizes and not "smoothing". So in many cases the resulting text display is equivalent to VALUE_TEXT_ANTIALIAS_OFF.

4. Antialiasing using the TEXT_ANTIALIAS_LCD_HRGB hint.

Page 38: Performing Custom Painting(Drawing)

1076

Note: If you don't see the applet running above, you need to install release 6 of the JDK. The complete code for this applet is in AntialiasedText.java.

Using Text Attributes to Style Text

Applications typically need the capability to apply the following text attributes:

Underline – A line that is drawn underneath text Strikethrough – A horizontal line that is drawn through the text Superscript or Subscript – A text or a letter that appears slightly above a line or

correspondingly below a line Kerning – The adjustment of the space between characters

These and other text attributes can be applied by using the Java 2D™ TextAttribute class.

To apply these text attributes by add them to a Font object. For example:

Map<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>(); map.put(TextAttribute.KERNING, TextAttribute.KERNING_ON); font = font.deriveFont( map ); graphics.setFont( font );

The code example represented below shows the application of text attributes in the following order:

1. Sample string (no text attributes applied) 2. Kerning 3. Kerning and Underlining 4. Kerning,Underlining and Strikethrough 5. Kerning,Underlining, Strikethrough and Color

Note: If you don't see the applet running above, you need to install release 6 of the JDK. The complete code for this applet is in AttributedText.java.

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 the LineBreakMeasurer class. This class enables styled text to be broken into lines so that they fit within a particular visual advance. Each line is returned as a TextLayout object, which represents unchangeable, styled character data. However, this class also enables 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 object so that the font and point size attributes can be stored with the text.

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

Page 39: Performing Custom Painting(Drawing)

1077

Note: If you don't see the applet running above, you need to install release 6 of the JDK. The complete code for this applet 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(); FontRenderContext frc = g2d.getFontRenderContext(); lineMeasurer = new LineBreakMeasurer(paragraph, frc);

The size of the window is used to determine where the line should break. Also a TextLayout object is created for each line in the paragraph.

// Set break width to width of Component. float breakWidth = (float)getSize().width; float drawPosY = 0; // Set position to the index of the first character in the paragraph. lineMeasurer.setPosition(paragraphStart); // Get lines from until the entire paragraph has been displayed. while (lineMeasurer.getPosition() < paragraphEnd) { TextLayout layout = lineMeasurer.nextLayout(breakWidth); // Compute pen x position. If the paragraph is right-to-left we // will align the TextLayouts to the right edge of the panel. float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance(); // Move y-coordinate by the ascent of the layout. drawPosY += layout.getAscent(); // Draw the TextLayout at (drawPosX, drawPosY). layout.draw(g2d, drawPosX, drawPosY); // Move y-coordinate in preparation for next layout. drawPosY += layout.getDescent() + layout.getLeading(); }

The TextLayout class is not frequently created directly by applications. However, this class is useful when applications need to work directly with text that has had styles (text attributes) applied at specific positions in text. For example, to draw a single word italicized in a paragraph, an application would need to perform measurements and set the font for each substring. If the text is bidirectional, this task is not so easy to do correctly. Creating a TextLayout object from an AttributedString object handles this problem for you. Consult the Java SE specification for more information about TextLayout.

Page 40: Performing Custom Painting(Drawing)

1078

Lesson: Working with Images As you have already learned from the Overview lesson, Images are described by a width and a height, measured in pixels, and have a coordinate system that is independent of the drawing surface.

There are a number of common tasks when working with images.

Loading an external GIF, PNG JPEG image format file into Java 2D™'s internal image representation.

Directly creating a Java 2D image and rendering to it. Drawing the contents of a Java 2D image on to a drawing surface. Saving the contents of a Java 2D image to an external GIF, PNG, or JPEG image file.

This lesson teaches you the basics of loading, displaying, and saving images.

The are two main classes that you must learn about to work with images:

The java.awt.Image class is the superclass that represents graphical images as rectangular arrays of pixels.

The java.awt.image.BufferedImage class, which extends the Image class to allow the application to operate directly with image data (for example, retrieving or setting up the pixel color). Applications can directly construct instances of this class.

The BufferedImage class is a cornerstone of the Java 2D immediate-mode imaging API. It manages the image in memory and provides methods for storing, interpreting, and obtaining pixel data. Since BufferedImage is a subclass of Image it can be rendered by the Graphics and Graphics2D methods that accept an Image parameter.

A BufferedImage is essentially an Image with an accessible data buffer. It is therefore more efficient to work directly with BufferedImage. 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 performs the following functions:

Represents the rectangular coordinates of the image Maintains image data in memory Provides a mechanism for creating multiple subimages from a single image data

buffer Provides methods for accessing specific pixels within the image

The basic operations with images are represented in the following sections:

Reading/Loading an image

This section explains how to load an image from an external image format into a Java application using the Image I/O API

Drawing an image

This section teaches how to display images using the drawImage method of the Graphics and Graphics2D classes.

Page 41: Performing Custom Painting(Drawing)

1079

Creating and drawing To an image

This section describes how to create an image and how to use the image itself as a drawing surface.

Writing/saving an image

This section explains how to save created images in an appropriate format.

Reading/Loading an Image When you think of digital images, you probably think of sampled image formats such as the JPEG image format used in digital photography, or GIF images commonly used on web pages. All programs that can use these images must first convert them from that external format into an internal format.

Java 2D™ supports loading these external image formats into its BufferedImage format using its Image I/O API which is in the javax.imageio package. Image I/O has built-in support for GIF, PNG, JPEG, BMP, and WBMP. Image I/O is also extensible so that developers or administrators can "plug-in" support for additional formats. For example, plug-ins for TIFF and JPEG 2000 are separately available.

To load an image from a specific file use the following code:

BufferedImage img = null; try { img = ImageIO.read(new File("strawberry.jpg")); } catch (IOException e) { }

Image I/O recognises the contents of the file as a JPEG format image, and decodes it into a BufferedImage which can be directly used by Java 2D.

LoadImageApp.java shows how to display this image.

If the code is running in an applet, then its just as easy to obtain the image from the applet codebase :

try { URL url = new URL(getCodeBase(), "strawberry.jpg"); img = ImageIO.read(url); } catch (IOException e) { }

The getCodeBase method used in this example returns the URL of the directory containing this applet.

The following example shows how to use the getCodeBase method to load the strawberry.jpg file.

Page 42: Performing Custom Painting(Drawing)

1080

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

LoadImageApp.java contains the complete code for this example and this applet requires the strawberry.jpg image file. In addition to reading from files or URLS, Image I/O can read from other sources, such as an InputStream.

ImageIO.read() is the most straightforward convenience API for most applications, but the javax.imageio.ImageIO provides many more static methods for more advanced usages of the Image I/O API. The collection of methods on this class represent just a subset of the rich set of APIs for discovering information about the images and for controlling the image decoding (reading) process.

We will explore some of the other capabilities of Image I/O later in the Writing/saving an image section. More information can be found in the Image I/O guide.

Drawing an Image As you have already learned, the Graphics.drawImage method draws an image at a specific location:

boolean Graphics.drawImage(Image img, int x, int y, ImageObserver observer);

The x,y location specifies the position for the top-left of the image. The observer parameter notifies the application of updates to an image that is loaded asynchronously. The observer parameter is not frequently used directly and is not needed for the BufferedImage class, so it usually is null.

The described method addresses only the case where the entire image is to be drawn, mapping image pixels to user space coordinates 1:1. Sometimes applications require to draw a part of the image (a sub-image), or scale the image to cover a particular area of the drawing surface, or transform or filter the image before drawing.

The overloads of the drawImage() method perform these operations. For example, the following overload of the drawImage() method enables you to draw as much of a specified area of the specified image as is currently available, scaling it to fit inside the specified area of the destination drawable surface:

boolean Graphics.drawImage(Image img, int dstx1, int dsty1, int dstx2, int dsty2, int srcx1, int srcy1, int srcx2, int srcy2, ImageObserver observer);

The src parameters represent the area of the image to copy and draw. The dst parameters display the area of the destination to cover by the the source area. The dstx1, dsty1 coordinates define the location to draw the image. The width and height dimensions on the destination area are calculated by the following expressions: (dstx2-dstx1), (dsty2-dsty1). If the dimensions of the source and destinations areas are different, the Java 2D™ API will scale up or scale down, as needed.

The following code example divides an image into four quadrants and randomly draws each quadrant of the source image into a different quadrant of the destination.

Page 43: Performing Custom Painting(Drawing)

1081

Note: If you don't see the applet running above, you need to install release 6 of the JDK. The complete code for this applet is in JumbledImageApplet.java. This example uses the following code to paint the jumbled duke_skateboard.jpg image. It iterates over the four sub-images of the source, drawing each in turn into a randomly selected destination quadrant. /* divide the image 'bi' into four rectangular areas and draw each * of these areas in to a different part of the image, so as to * jumble up the image. * 'cells' is an array which has been populated with values * which redirect drawing of one subarea to another subarea. */ int cellWidth = bi.getWidth(null)/2; int cellHeight = bi.getHeight(null)/2; for (int x=0; x<2; x++) { int sx = x*cellWidth; for (int y=0; y<2; y++) { int sy = y*cellHeight; int cell = cells[x*2+y]; int dx = (cell / 2) * cellWidth; int dy = (cell % 2) * cellHeight; g.drawImage(bi, dx, dy, dx+cellWidth, dy+cellHeight, sx, sy, sx+cellWidth, sy+cellHeight, null); } }

Filtering Images

In addition to copying and scaling images, the Java 2D API also filter an image. Filtering is drawing or producing a new image by applying an algorithm to the pixels of the source image. Image filters can be applied by using the following method:

void Graphics2D.drawImage(BufferedImage img, BufferedImageOp op, int x, int y)

The BufferedImageOp parameter implements the filter. The following applet represents an image drawn on top of text. Drag the slider to show more or less of the text through the image and make the image more or less transparent.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

The following code shows how the filter action is done by operating on a BufferedImage object with an alpha channel and rescales that alpha channel by using the RescaleOp object. The alpha channel determines the translucency of each pixel. It also specifies the degree to which this image overwrites.

/* Create an ARGB BufferedImage */ BufferedImage img = ImageIO.read(imageSrc); int w = img.getWidth(null);

Page 44: Performing Custom Painting(Drawing)

1082

int h = img.getHeight(null); BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics g = bi.getGraphics(); g.drawImage(img, 0, 0, null); /* Create a rescale filter op that makes the image 50% opaque */ float[] scales = { 1f, 1f, 1f, 0.5f }; float[] offsets = new float[4]; RescaleOp rop = new RescaleOp(scales, offsets, null); /* Draw the image, applying the filter */ g2d.drawImage(bi, rop, 0, 0);

The complete example represented in SeeThroughImageApplet.java includes the code that uses the slider to adjust the transparency from the initial 50%. This example also requires the duke_skateboard.jpg image.

The RescaleOp object is just one of many filters that can be created. The Java 2D API has several built in filters including the following:

ConvolveOp. Each output pixel is computed from surrounding pixels in the source image. It may be used to blur or sharpen images.

AffineTransformOp. This filter maps pixels in the source to a different position in the destination by applying a transformation on the pixel location.

LookupOp. This filter uses an application supplied lookup table to remap pixel colors.

RescaleOp. This filter multiplies the colors by some factor. Can be used to lighten or darken the image, to increase or reduce its opacity, etc.

The following example uses each of the described filters as well as scaling:

Note: If you don't see the applet running above, you need to install release 6 of the JDK. The complete code for this applet is in ImageDrawingApplet.java and this applet requires the bld.jpg image.

Use the drop-down menu to select an image scaling or filtering operation.

Creating and Drawing to an Image We already know how to load an existing image, which was created and stored in your system or in any network location. But, you probably would like also to create an new image as a pixel data buffer.

In this case, you can create a BufferedImage object manually, using three constructors of this class:

Page 45: Performing Custom Painting(Drawing)

1083

new BufferedImage(width, height, type) - constructs a BufferedImage of one of the predefined image types.

new BufferedImage(width, height, type, colorModel) - constructs a BufferedImage of one of the predefined image types: TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED.

new BufferedImage(colorModel, raster, premultiplied, properties) - constructs a new BufferedImage with a specified ColorModel and Raster.

On the other hand, we can use methods of the Component class. These methods can analyze the display resolution for the given Component or GraphicsConfiguration and create an image of an appropriate type.

Component.createImage(width, height) GraphicsConfiguration.createCompatibleImage(width, height) GraphicsConfiguration.createCompatibleImage(width, height,

transparency)

GraphicsConfiguration returns an object of BufferedImage type, but the Component returns an object of Image type, if you need a BufferedImage object instead then you can perform an instanceof and cast to a BufferedImage in your code.

As was already mentioned in the previous lessons, we can render images not only on screen. An images itself can be considered as a drawing surface. You can use a createGraphics() method of the BufferedImage class for this purpose:

... BufferedImage off_Image = new BufferedImage(100, 50, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = off_Image.createGraphics();

Another interesting use of offscreen images is an automatic double buffering. This feature allows to avoid flicker in animated images by drawing an image to a back buffer and then copying that buffer onto the screen instead of drawing directly to the screen.

Java 2D™ also allows access to hardware acceleration for offscreen images, which can provide the better performance of rendering to and copying from these images. You can get the benefit of this functionality by using the following methods of the Image class:

The getCapabilities method allows you to determine whether the image is currently accelerated.

The setAccelerationPriority method lets you set a hint about how important acceleration is for the image.

The getAccelerationPriority method gets a hint about the acceleration importance.

Writing/Saving an Image

Page 46: Performing Custom Painting(Drawing)

1084

This lesson started with an explanation for using the javax.imageio package, to load images from an external image format into Java 2D™'s internal BufferedImage format. Then it explains how to use the Graphics.drawImage() to draw that image, with optional filtering.

The final stage is saving a BufferedImage object into an external image format. This may be an image that was originally loaded by the Image I/O class from an external image format and perhaps modified using the Java 2D APIs, or it may be one that was created by Java 2D.

The Image I/O class provides a simple way to save images in a variety of image formats in the following example:

static boolean ImageIO.write(RenderedImage im, String formatName, File output) throws IOException

Note: the BufferedImage class implements the RenderedImage interface.

.

The formatName parameter selects the image format in which to save the BufferedImage.

try { BufferedImage bi = getMyImage(); // retrieve image File outputfile = new File("saved.png"); ImageIO.write(bi, "png", outputfile); } catch (IOException e) }

The ImageIO.write method calls the code that implements PNG writing a “PNG writer plug-in”. The term plug-in is used since Image I/O is extensible and can support a wide range of formats.

But the following standard image format plugins : JPEG, PNG, GIF, BMP and WBMP are always be present.

Each image format has its advantages and disadvantages:

Plus Minus

GIF Supports animation, and transparent pixels Supports only 256 colors and no translucency

PNG Better alternative than GIF or JPG for high colour lossless images, supports translucency

Doesn't support animation

JPG Great for photographic images Loss of compression, not good for text, screenshots, or any application where the original image must be preserved exactly

For most applications it is sufficient to use one of these standard plugins. They have the advantage of being readily available. The Image I/O class provides a way to plug in support for additional formats which can be used, and many such plug-ins exist. If you are interested in what file formats are available to load or save in your system, you may use the getReaderFormatNames and

Page 47: Performing Custom Painting(Drawing)

1085

getWriterFormatNames methods of the ImageIO class. These methods return an array of strings listing all of the formats supported in this JRE.

String writerNames[] = ImageIO.getWriterFormatNames();

The returned array of names will include any additional plug-ins that are installed and any of these names may be used as a format name to select an image writer. The following code example is a simple version of a complete image edit/touch up program which uses a revised version of the ImageDrawingApplet.java sample program which can be used as follows :

An image is first loaded via Image I/O The user selects a filter from the drop down list and a new updated image is drawn The user selects a save format from the drop down list Next a file chooser appears and the user selects where to save the image The modified image can now be viewed by other desktop applications

The complete code of this example is represented in SaveImage.java.

In this lesson you have learned just the basics of Image I/O, which provides extensive support for writing images, including working directly with an ImageWriter plug-in to achieve finer control over the encoding process. ImageIO can write multiple images, image metadata, and determine quality vs. size tradeoffs. For more information see Java Image I/O API Guide.

Lesson: Printing Since the Java 2D™ API enables you to draw on any surface, a natural extension of that is the ability to print Java 2D graphics. A printer can be considered a graphics device just like a display.

The Java 2D printing API is not limited to printing graphics. It enables you to print the content of an application's user interface as well. Content can be printed by sending raw data to the printer under the formatting control of the Java 2D printing API, or by using the Java 2D Graphics API.

In this lesson you will explore the printer and job control functions of the Java 2D printing API which are complements to the rendering elements. You will learn how to look up printers configured on the system or network and discover information about these printers such as the paper sizes it supports, and selecting these attributes for printing, and user dialogs.

The main classes and interfaces involved in printing are represented in the java.awt.print and javax.print packages (the last package allows you to get access to the printing services).

The basic printing operations are represented in the following sections:

A Basic Printing Program

This section describes the Printable interface and presents a basic printing program.

Using Print Setup Dialogs

This sections explains how to display the Print Setup Dialog.

Page 48: Performing Custom Painting(Drawing)

1086

Printing a Multiple Page Document

This section explains how to use pagination for printing a multiple page document.

Working with Print Services and Attributes

This section teaches you about print services, how to specify the print data format, and how to create print job using the javax.print package.

Printing the Contents of a User Interface

This section explains how to print the contents of a window or a frame.

Printing Support in Swing Components

This section provides a brief description of the related printing functionality in Swing and refers to specific Swing classes and interfaces.

A Basic Printing Program This section explains how to create a basic printing program that displays a print dialog and prints the text "Hello World" to the selected printer.

Printing task usually consists of two parts:

Job control — Creating a print job, associating it with a printer, specifying the number of copies, and user print dialog interaction.

Page Imaging — Drawing content to a page, and managing content that spans pages (pagination).

First create the printer job. The class representing a printer job and most other related classes is located in the java.awt.print package.

import java.awt.print.*; PrinterJob job = PrinterJob.getPrinterJob();

Next provide code that renders the content to the page by implementing the Printable interface.

class HelloWorldPrinter implements Printable { ... } .. job.setPrintable(new HelloWorldPrinter());

An application typically displays a print dialog so that the user can adjust various options such as number of copies, page orientation, or the destination printer.

boolean doPrint = job.printDialog();

This dialog appears until the user either approves or cancels printing. The doPrint variable will be true if the user gave a command to go ahead and print. If the doPrint variable is false, the user

Page 49: Performing Custom Painting(Drawing)

1087

cancelled the print job. Since displaying the dialog at all is optional, the returned value is purely informational.

If the doPrint variable is true, then the application will request that the job be printed by calling the PrinterJob.print method.

if (doPrint) { try { job.print(); } catch (PrinterException e) { /* The job did not successfully complete */ } }

The PrinterException will be thrown if there is problem sending the job to the printer. However, since the PrinterJob.print method returns as soon as the job is sent to the printer, the user application cannot detect paper jams or paper out problems. This job control boilerplate is sufficient for basic printing uses.

The Printable interface has only one method:

public int print(Graphics graphics, PageFormat pf, int page) throws PrinterException;

The PageFormat class describes the page orientation (portrait or landscape) and its size and imageable area in units of 1/72nd of an inch. Imageable area accounts for the margin limits of most printers (hardware margin). The imageable area is the space inside these margins, and in practice if is often further limited to leave space for headers or footers.

A page parameter is the zero-based page number that will be rendered.

The following code represents the full Printable implementation:

import java.awt.print.*; import java.awt.*; public class HelloWorldPrinter implements Printable { public int print(Graphics g, PageFormat pf, int page) throws PrinterException { if (page > 0) { /* We have only one page, and 'page' is zero-based */ return NO_SUCH_PAGE; } /* User (0,0) is typically outside the imageable area, so we must * translate by the X and Y values in the PageFormat to avoid clipping */ Graphics2D g2d = (Graphics2D)g; g2d.translate(pf.getImageableX(), pf.getImageableY()); /* Now we perform our rendering */ g.drawString("Hello world!", 100, 100); /* tell the caller that this page is part of the printed document */ return PAGE_EXISTS; } }

Page 50: Performing Custom Painting(Drawing)

1088

The complete code for this example is in HelloWorldPrinter.java.

Sending a Graphics instance to the printer is essentially the same as rendering it to the screen. In both cases you need to perform the following steps:

To draw a test string is as easy as the other operations that were described for drawing to a Graphics2D.

Printer graphics have a higher resolution, which should be transparent to most code. The Printable.print() method is called by the printing system, just as the

Component.paint() method is called to paint a Component on the display. The printing system will call the Printable.print() method for page 0, 1,.. etc until the print() method returns NO_SUCH_PAGE.

The print() method may be called with the same page index multiple times until the document is completed. This feature is applied when the user specifies attributes such as multiple copies with collate option.

The PageFormat's imageable area determines the clip area. Imageable area is also important in calculating pagination, or how to span content across printed pages, since page breaks are determined by how much can fit on each page.

Note: A call to the print() method may be skipped for certain page indices if the user has specified a different page range that does not involve a particular page index.

Using Print Setup Dialogs Traditionally, the user wants to see the page setup and print dialog boxes. From the print dialog you can select a printer, specify pages to print, and set the number of copies.

Page 51: Performing Custom Painting(Drawing)

1089

An application displays a print dialog when the user presses a button related to the print command, or chooses an item from the print menu. To display this dialog, call the printDialog method of the PrinterJob class:

PrinterJob pj = PrinterJob.getPrinterJob(); ... if (pj.printDialog()) { try {pj.print();} catch (PrinterException exc) { System.out.println(exc); } } ...

This method returns true if the user clicked OK to leave the dialog, and false otherwise. The user's choices in the dialog are constrained based on the number and format of the pages that have been set to the PrinterJob.

The printDialog method in the above code snippet opens a native print dialog. The PrintDialogExample.java code example shows how to display a cross-platform print dialog.

You can change the page setup information contained in the PageFormat object by using the page setup dialog.

To display the page setup dialog, call the pageDialog method of the PrinterJob class. PrinterJob pj = PrinterJob.getPrinterJob(); PageFormat pf = pj.pageDialog(pj.defaultPage());

The page setup dialog is initialized using the parameter passed to pageDialog. If the user clicks the OK button in the dialog, the PageFormat instance will be created in accordance with the user’s selections, and then returned. If the user cancels the dialog, pageDialog returns the original unchanged PageFormat.

Usually the Java 2D™ Printing API requires an application to display a print dialog, but in sometimes it's possible to print without showing any dialog at all. This type of printing is called

Page 52: Performing Custom Painting(Drawing)

1090

silent printing. It may be useful in specific cases, such as, when you need to print a particular database weekly report. In the other cases it is always recommended to inform the user when a print process is starting.

Printing a Multiple Page Document You have already learned how to use the Printable interface to print a single page document. However, documents are usually more than one physical page in length. Pagination is the process of identifying the location in a document where page breaks and printing accordingly.

In case of printing several graphics images, one per page, use the page index to iterate through these pages and print one on each page. For example, if several images are represented in the following array:

BufferedImage[] images = new BufferedImage[10]; then use the print() method as shown in the following code fragment: public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { if (pageIndex < images.length) { graphics.drawImage(images[pageIndex], 100, 100, null); return PAGE_EXISTS; } else { return NO_SUCH_PAGE: } } If the document is continuous, the application must calculate how much content can fit on each page, and break the page at that point. If text document consists of many lines, then an application must calculate how many of these lines can fit entirely on a page. The Point class creates a point representing a location in (x,y)

To calculate the height of a single line of text, use the FontMetrics class.

Font font = new Font("Serif", Font.PLAIN, 10); FontMetrics metrics = graphics.getFontMetrics(font); int lineHeight = metrics.getHeight();

The PageFormat parameter describes the printable area of the page. In particular, to find the vertical span of the page use the following code fragment:

double pageHeight = pageFormat.getImageableHeight(); Use the following code fragment to calculate the number of lines that fit on a page and the number of page breaks: int linesPerPage = ((int)pageHeight)/lineHeight); int numBreaks = (textLines.length-1)/linesPerPage; int[] pageBreaks = new int[numBreaks]; for (int b=0; b < numBreaks; b++) { pageBreaks[b] = (b+1)*linesPerPage; }

Use the print() method to calculate the printable area for the following reasons:

Text measurement depends on the FontRenderContext and this is implicit in the FontMetrics object returned by the printer graphics which is not available except inside the print() method.

Page 53: Performing Custom Painting(Drawing)

1091

The page format may not be disclosured until printing occurs. Since if the user selected a landscape mode in the print dialog, then this setting needs to be accounted for. The PageFormat object passed into the print() method provides this information.

The page break positions are used as represented in the following code fragment: /* Draw each line that is on this page. * Increment 'y' position by lineHeight for each line. */ int y = 0; int start = (pageIndex == 0) ? 0 : pageBreaks[pageIndex-1]; int end = (pageIndex == pageBreaks.length) ? textLines.length : pageBreaks[pageIndex]; for (int line=start; line<end; line++) { y += lineHeight; g.drawString(textLines[line], 0, y); } If a document contains 100 lines and only 48 lines fit on a page, then an application prints 3 pages with page breaks after 48 and 96 lines of text. The remaining 4 lines are printed on the last page. The complete code for this example is in PaginationExample.java.

The following simplifying factors are used in the PaginationExample code:

Each page has the same height. The same font is used.

Working with Print Services and Attributes From the previous lessons you have learned that the Java 2D ™ printing API supports page imaging, displays print and page setup dialogs, and specifies printing attributes. Printing services is another key component of any printing subsystem.

The Java™ Print Service (JPS) API extends the current Java 2D printing features to offer the following functionality:

Application discovers printers that cater to its needs by dynamically querying the printer capabilities.

Application extends the attributes included with the JPS API. Third parties can plug in their own print services with the Service Provider Interface,

which print different formats, including Postscript, PDF, and SVG.

The Java Print Service API consists of four packages:

Page 54: Performing Custom Painting(Drawing)

1092

The javax.print package provides the principal classes and interfaces for the Java™ Print Service API. It enables client and server applications to:

Discover and select print services based on their capabilities. Specify the format of print data. Submit print jobs to services that support the document type to be printed.

Document Type Specification

The DocFlavor class represents format of the print data, such as JPEG or PostScript. The DocFlavor format consists of two parts: a MIME type and a representation class name. A MIME type describes the format, and a document representation class name indicates how the document is delivered to the printer or output stream. An application uses the DocFlavor and an attribute set to find printers with the capabilities specified by the attribute set. This code sample demonstrates obtaining an array of StreamPrintServiceFactory objects that can return StreamPrintService objects able to convert a GIF image into PostScript:

DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF; String psMimeType = DocFlavor.BYTE_ARRAY.POSTSCRIPT.getMimeType(); StreamPrintServiceFactory[] psfactories = StreamPrintServiceFactory.lookupStreamPrintServiceFactories( flavor, psMimeType);

Attribute Definitions

The javax.print.attribute and javax.print.attribute.standard packages define print attributes which describe the capabilities of a print service, specify the requirements of a print job, and track the progress of the print job.

For example, if you would like to use A4 paper format and print three copies of your document you will have to create a set of the following attributes implementing the PrintRequestAttributeSet interface:

PrintRequestAttributeSet attr_set = new HashPrintRequestAttributeSet(); attr_set.add(MediaSize.ISO_A4); attr_set.add(new Copies(3));

Then you must pass the attribute set to the print job's print method, along with the DocFlavor.

Print Service Discovery

Page 55: Performing Custom Painting(Drawing)

1093

An application invokes the static methods of the abstract class PrintServiceLookup to locate print services that have the capabilities to satisfy the application's print request. For example, in order to print two copies of a double-sided document, the application first needs to find printers that have double-sided printing capability:

DocFlavor doc_flavor = DocFlavor.INPUT_STREAM.PDF; PrintRequestAttributeSet attr_set = new HashPrintRequestAttributeSet(); attr_set.add(new Copies(2)); attr_set.add(Sides.DUPLEX); PrintService[] service = PrintServiceLookup.lookupPrintServices(doc_flavor, attr_set);

Common Use of the API

In conclusion, the Java Print Service API performs the following steps to process a print request:

1. Chooses a DocFlavor. 2. Creates a set of attributes. 3. Locates a print service that can handle the print request as specified by the DocFlavor

and the attribute set. 4. Creates a Doc object encapsulating the DocFlavor and the actual print data. 5. Gets a print job, represented by DocPrintJob, from the print service. 6. Calls the print method of the print job.

For more information about Java Print Service, see Java 2D™ Print Service API User Guide.

Printing the Contents of a User Interface Another common printing task is to print the contents of a window or a frame, either in whole, or in part. The window may contain the following components: toolbars, buttons sliders, text labels, scrollable text areas, images, and other graphical content. All of these components are printed using the following methods of the Java 2D™ printing API:

java.awt.Component.print(Graphics g); java.awt.Component.printAll(Graphics g);

The following figure represents a simple user interface.

Page 56: Performing Custom Painting(Drawing)

1094

The code to create this UI is located in the sample program PrintUIWindow.java.

To print this window, modify the code in the earlier examples which printed text or images. The resulting code should appear as follows:

public int print(Graphics g, PageFormat pf, int page) throws PrinterException { if (page > 0) { return NO_SUCH_PAGE; } Graphics2D g2d = (Graphics2D)g; g2d.translate(pf.getImageableX(), pf.getImageableY()); /* Print the entire visible contents of a java.awt.Frame */ frame.printAll(g); return PAGE_EXISTS; }

Note: The call to the printAll method is the only difference between this example and examples to print text or image. The print(Graphics g) method mirrors the java.awt.Component.paint(Graphics g) method used for on-screen rendering. Use the print() method rather than the paint() method as the Components class may have overridden the print() method to handle the printing case differently.

The printAll(Graphics g)method prints the component and

all its subcomponents. This method is usually used to print

object such as a complete window, rather than a single component.

Page 57: Performing Custom Painting(Drawing)

1095

Printing Support in Swing Components The PrintUIWindow.java example represented in the previous section demonstrates that the printout is exactly the same you saw on the screen. This appearance seems reasonable. However, if a window is scrollable, then the contents that are currently scrolled out of view are not included in the printout. This creates a dump effect on the printer. This becomes a particular problem when printing large components such as a Swing table or text components. Components may contain many lines of text which cannot all be entirely visible on the screen. In this case, print the contents displayed by the component in a manner consistent with the screen display.

To solve this problem, the Swing table and all text components are printing aware. The following methods directly provide the use of the Java 2D™ printing:

javax.swing.JTable.print(); javax.swing.text.JTextComponent.print();

These methods provide a full implementation of printing for their contents. An application doesn't need directly create a PrinterJob object and implement the Printable interface. The call of these methods displays a print dialog and prints the component's data in accordance with the user's selections. There are also additional methods which provide more options.

For more information see related articles at java.sun.com.

Page 58: Performing Custom Painting(Drawing)

1096

Lesson: Advanced Topics in Java2D This lesson shows you how to use Graphics2D to display graphics with fancy outline and fill styles, transform graphics when they are rendered, constrain rendering to a particular area, and generally control the way graphics look when they are rendered. You'll also learn how to create complex Shape objects by combining simple ones and how to detect when the user clicks on a displayed graphics primitive. These topics are discussed in the following sections:

Transforming Shapes, Text, and Images

This section shows you how to modify the default transformation so that objects are translated, rotated, scaled, or sheared when they are rendered.

Clipping the Drawing Region

You can use any shape as a clipping path--the area within which rendering takes place.

Compositing Graphics

This section illustrates the various compositing styles supported by AlphaComposite and shows you how to set the compositing style in the Graphics2D rendering context.

Controlling Rendering Quality

This section describes the rendering hints that Graphics2D supports and shows you how to specify your preference in the trade-off between rendering quality and speed.

Constructing Complex Shapes from Geometry Primitives

This section shows you how to perform boolean operations on Shape objects using the Area class.

Supporting User Interaction

This section shows you how to perform hit detection on graphics primitives.

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 the AffineTransform class. An affine transform is a transformation such as translate, rotate, scale, or shear in which parallel lines remain parallel even after being transformed.

The Graphics2D class 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:

Page 59: Performing Custom Painting(Drawing)

1097

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 object 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.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

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:

Page 60: Performing Custom Painting(Drawing)

1098

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);

Note: Never use the setTransform method 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 the getTransform method 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 the setTransform method.

Clipping the Drawing Region Any Shape object can be used as a clipping path that restricts the portion of the drawing area that will be 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.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

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);

Page 61: Performing Custom Painting(Drawing)

1099

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 contain 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 textTl = 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);

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

Starry.java contains the complete code for this program. This applet requires the Starry.gif image file.

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.

Page 62: Performing Custom Painting(Drawing)

1100

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 the Graphics2D class, 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.

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

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.

Page 63: Performing Custom Painting(Drawing)

1101

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 Use the Graphics2D class 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, construct a RenderingHints object and pass it into Graphics2D by using the setRenderingHints method. 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:

public void paint (graphics g){ Graphics2D g2 = (Graphics2D)g; RenderingHints rh = new RenderingHints( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHints(rh); ... }

Note: 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:

Page 64: Performing Custom Painting(Drawing)

1102

Hint Key Values

Antialiasing KEY_ANTIALIASING VALUE_ANTIALIAS_ON VALUE_ANTIALIAS_OFF VALUE_ANTIALIAS_DEFAULT

Alpha Interpolation

KEY_ALPHA_INTERPOLATION VALUE_ALPHA_INTERPOLATION_QUALITY VALUE_ALPHA_INTERPOLATION_SPEED VALUE_ALPHA_INTERPOLATION_DEFAULT

Color Rendering

KEY_COLOR_RENDERING VALUE_COLOR_RENDER_QUALITY VALUE_COLOR_RENDER_SPEED VALUE_COLOR_RENDER_DEFAULT

Dithering KEY_DITHERING VALUE_DITHER_DISABLE VALUE_DITHER_ENABLE VALUE_DITHER_DEFAULT

Fractional Text Metrics

KEY_FRACTIONALMETRICS VALUE_FRACTIONALMETRICS_ON VALUE_FRACTIONALMETRICS_OFF VALUE_FRACTIONALMETRICS_DEFAULT

Image Interpolation

KEY_INTERPOLATION VALUE_INTERPOLATION_BICUBIC VALUE_INTERPOLATION_BILINEAR VALUE_INTERPOLATION_NEAREST_NEIGHBOR

Rendering KEY_RENDERING VALUE_RENDER_QUALITY VALUE_RENDER_SPEED VALUE_RENDER_DEFAULT

Stroke Normalization Control

KEY_STROKE_CONTROL VALUE_STROKE_NORMALIZE VALUE_STROKE_DEFAULT VALUE_STROKE_PURE

Stroke Normalization Control

KEY_STROKE_CONTROL VALUE_STROKE_NORMALIZE VALUE_STROKE_DEFAULT VALUE_STROKE_PURE

Text Antialiasing

KEY_TEXT_ANTIALIASING VALUE_TEXT_ANTIALIAS_ON VALUE_TEXT_ANTIALIAS_OFF VALUE_TEXT_ANTIALIAS_DEFAULT VALUE_TEXT_ANTIALIAS_GASP VALUE_TEXT_ANTIALIAS_LCD_HRGB VALUE_TEXT_ANTIALIAS_LCD_HBGR VALUE_TEXT_ANTIALIAS_LCD_VRGB VALUE_TEXT_ANTIALIAS_LCD_VBGR

LCD Text Contrast

KEY_TEXT_LCD_CONTRAST Values should be a positive integer in the range 100 to 250. A lower value (eg 100) corresponds to higher contrast text when displaying dark text on a light background. A higher value (eg 200) corresponds to lower contrast text when displaying dark text on a light background. A typical useful value is in the narrow range 140-180. If no value is specified, a system or implementation default value will be applied.

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

Page 65: Performing Custom Painting(Drawing)

1103

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 the Area class implements the Shape interface and supports the following boolean operations.

Union

Subtraction

Intersection

Exclusive-or (XOR)

Example: Areas

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

Note: If you don't see the applet running above, you need to install release 6 of the JDK.

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);

Page 66: Performing Custom Painting(Drawing)

1104

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 enable 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 hit method of the Graphics2D class provides a way to easily determine whether a mouse click occurred over a particular Shape object. 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 the TextLayout class to manage the shaping and positioning of the text, you can also use TextLayout to perform hit testing for text editing. For more information see the chapter Text and Fonts in the Java 2D™ Programmer's Guide or see the HitTestSample example below, which uses a TextLayout to perform simple hit-testing.

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.

Note: If you don't see the applet running above, you need to install release 6 of the JDK. 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); ...

Page 67: Performing Custom Painting(Drawing)

1105

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.

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.

Note: If you don't see the applet running above, you need to install release 6 of the JDK. 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);

Page 68: Performing Custom Painting(Drawing)

1106

// 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();