Main Page

Previous Next

Drawing on a Component

Before we get into the specifics of how you draw on a component, let's understand the principle ideas behind it. When you draw on a component using the Java 2D capabilities, there are two coordinate systems involved. When you draw something – a line or a curve for instance – you specify the line or the curve in a device-independent logical coordinate system called the user coordinate system for the component, or user space. By default this coordinate system has the same orientation as the system that we discussed for positioning components in containers. The origin is at the top-left corner; the positive x-axis runs from left to right, and the positive y-axis from top to bottom. Coordinates are usually specified as floating point values, although integers can also be used.

Click To expand

A particular graphical output device will have its own device coordinate system, or device space. This has the same orientation as the default user coordinate system, but the coordinate units depend on the characteristics of the device. Your display, for instance, will have a different device coordinate system for each configuration of the screen resolution, so the coordinate system when your display is set to 1024x768 resolution will be different from the coordinate system for 800x600 pixels.

Note 

Incidentally, the drawing process is often referred to as rendering, since graphical output devices are generally raster devices and the drawing elements such as lines, rectangles, text, and so on need to be rendered into a rasterized representation before they can be output to the device.

Having a device-independent coordinate system for drawing means that you can use essentially the same code for outputting graphics to a variety of different devices – to your display screen, for example, or to your printer – even though these devices themselves have quite different coordinate systems with different resolutions. The fact that your screen might have 90 pixels per inch while your printer may have 600 dots per inch is automatically taken care of. Java 2D will deal with converting your user coordinates to the device coordinate system that is specific to the output device you are using.

With the default mapping from user coordinates to device coordinates, the units for user coordinates are assumed to be 1/72 of an inch. Since for most screen devices the pixels are approximately 1/72 inch apart, the conversion amounts to an identity transformation. If you want to use user coordinates that are in some other units, you have to provide for this yourself. We will look into the mechanism that you would use to do this when we discuss transformations in the next chapter.

Graphics Contexts

The user coordinate system for drawing on a component using Java 2D is encapsulated in an object of type Graphics2D, which is usually referred to as a graphics context. It provides all the tools you need to draw whatever you want on the surface of the component. A graphics context enables you to draw lines, curves, shapes, filled shapes, as well as images, and gives you a great deal of control over the drawing process.

The Graphics2D class is derived from the Graphics class that defined device contexts in earlier versions of Java, so if you feel the need to use the old drawing methods, they are all inherited in the Graphics2D class. We will be concentrating on the new more powerful and flexible facilities provided by Graphics2D but, as you will see, references to graphics contexts are usually passed around as type Graphics so you need to be aware of it. Note that both the Graphics and Graphics2D classes are abstract classes, so you can't create objects of either type directly. An object representing a graphics context is entirely dependent on the component to which it relates, so a graphics context is always obtained for use with a particular component.

The Graphics2D object for a component takes care of mapping user coordinates to device coordinates, so it contains information about the device that is the destination for output as well as the user coordinates for the component. The information required for converting user coordinates to device coordinates is encapsulated in three different kinds of object:

  • A GraphicsEnvironment object encapsulates all the graphics devices (as GraphicsDevice objects) and fonts (as Font objects) that are available on your computer.

  • A GraphicsDevice object encapsulates information about a particular device such as a screen or a printer, and stores it in one or more GraphicsConfiguration objects.

  • A GraphicsConfiguration object defines the characteristics of a particular device such as a screen or a printer. Your display screen will typically have several GraphicsConfiguration objects associated with it, each corresponding to a particular combination of screen resolution and number of displayable colors.

The graphics context also maintains other information necessary for drawing operations such as the drawing color, the line style and the specification of the fill color and pattern for filled shapes. We will see how to work with these attributes in examples later in this chapter.

Since a graphics context defines the drawing context for a specific component, before you can draw on a component you must have a reference to its graphics context object. For the most part, you will draw on a component by implementing the paint() method that is called whenever the component needs to be reconstructed. An object representing the graphics context for the component is passed as an argument to the paint() method, and you use this to do the drawing. The graphics context includes all the methods that you use to draw on a component and we will be looking into many of these in this chapter.

The paint() method is not the only way of drawing on a component. You can obtain a graphics context for a component at any time just by calling its getGraphics()method.

There are occasions when you want to get a component redrawn while avoiding a direct call of the paint() method. In such cases you should call repaint() for the component. There are five versions of this method that you can use; we'll look at four:

repaint() Method

Description

repaint()

Causes the entire component to be repainted by calling its paint() method after all of the currently outstanding events have been processed.

repaint(long msec)

Requests that the entire component is repainted within msec milliseconds.

repaint(int msec, int x, int y, int width, int height)

Adds the region specified by the arguments to the dirty region list if the component is visible. The dirty region list is simply a list of areas of the component that need to be repainted. The component will be repainted by calling its paint() method when all currently outstanding events have been processed or within msec milliseconds. The region is the rectangle at position (x, y) with the width and height as specified by the last two arguments.

repaint(Rectangle rect)

Adds the rectangle specified by rect to the dirty region list if the component is visible.

You will find that the first and the last methods are the ones you use most of the time.

That's enough theory for now. Time to get a bit of practice.

Let's get an idea of how we can draw on a component by drawing on the SketchView object that we added to Sketcher. All we need to do is implement the paint() method in the SketchView class.

Try It Out – Drawing in a View

Add the following implementation of the method to the SketchView class:

