Concepts / Colors in Java Graphics

Colors in Java Graphics

A Paint object describes a fill or line color; it has no effect until it is installed in a Graphics2D context with setPaint().

  • Programming

Predicting Which Shapes Change Color

Imagine a Java program that draws three filled rectangles, then installs a new paint, then draws two more shapes. Before reading on, decide for yourself which shapes end up with the new paint. The answer depends on one method call, setPaint(), and on where that call appears in the code. This article follows a single idea from start to finish: a Paint object is only a description of a color, and it takes effect on the drawing surface only when it is installed and used by later drawing operations.

The source material illustrates the same idea with a polygon filled two different ways. One fill uses a gradient, the other uses a texture, and the outline is drawn in plain black. Those pictures were produced by a sample program called PaintDemo, which lets you choose among several paints and adjust their properties. Keep that picture in mind as you read, because the central question is how a single line of code can change the fill of everything drawn after it.

What do you think happens?

A program fills rectangle A, then calls setPaint() with a gradient, then fills rectangle B. Which rectangles use the gradient?

  • Rectangle A and rectangle B

  • Neither rectangle

  • Only rectangle B

Reveal answer
Answer:

Only rectangle B

Subsequent drawing operations on a Graphics2D object use the installed paint, so the position of setPaint() decides which shapes are affected. Rectangle A was drawn before the installation, when the gradient had not yet been installed, so it does not pick up the gradient.

Paint Objects and the Graphics2D Context

Definition

A Paint object describes a fill or line color. It has no effect until it is installed in a Graphics2D context with setPaint().

A Paint object and a Graphics2D object play different roles. The Paint object holds the colors and the rules for how they are distributed across a surface. The Graphics2D object is the drawing context that performs the actual drawing. Every drawing operation in Graphics2D takes its color from whichever Paint object is currently installed. This means the way you install a paint determines everything that follows it.

Paint objectholds the colorsGraphics2D contextstores the installed paintfill callafter setPaint()draw callafter setPaint()setPaint()uses installed paintuses installed paint
The Paint object is created first, handed to the Graphics2D context with setPaint(), and then consulted by every later draw call. Creating the Paint alone changes nothing on screen.

The flow in this diagram runs in one direction. Nothing flows backward: a drawing call made before the installation does not pick up the new paint, because the installation had not happened yet. Only calls made after setPaint() see the new paint, until another paint is installed or the paint is replaced.

Building a Gradient from Two Points

A basic gradient paint is created with a constructor that takes two points, a color for each point, and a boolean called cyclic. The first four parameters together determine the geometry of the gradient. The constructor creates a gradient that has color c1 at the point (x1,y1) and color c2 at the point (x2,y2).

Moving along the straight line between the two points, the color changes smoothly from c1 to c2. Along any line perpendicular to that line, the color stays constant. The gradient is therefore organized in bands that run across the direction of the line joining the two points. The two points define both a direction and a length, so moving them changes the width and orientation of the gradient. A short distance between the points produces a sharp transition, while a long distance produces a gentle one.

Worked example

A black-to-gray gradient along a diagonal

Create a GradientPaint that runs from black at (0,0) to light gray at (100,100), with cyclic set to false.

  1. 1
    Identify the first point and its color

    The point (0,0) carries color c1, which is black.

  2. 2
    Identify the second point and its color

    The point (100,100) carries color c2, which is light gray.

  3. 3
    Interpret the line between the points

    The color changes smoothly along the straight line from (0,0) to (100,100). Any line perpendicular to it has a constant color.

  4. 4
    Write the constructor call

    Pass x1, y1, c1, x2, y2, c2 and then the cyclic flag. Because the source names the color Color.LIGHT GRAY with a space as a typographical artifact, the correct Java constant is written Color.LIGHT_GRAY with an underscore.

Result

A gradient that moves from black to light gray along the diagonal, with cyclic set to false.

java
Notes on the code
  • 0, 0 is the first point (x1, y1), where Color.BLACK is c1
  • 100, 100 is the second point (x2, y2), where Color.LIGHT_GRAY is c2
  • false is the cyclic flag, which is covered in the next section

Gradient Bands and the Cyclic Flag

The last parameter of the gradient constructor is the cyclic flag. The source describes its job precisely: it decides whether colors keep changing beyond the second endpoint. The gradient itself is defined between the two points, and the flag controls what happens on the far side of the second point. Because the source does not describe the appearance in more detail, the safe way to reason about it is to compare the two settings directly: with cyclic set to false, the gradient is defined by the two endpoints alone, while with cyclic set to true, the colors continue to change past the second endpoint.

The source also mentions two related classes. LinearGradientPaint is similar to GradientPaint but can be based on more than two colors. RadialGradientPaint colors pixels by their distance from a central point, which produces rings of constant color instead of bands. The source refers readers to the API documentation for details on these classes, so this article does not cover them further.

Tiling a Texture with TexturePaint

A TexturePaint creates a texture effect from an image. To construct one you need two things: a BufferedImage that holds the texture, and a rectangle in which that image will be drawn. The rectangle is a Rectangle2D anchor rectangle. The image is scaled, if necessary, so that it exactly fills the anchor rectangle.

Outside the anchor rectangle, the image repeats horizontally and vertically to cover the whole plane. This repetition is what makes the paint behave like a tiled texture rather than a single picture. The size and position of the rectangle control the scale of the texture and where its pattern lines up on the drawing surface.

Anchor tileimage fills the rectangleRepeat to the rightsame image, tiledRepeat belowsame image, tiledRepeat diagonallysame image, tiledrepeats horizontallyrepeats verticallyrepeats both ways
The image fills the anchor rectangle, and copies of it repeat outward in every direction. Each copy lines up with the anchor rectangle's position and size.

