Concepts / Introduction to Graphics2D

Introduction to Graphics2D

A transparent color is a Color object created with an alpha component, which controls its opacity; an ordinary three-argument RGB color is fully opaque.

  • Programming

Why Some Colors Let Shapes Show Through

Most colors you have used so far in Java are solid. When you draw with a solid color, whatever you paint replaces what was underneath it. Sometimes you want the opposite: a shape that lets the shapes beneath it show through. Java handles this with colors that carry an alpha component.

This article starts from a small program that builds transparent colors and asks you to reason about what each color will look like. It then explains the rules that make those predictions work, and finally shows how the graphics object that does the drawing fits into the same picture, including the second, more capable drawing class Graphics2D.

Tracing a Transparent Color Program

The traced program for this lesson creates colors, stores them in variables, and sends them to a graphics context. Follow it one line at a time. For each line, ask two questions: which value is being stored, and does that value fall inside the range the constructor requires? Most surprising results trace back to one of those two questions.

java
Notes on the code
  • Line 1: integer form, red 255, green 0, blue 0, alpha 200 (slightly transparent)
  • Line 2: float form, red 0.0, green 0.5, blue 1.0, alpha 0.5 (50 percent opaque)
  • Line 3: three-argument RGB form, no alpha value in the call

What do you think happens?

The three-argument constructor creates a color with only red, green, and blue values, so no alpha value appears in the call. What opacity does Java give the color stored in solid?

Reveal answer
Answer:

Full opacity. An ordinary three-argument RGB color is fully opaque.

The source states that an ordinary three-argument RGB color is fully opaque. Because the call has no alpha argument, Java supplies the fully opaque value for you. On the integer scale that is 255, and on the float scale it is 1.0.

  1. 1
    Read line 1 from left to right. The first three arguments set red, green, and blue, and the last argument, 200, sets alpha. Check that 200 falls in the integer range of 0 to 255. It does, so the color is slightly transparent.
  2. 2
    Read line 2. The arguments are floating-point numbers, so this is the float form. Check that every value falls in the range 0.0 to 1.0. They do. The alpha of 0.5 means 50 percent opacity.
  3. 3
    Read line 3. No alpha argument appears, so the color is fully opaque, as the prediction above should have shown.
  4. 4
    If any line fails your check, stop there. That line is the failure location, and the cause is almost always in the arguments passed on that line or in the choice of constructor.

How Alpha Controls Opacity

Colors with alpha components were introduced in Java along with Graphics2D, but they can also be used with ordinary Graphics objects. The alpha component is the part of a color that controls its opacity. A fully opaque color replaces what is underneath it, while a semi-transparent color lets the underlying drawing show through. To give a color an alpha component, you create the Color object using one of the constructors from the Color class.

The Color class offers two constructors that accept an alpha component. Both take four parameters: three color channels and one alpha value. The two forms differ only in the kind of number they expect. The first uses integers, and the second uses floating-point numbers.

200 (integer scale)range 0 to 2550.784 (float scale)range 0.0 to 1.0same opacity
How does a given alpha value map between the integer scale and the float scale?

The figure shows one opacity written two ways. Dividing the integer alpha by 255 gives the same proportion on the float scale, so 200 out of 255 is about 0.784. The figure is a reading aid for the two scales; the rule that matters is that each constructor form expects its own range.

  1. 1
    Read a constructor call from left to right. The first three arguments set the red, green, and blue channels, and the last argument sets the alpha component.
  2. 2
    Check the last argument first to confirm which opacity you are setting.
  3. 3
    Then confirm the other three arguments fall inside the range that matches the constructor form you are using.

The float form lets you think in percentages. A value of 0.5 is a half, which is exactly the 50 percent opacity the source describes. That is the main reason to prefer the float form when your thinking is already about proportions, while the integer form suits code that already works in 0 to 255 channel values.

java

Once a transparent color exists, you use it exactly the way you use any other color. Pass it to the graphics context with setColor, and every drawing operation that follows will use it. Calling g.setColor(c) sets the color for subsequent drawing, which is the same mechanism used for opaque colors. In the snippet above, the rectangle is drawn with the 25 percent opaque blue because that is the color most recently set on g.

Drawing Through a Graphics Object

In Java, every drawing operation is carried out through an object of type Graphics. Think of this object as the single channel through which pixels reach the screen. When you want to draw something, you do not talk to the screen directly; you ask the Graphics object to perform the drawing for you.

The Graphics class is a basic class for drawing operations. It supplies the everyday commands a program needs: commands for drawing shapes, commands for drawing text, and commands for selecting the color that subsequent drawing will use. Those three groups cover a lot of ordinary needs. The source is candid about the limit of this toolkit: these basic commands are adequate in many cases, but they fall well short of what a serious computer graphics program needs. That gap is the reason Java provides a second class.

How Graphics2D Extends Graphics

Java's second drawing class is Graphics2D. It is a sub-class of Graphics, and it provides a larger set of drawing operations than its parent. The subclass relationship has an important consequence: because Graphics2D inherits from Graphics, every method defined in Graphics is also available on a Graphics2D object. Nothing the basic class offers is lost; the subclass only adds to it.