import javax.swing.JComponent;
import java.util.Observer; 
import java.util.Observable; 
import java.awt.*;                                   // For Graphics

class SketchView extends JComponent implements Observer {
  public void paint(Graphics g) {
    // Temporary code
    Graphics2D g2D = (Graphics2D)g;                // Get a Java 2D device context

    g2D.setPaint(Color.red);                        // Draw in red
    g2D.draw3DRect(50, 50, 150, 100, true);         // Draw a raised 3D rectangle
    g2D.drawString("A nice 3D rectangle", 60, 100); // Draw some text
  }

  // Rest of the class as before...
}

If you recompile the file SketchFrame.java and run Sketcher, you can see what the paint() method produces. You should see the window shown here.

OK, it's not your traditional meaning of 3D. In this case, the edges of the rectangle are highlighted so that that they appear to be beveled and lift from the top left hand corner (or the coordinate origin).

How It Works

The graphics context is passed as the argument to the paint() method as type Graphics (the base class for Graphics2D)so to use the methods defined in the Graphics2D class we must first cast it to that type. The paint() method has a parameter type of Graphics for compatibility reasons.

Once we have cast the graphics context, we then set the color in which we will draw by calling the setPaint() method for the Graphics2D object and passing the drawing color as an argument. All subsequent drawing operations will now be in Color.red. We can change this again later with another call to setPaint() when we want to draw in a different color.

Next we call the draw3DRect() method defined in the Graphics2D class that draws a 3D rectangle. The first two arguments are integers specifying the x and y coordinates of the top-left corner of the rectangle to be drawn, relative to the user space origin of the component – in this case the top-left corner of the view object in the content pane. The third and fourth arguments are the width and height of the rectangle respectively, also in user coordinates.

The drawString() method draws the string specified as the first argument at the position determined by the second and third argument – these are the x and y coordinates in user coordinates of the bottom-left corner of the first letter of the string. The string will be drawn by obtaining the glyphs for the current Font object in the device context corresponding to the characters in the string. As we said when we discussed Font objects, the glyphs for a font define the physical appearance of the characters.

However, there's more to drawing than is apparent from this example. The graphics context has information about the line style to be drawn, as well as the color, the font to be used for text, and more besides. Let's dig a little deeper into what is going on.

The Drawing Process

A Graphics2D object maintains a whole heap of information that determines how things are drawn. Most of this information is contained in six attributes within a Graphics2D object:

  • The paint attribute determines the drawing color for lines. It also defines the color and pattern to be used for filling shapes. The paint attribute is set by calling the setPaint(Paint paint) method for the graphics context. Paint is an interface that is implemented by the Color class that defines a color. It is also implemented by the GradientPaint and TexturePaint classes that are a color pattern and a texture respectively. You can therefore pass references of any of these types to the setPaint() method. The default paint attribute is the color of the component.

  • The stroke attribute defines a pen that determines the line style, such as solid, dashed or dotted lines, and the line thickness. It also determines the shape of the ends of lines. The stroke attribute is set by calling the setStroke(Stroke s) method for a graphics context. The default stroke attribute defines a square pen that draws a solid line with a thickness of 1 user coordinate unit. The ends of the line are square and joins are mitered.

  • The font attribute determines the font to be used when drawing text. The font attribute is set by calling the setFont(Font font) method for the graphics context. The default font is the font set for the component.

  • The transform attribute defines the transformations to be applied during the rendering process. What you draw can be translated, rotated and scaled as determined by the transforms currently in effect. There are several methods for applying transforms to what is drawn, as we will see. The default transform is the identity transform, which leaves things unchanged.

  • The clip attribute defines the boundary of an area on a component. Rendering operations are restricted so that drawing only takes place within the area enclosed by the clip boundary. The clip attribute is set by calling one of the two setClip() methods for a graphics context. The default clip attribute is the whole component area.

  • The composite attribute determines how overlapping shapes are drawn on a component. You can alter the transparency of the fill color of a shape so an underlying shape shows through. You set the composite attribute by calling the setComposite(Composite comp) method for the graphics context. The default composite attribute causes a new shape to be drawn over whatever is already there, taking account of the transparency of any of the colors used.

All of the objects representing attributes are stored as references within a Graphics2D object. Therefore, you must always call a setXXX() method to alter an attribute in a graphics context, not try to modify an external object directly. If you alter an object externally that has been used to set an attribute, the results are unpredictable.

Note 

You can also affect how the rendering process deals with 'jaggies' when drawing lines. The process to eliminate 'jaggies' on sloping lines is called antialiasing, and you can change the antialiasing that is applied by calling one of the two setRenderingHints() methods for a graphics context. We will not be going into this aspect of drawing further though.

There's a huge amount of detail on attributes under the covers. Rather than going into all that here, we'll explore how to apply new attributes to a graphics context piecemeal where it is relevant to the various examples we will create.

Rendering Operations

The following basic methods are available to a Graphics2D object for rendering various kinds of entity:

Method

Description

draw(Shape shape)

Renders a shape using the current attributes for the graphics context. We will be discussing what a shape is next.

fill(Shape shape)

Fills a shape using the current attributes for the graphics context. We will see how to do this later in this chapter.

drawString(String text)

Renders a text string using the current attributes for the graphics context. We will be applying this further in the next chapter.

drawImage()

Renders an image using the current attributes for the graphics context. This is quite a complicated operation so we won't be getting very far into this.

Let's see what shapes are available. They'll help make Sketcher a lot more useful.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor