Java 2D Graphics, Texture Fill
by Richard G. Baldwin
baldwin@austin.cc.tx.us
Java Programming, Lecture Notes # 316
March 19, 2000
Introduction
In an earlier lesson, I explained that the
Graphics2D class extends
the
Graphics class to provide more sophisticated control over geometry,
coordinate transformations, color management, and text layout. Beginning
with JDK 1.2,
Graphics2D is the fundamental class for rendering
two-dimensional shapes, text and images.
I also explained that without understanding the behavior of other classes
and interfaces such as Shape, AffineTransform, GraphicsConfiguration,
PathIterator,
and Stroke, it is not possible to fully understand the inner workings
of the Graphics2D class.
What has been covered previously?
Earlier lessons have explained a number of Java 2D concepts, including
Shape,
AffineTransform,
and
PathIterator. Before, I can explain the
Stroke class,
I need to explain how to fill a
Shape. Earlier lessons showed
you how to fill a
Shape with a solid color and with a color gradient.
How to fill, in general
I explained in an earlier lesson that if you want to
fill a
Shape
object before you draw it, you can accomplish this with the following two
steps:
-
Invoke setPaint() on the Graphics2D object, passing a reference
to an object of a class that implements the Paint interface as a
parameter.
-
Invoke the fill() method on the Graphics2D object, passing
a reference to the Shape object that you want to fill as a parameter.
This lesson will show you how to fill a
Shape with a tiled version
of an image, otherwise known as a texture.
The Three Paint Classes
In a previous lesson, I explained that the Java2D API in JDK 1.2.2 provides
three classes that implement the
Paint interface (and you can also
define your own):
-
Color
-
GradientPaint
-
TexturePaint
The Color class
The
Color class can be used to fill a
Shape object with a
solid color. That was the topic of an earlier lesson.
The GradientPaint class
The
GradientPaint class can be used to fill a
Shape with
a color gradient. That also was the topic of an earlier lesson.
The TexturePaint class
The
TexturePaint class can be used to fill a
Shape with a
tiled version of a
BufferedImage object. That is the topic
of this lesson.
At this point, we need to learn a little more about the class named
TexturePaint.
Here is what Sun has to say about the class.
“The
TexturePaint
class provides a way to fill a Shape with a texture that is specified
as a BufferedImage. The size of the BufferedImage object
should be small because the BufferedImage data is copied by the
TexturePaint
object.
At construction time, the texture is anchored to the upper left corner
of a Rectangle2D that is specified in user space. Texture is computed
for locations in the device space by conceptually replicating the specified
Rectangle2D
infinitely in all directions in user space and mapping the BufferedImage
to each replicated Rectangle2D.” |
So, we see immediately that we need to know something about the BufferedImage
class.
The BufferedImage Class
The
BufferedImage class extends the
Image class in JDK 1.2.Here
is what Sun has to say about
BufferedImage.
“The
BufferedImage
subclass describes an Image with an accessible buffer of image data.
A BufferedImage is comprised of a ColorModel and a Raster
of image data. The number and types of bands in the
SampleModel
of the Raster must match the number and types required by the ColorModel
to represent its color and alpha components. All BufferedImage objects
have an upper left corner coordinate of (0, 0). Any Raster used
to construct a BufferedImage must therefore have minX=0 and minY=0.” |
All in all, this looks pretty complicated. Fortunately, we don’t
need to be too concerned about many of the technical details.
Getting a BufferedImage
There are a couple of ways that you can get a
BufferedImage to use
to fill your
Shape. One way is to read a file containing an
image. I plan to cover that approach in subsequent lessons that deal
with image processing.
Another approach is to invoke the createImage() method on any
object that extends the Component class. This will return
a reference to an object of type Image, which can be downcast to
BufferedImage.
This is the approach used in this lesson,
The createImage() method
Here is what Sun has to say about the
createImage() method.
public
Image createImage(
int width,
int height)
Creates an off-screen drawable image to be used for double buffering.
Parameters:
width - the specified width.
height - the specified height.
Returns: an off-screen drawable image, which can be used for double
buffering. |
As mentioned above, the BufferedImage class extends the Image
class in JDK 1.2.
You can cast the Image to a BufferedImage
Here is what
Java Foundation Classes in a Nutshell, by David Flanagan,
has to say about the
createImage() method.
“This
method was first introduced in Java 1.0; it returns an Image object.
In Java 1.2, however, the returned Image object is always an instance
of BufferedImage so you can safely cast it.
After you have created an empty BufferedImage you can call its
createGraphics()
method to obtain a Graphics2D object.
...
Anything you can draw on the screen, you can draw into a BufferedImage.” |
The first sample program shows how to create and display a BufferedImage
object.
The second program shows how to create a BufferedImage program
and use it to fill a Shape object.
Sample Program BufferedImage01
This program shows how to create and display a
BufferedImage object
without a requirement for an external file containing an image.
A significantly reduced screen shot of the output of this program is
shown below.
The GUI is a Frame object
The program draws a four-inch by four-inch
Frame on the screen.
It translates the origin to the center of the
Frame. Then
it draws a pair of X and Y-axes centered on the new origin.
Then the program creates a BufferedImage object that is three
inches on each side. It gets a Graphics2D context on the BufferedImage.
Draw a green rectangle on the BufferedImage
Once the
Graphics2D context is available, the program draws a rectangle
on the context the same size as the
BufferedImage. Then it
fills the rectangle with the solid color green.
Draw a red circle on the BufferedImage
Then the program draws a filled circle on the context that just fits inside
the dimensions of the
BufferedImage object. The circle is
filled with the solid color red. The circle covers the green rectangle
leaving some green exposed around the edges.
Draw the BufferedImage on the Frame
Finally, the program draws the
BufferedImage object on the
Frame,
centered on the origin.
The program was tested using JDK 1.2.2 under WinNT Workstation 4.0
This
discussion of dimensions in inches on the screen depends on the method
named getScreenResolution() returning the correct value. However,
the getScreenResolution() method always seems to return 120 on my
computer regardless of the actual screen resolution settings. |
Will discuss in fragments
I will discuss this program in fragments. The controlling class and
the constructor for the GUI class are essentially the same as you have
seen in several previous lessons, so, I won’t repeat that discussion here.
You can view that material in the complete listing of the program at the
end of the lesson.
All of the interesting action takes place in the overridden paint()
method, so I will begin the discussion there.
Overridden paint() method
The beginning portions of the overridden
paint() method should be
familiar to you by now as well. So, I am going to let the comments
in the
Figure 1 speak for themselves.
Get a BufferedImage object
Figure 2 invokes the
createImage()
method on the
Frame (this) to get a reference to an object of type
Image.
The
Image is a square, three inches on each side.
This reference is immediately downcast to BufferedImage and stored
in a reference variable of type BufferedImage named bufImg.
Get a Graphics2D context on the BufferedImage
Figure 3 invokes the
createGraphics()
method on the
BufferedImage to get a
Graphics2D context on
the
BufferedImage.
Once I have the Graphics2D context, I can draw pictures on the
BufferedImage
just as though I am drawing on the screen.
Draw a green square on the BufferedImage
Figure 4 uses familiar code to draw a square
on the
BufferedImage. The square is the same size as the
BufferedImage,
and is filled with solid green color.
Draw a red circle on the BufferedImage
Continuing with familiar code,
Figure 5
draws a filled red circle on the
BufferedImage. This circle
covers the green square giving us a green square containing a red filled
circle.
Render the BufferedImage on the Frame
It is important to understand that up to this point, I have simply been
creating a virtual image in the computer’s memory. Nothing has been
done so far to cause this image to be visible to a human observer.
One common use of BufferedImage is to build virtual images in
memory and to blast them to the screen as quickly as possible to produce
smooth animation. This can sometimes eliminate the flicker that often
occurs when we attempt to produce animation by drawing directly on the
screen.
In this case, all I want to do is to see the image that I have constructed
in the computer’s memory. I accomplish that in Figure
6, which invokes the drawImage() method of the Graphics2D
class to render the BufferedImage on the screen.
A red filled circle on a green square
If you compile and run this program, you should see a filled red circle
on a green square. The outermost edges of the circle coincide with
the edges of the square. If the actual resolution of your screen
is given by
ds, the size of the square should be three inches on
each side.
How does this differ from previous programs
Although it may not be obvious from the visual presentation, this is significantly
different from the previous programs that have drawn circles on the screen
and filled them.
This program did not draw a circle on the screen. Rather, the
circle was drawn and filled in the computer’s memory and transferred to
the screen in the final rendering process.
Why go to all of this trouble
This does seem like a hard way to get a filled red circle on the screen.
However, the purpose of this program was not to display a filled red circle
on the screen. The purpose was to show you how to create a
BufferedImage
under program control and to draw something on that
BufferedImage.
The rendering of the BufferedImage on the screen was simply a
little icing on the cake.
The next sample program will need a BufferedImage object, and
you now know how to create one without the need for an external file containing
an image.
You can view a complete listing of the program at the end of the lesson.
Sample Program PaintTexture01
This program illustrates the use of a
TexturePaint object to fill
a relatively large circle with small tiled instances of a
BufferedImage
object.
A significantly reduced screen shot of the output of this program is
shown below.
The BufferedImage object has a red filled circle on a green square
the same as in the previous program.
The program uses code to create the BufferedImage object to avoid
the need to provide an auxiliary image file to accompany this lesson.
As usual, the GUI is a Frame object
The program draws a four-inch by four-inch
Frame on the screen.
It translates the origin to the center of the
Frame. Then
it draws a pair of X and Y-axes centered on the new origin.
Then it fills a two-inch diameter circle with small tiled versions of
the BufferedImage object described above.
Finally, the program draws the filled two-inch diameter circle on the
Frame,
centered on the origin.
The program was tested using JDK 1.2.2 under WinNT Workstation 4.0
This
discussion of dimensions in inches on the screen depends on the method
named getScreenResolution() returning the correct value. However,
the getScreenResolution() method always seems to return 120 on my
computer regardless of the actual screen resolution settings. |
Will discuss in fragments
I will discuss this program in fragments. The controlling class and
the constructor for the GUI class are essentially the same as you have
seen in several previous lessons, so, I won’t repeat that discussion here.
You can view that material in the complete listing of the program at the
end of the lesson.
All of the interesting action takes place in the overridden paint()
method, so I will begin the discussion there.
Overridden paint() method
The beginning portions of the overridden
paint() method should be
familiar to you by now as well. So, I am going to let the comments
in
Figure 7 speak for themselves.
The main Shape object
Figure 8 instantiates the main
Shape
object. This is a circle, two inches in diameter. This circle
will be filled with a tile pattern constructed from the
BufferedImage
object, which is a red ball on a green square background.
The interesting part
That brings us to the interesting part.
Figure
9 declares and initializes a
double variable named
tileSize
that will be used to establish the size of the tiles, in inches, used to
fill the circle.
Controlling the size of the tiles
This variable is used to instantiate a new
Rectangle2D.Double object
that is a square, 0.25 inches on each side. As we will see later,
the
BufferedImage object will be automatically scaled to fit this
rectangle, and a large number of such rectangles will be used to fill the
circle.
The TexturePaint class
At this point, we need to learn a little more about the
TexturePaint
class. This is what Sun has to say about the constructor for this
class.
public
TexturePaint(
BufferedImage txtr,
Rectangle2D anchor)
Constructs a TexturePaint object.
Parameters:
-
txtr - the BufferedImage object with the texture used for painting
-
anchor - the Rectangle2D in user space used to anchor and replicate the
texture
|
As you can see, the constructor takes two parameters. The first
parameter is a reference to a BufferedImage object that will be
used to produce the tiles to fill the Shape.
The second parameter is a rectangle that is used to anchor and replicate
the texture. Sun has this to say about the actual process of filling
the Shape.
“Texture
is computed for locations in the device space by conceptually replicating
the specified Rectangle2D infinitely in all directions in user space
and mapping the BufferedImage to each replicated Rectangle2D.” |
The bottom line
The bottom line is that the circle is filled with rectangles of the specified
size, and the
BufferedImage is scaled down and drawn automatically
on each rectangle.
Originally, I created a small BufferedImage objectthat was the
same size as the rectangle. When I did that, I got a very ragged
looking circle in the BufferedImage. I found that the results
were much better when I created a fairly large BufferedImage and
allowed it to be scaled down automatically to fit the rectangle.
Instantiate a TexturePaint object
Figure 10 instantiates the new
TexturePaint
object, passing the
BufferedImage and the anchor rectangle to the
constructor as parameters.
The BufferedImage is actually produced by invoking a method named
getBufferedImage()
that I will discuss shortly.
There is nothing special about the getBufferedImage() method.
It is simply a convenience method that I wrote to separate the code that
generates the BufferedImage from the code that uses the BufferedImage.
Filling and drawing the circle
Figure 11 shows the three statements that
you have come to expect for filling and drawing
Shape objects.
Compile and run the program
If you compile and run this program, you should see a two-inch diameter
circle centered on a
Frame. The circle is filled with small
green squares, and each green square contains a red filled circle.
The getBufferedImage() method
Figure 12 contains the entire method named
getBufferedImage().
As mentioned earlier, this is a convenience method that I wrote to separate
the code that creates the BufferedImage from the code that uses
it. The method creates and returns a BufferedImage object
consisting of a filled red circle on a green square background.
This is essentially the same code as in the previous program, except
that this time, instead of rendering the BufferedImage on the screen,
the method simply returns a reference to the BufferedImage object.
Since there is nothing new in the fragment, I won’t discuss it further.
Summary
In this lesson, I have shown you how to create a
BufferedImage object
under program control without the requirement for an external image file.
I have also shown you how to use a BufferedImage object to fill
a Shape with a texture produced by tiling the BufferedImage.
Complete Program Listings
A complete listing of both programs is provided in
Figure
13 and
Figure 14.
Copyright 2000, Richard G. Baldwin. Reproduction in whole or in
part in any form or medium without express written permission from
Richard Baldwin is prohibited.
About the author
Richard Baldwin
is a college professor and private consultant whose primary focus is a
combination of Java and XML. In addition to the many platform-independent
benefits of Java applications, he believes that a combination of Java and
XML will become the primary driving force in the delivery of structured
information on the Web.
Richard has participated in numerous consulting projects involving
Java, XML, or a combination of the two. He frequently provides onsite
Java and/or XML training at the high-tech companies located in and around
Austin, Texas. He is the author of Baldwin's Java Programming Tutorials,
which has gained a worldwide following among experienced and aspiring Java
programmers. He has also published articles on Java Programming in Java
Pro magazine.
Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.
-end-