Graphicsshapes, text, colorGraphics2Dlarger set of operationssub-class of
How is Graphics2D related to Graphics, and which operations live in the subclass but not the parent?

Read the diagram as an inheritance chain. Graphics2D sits below Graphics because it is the subclass. Anything you can call on a Graphics object you can also call on a Graphics2D object, and Graphics2D adds the richer set of drawing operations that the basic class lacks.

Casting the paintComponent Graphics Context

A program rarely creates a drawing object on its own in this context. Instead, the paintComponent() method of a JComponent hands you a graphics context, and its declared type is Graphics. The important detail is what the context actually is at runtime. The source states that the graphics context belongs to the sub-class Graphics2D, even though it is declared as Graphics. That means it can be type-cast to gain the advanced methods.

Code walkthrough
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(faded);
    g2.fillRect(10, 10, 100, 60);
}
  1. 1
    Line 2 calls the parent version of paintComponent, passing the context along, as the source pattern does.
  2. 2
    Line 3 type-casts g to Graphics2D and stores the result in g2. The cast works because the runtime object is a Graphics2D.
  3. 3
    Line 4 uses g2 to set the color. Because g2 and g refer to the same context, the color applies to the same drawing destination.
  4. 4
    Line 5 draws a rectangle through g2, using the advanced method set that the cast made available.
  • The parameter g is declared as Graphics
  • The cast creates a Graphics2D reference to the same context

Now return to the question from the introduction. With the cast in place, a transparent color set on g2 governs what is drawn next, which is the same mechanism you traced earlier with g.setColor(c). When a result looks wrong, check the same two things on the line that sets the color: which value is stored, and whether it falls inside the required range.

Mistakes With Graphics and Graphics2D

  • Common mistake

    Passing an alpha value outside the range for the chosen constructor form

    What it looks like

    new Color(255, 0, 0, 1.0f) mixes an integer channel form with a float alpha, and a value such as 300 on the integer scale is outside 0 to 255.

    Why it fails

    Each constructor form expects its own range for all four arguments, so an out-of-range value does not produce the color you intended.

    Do this instead

    Pick one form first. Use integers from 0 to 255 for the integer form or floats from 0.0 to 1.0 for the float form, and check the alpha argument first.

  • Common mistake

    Assuming a three-argument color is transparent

    What it looks like

    Expecting new Color(255, 0, 0) to be see-through and then being surprised that it covers what is beneath it.

    Why it fails

    An ordinary three-argument RGB color is fully opaque.

    Do this instead

    If you want transparency, use one of the four-argument constructors and supply an alpha value.

  • Common mistake

    Thinking the cast creates a separate drawing surface

    What it looks like

    Drawing through g2 and expecting the result to appear somewhere other than where g draws.

    Why it fails

    Casting gives a more specifically typed reference to the same graphics context.

    Do this instead

    Treat g and g2 as two names for one context. Drawing through either one goes to the same destination.

  • Common mistake

    Casting a Graphics object that is not a Graphics2D

    Why it fails

    The cast relies on the runtime object actually belonging to Graphics2D. The source states that the paintComponent context does belong to that sub-class, so the cast is valid there, but the same assumption does not hold for every object you might hold.

    Do this instead

    Cast the context you receive from paintComponent, and confirm the object type when you are unsure what a method hands you.

  • Common mistake

    Confusing the declared type of a variable with the actual object behind it

Edge case

Floating-point values are one place where the float form needs care. Both constructor forms take the same four positions, so a value written with the wrong suffix or number style can pass through the wrong constructor. Read the argument types, not only the numbers, when a color does not match your prediction.

Practice Tracing Transparent Colors

MEDIUM

A program runs these three lines in order: Color a = new Color(0, 255, 0, 128); Color b = new Color(0.0f, 1.0f, 0.0f, 1.5f); Color c = new Color(0, 255, 0); Trace each line. Which line is the first to produce a result that differs from what you expected, and what is the cause?

Hints
  • Start at line 1 and check whether the alpha argument falls in the integer range for that form.
  • Line 2 uses the float form, so check every value against the 0.0 to 1.0 range.
  • Line 3 has no alpha argument, so think about the opacity Java gives an ordinary three-argument color.
MEDIUM

Write a paintComponent method that sets a semi-transparent color using the float form with alpha 0.5, casts the Graphics parameter to Graphics2D, and fills one rectangle with that color. Then explain in one sentence why the cast does not create a new drawing surface.

Hints
  • Use a Graphics2D variable for the cast result.
  • Call setColor on the cast variable before the fill call.

Key Takeaways

  • An alpha component controls a color's opacity. A three-argument RGB color is fully opaque, while the four-argument constructors accept an alpha value.
  • The integer form takes values from 0 to 255 and the float form takes values from 0.0 to 1.0. Each form expects its own range for all four arguments, and the float form suits percentage-style thinking.
  • Every drawing operation in Java goes through a Graphics object. Graphics2D is a sub-class of Graphics that inherits its methods and adds a larger set of drawing operations.
  • The graphics context passed to paintComponent() is declared as Graphics but belongs to Graphics2D, so you can cast it to Graphics2D; the cast gives a more specific reference to the same context, not a new surface.
  • When a transparent-color result looks wrong, trace the program line by line and stop at the first line whose stored value or argument range differs from your prediction.