Ordinarily the upper left corner of the rectangle sits at (0,0), and the rectangle has the same width and height as the image itself. In that common case the image is not scaled at all, and it tiles at its natural size. The source gives this pattern as the usual way to write the constructor call. Changing the rectangle is the way to change the scale of the texture, and moving its corner is the way to shift where the pattern begins.

Installing a Paint and Tracing Its Effect

The following program is an original illustration built around the gradient from the earlier section. It draws two shapes before setPaint() and two shapes after it. Trace it line by line and note which paint each fill uses at the moment it runs.

Code walkthrough
GradientPaint fade = new GradientPaint(0, 0, Color.BLACK, 100, 100, Color.LIGHT_GRAY, false);
Graphics2D g2 = (Graphics2D) g;
g2.fill(new Rectangle2D.Double(0, 0, 50, 50));
g2.setPaint(fade);
g2.fill(new Rectangle2D.Double(60, 60, 80, 80));
g2.fill(new Ellipse2D.Double(160, 10, 60, 60));
  1. 1
    Line 1 creates the Paint object. It holds the colors and the geometry but does not change the surface.
  2. 2
    Line 2 obtains the Graphics2D context from the graphics object g.
  3. 3
    Line 3 fills a rectangle. No gradient has been installed yet, so this fill is unaffected by the GradientPaint.
  4. 4
    Line 4 calls setPaint(fade). This is the step that installs the gradient into the context.
  5. 5
    Line 5 fills a second rectangle. This call happens after the installation, so the gradient is used.
  6. 6
    Line 6 fills an ellipse. It also happens after the installation and uses the same gradient, because no other paint has replaced it.
  • The GradientPaint is created, but at this point nothing is drawn with it
  • The first fill runs before any installation, so it uses whatever paint was already installed
  • setPaint() installs the gradient into the Graphics2D context
  • This fill runs after the installation, so it uses the gradient
  • This fill also runs after the installation, so it uses the gradient as well
Earlier paintinstalled before theprogramFirst filluses earlier paintsetPaint(fade)installs the gradientGradient installedfade is now activeSecond filluses gradientEllipse filluses gradientfill runsnext linestores paintfill runsfill runs
The installed paint changes only at setPaint(). Fills before that step use the earlier paint, and fills after it use the gradient.

Mistakes That Make Output Diverge

  • Common mistake

    Expecting setPaint() to recolor shapes drawn earlier

    What it looks like

    Filling rectangle A, then calling setPaint(fade), and expecting rectangle A to show the gradient.

    Why it fails

    Subsequent drawing operations use the installed paint. Calls made before the installation had not yet been affected by it.

    Do this instead

    Move setPaint() above the draw calls that should use the new paint, or accept that earlier shapes keep the earlier paint.

  • Common mistake

    Assuming that creating a Paint object changes what is drawn

    What it looks like

    Creating a GradientPaint and then drawing a shape without calling setPaint(), expecting the gradient to appear.

    Why it fails

    A Paint object does nothing until it is given to a graphics context. Creating it alone changes nothing on screen.

    Do this instead

    Pass the Paint object to setPaint() of the Graphics2D object before the drawing calls that should use it.

  • Common mistake

    Treating the anchor rectangle as a fixed image size

    What it looks like

    Creating a TexturePaint with a rectangle much larger than the image and expecting the picture to appear at its natural size.

    Why it fails

    The image is scaled to exactly fill the anchor rectangle, so the rectangle controls the texture's scale.

    Do this instead

    Use a rectangle with the image's width and height and upper left corner at (0,0) for natural size, or change the rectangle deliberately to scale the texture.

  • Common mistake

    Misspelling the Color constant copied from the source

    What it looks like

    Writing Color.LIGHT GRAY with a space, as it appears in the source text.

    Why it fails

    The space is a typographical artifact of the source. Java constants cannot contain spaces.

    Do this instead

    Write Color.LIGHT_GRAY with an underscore.

  • Common mistake

    Forgetting that the installed paint persists until it is replaced, so a later shape may use an unexpected gradient or texture

Practice: Finding Where the Output Diverges

MEDIUM

A program creates a GradientPaint named fade and a TexturePaint named brick. It fills rectangle A, then calls setPaint(fade), then fills rectangle B, then calls setPaint(brick), then fills rectangle C. Rectangle A should show the initial paint, rectangle B should show the gradient, and rectangle C should show the texture. Suppose rectangle B shows the texture instead of the gradient. Identify the step where the output first diverges from expectation and explain why rectangle B uses the wrong paint.

Hints
  • Find the setPaint() call that comes immediately before rectangle B's fill.
  • Ask which paint was installed at the moment rectangle B was filled.
  • Check whether the setPaint() calls appear in the order the program intends.

Key Takeaways

  • A Paint object describes a fill or line color and has no effect until it is installed in a Graphics2D context with setPaint().
  • Every drawing operation after setPaint() uses the installed paint, while calls made before the installation are unaffected, so the position of setPaint() decides which shapes change.
  • A GradientPaint blends from c1 at (x1,y1) to c2 at (x2,y2), and the cyclic flag decides whether colors keep changing beyond the second endpoint.
  • A TexturePaint scales a BufferedImage to an anchor Rectangle2D and repeats it across the plane, so the rectangle controls the texture's scale and position.
  • When output diverges from expectation, trace the order of setPaint() calls against the drawing calls to find the first point where the installed paint differs from what you intended.