Java Programming, Lecture Notes # 320
March 20, 2000
Must also understand other classes
I also explained that without understanding the behavior of other classes and interfaces, it is not possible to fully understand the inner workings of the Graphics2D class.
Throughout this series of lessons, I have been providing you with information and sample programs designed to help you understand the various classes and interfaces that are necessary for an understanding of the Graphics2D class.
Two ways to achieve transpanency
There are at least two different ways to achieve transparency in Java 2D. One way is to use new constructors for the Color class that allow you to create solid colors with a specified degree of transparency. I will discuss that approach in a subsequent lesson.
A more generl approach
A second, and possibly more general approach, is to make use of an object that implement the Composite interface, passing a reference to that object to the setComposite() method of the Graphics2D class.
The Composite interface
This lesson and the next are designed to give you an understanding of the Composite interface, with particular emphasis on transparency.
According to Sun,
“The CompositeContext interface defines the encapsulated and optimized environment for a compositing operation. CompositeContext objects maintain state for compositing operations. In a multi-threaded environment, several contexts can exist simultaneously for a single Composite object.” |
“The Composite interface, along with CompositeContext,
defines the methods to compose a draw primitive with the underlying graphics
area.
After the Composite is set in the Graphics2D context, it combines a shape, text, or an image being rendered with the colors that have already been rendered according to pre-defined rules.” |
We will set the composite property
In other words, before drawing onto a Graphics2D object, we will invoke the setComposite() method to set the composite property of the Graphics2D object.
We will pass a reference to an object that implements the Composite interface as a parameter.
This Composite object will control the way in which the colors of overlapping pixels are rendered.
“This AlphaComposite class implements the basic alpha compositing
rules for combining source and destination pixels to achieve blending and
transparency effects with graphics and images.
The rules implemented by this class are a subset of the Porter-Duff rules described in T. Porter and T. Duff, "Compositing Digital Images", SIGGRAPH 84, 253-259. If any input does not have an alpha channel, an alpha value of 1.0, which is completely opaque, is assumed for all pixels. A constant alpha value can also be specified to be multiplied with the alpha value of the source pixels. The following abbreviations are used in the description of the rules:
Cd = Cs*Fs + Cd*Fd Ad = As*Fs + Ad*Fd where Fs and Fd are specified by each rule. The above equations assume that both source and destination pixels have the color components premultiplied by the alpha component. Similarly, the equations expressed in the definitions of compositing rules below assume premultiplied alpha. For performance reasons, it is preferable that Rasters passed to the compose method of a CompositeContext object created by the AlphaComposite class have premultiplied data. If either source or destination Rasters are not premultiplied, however, appropriate conversions are performed before and after the compositing operation. The alpha resulting from the compositing operation is stored in the destination if the destination has an alpha channel. Otherwise, the resulting color is divided by the resulting alpha before being stored in the destination and the alpha is discarded. If the alpha value is 0.0, the color values are set to 0.0.” |
A fairly complex topic
This can be a fairly complex topic. An object of the AlphaComposite class can be used to implement any one of about eight different compositing rules.
As you can see from the above description, the manner in which the color components of the destination pixel are determined depend on the rule being applied (a single AlphaComposite object can apply only one rule).
Flanagan explains the rules
You can read about the different rules in Java Foundation Classes in a Nutshell, by David Flanagan.
In these lessons, I will illustrate only one of the compositing rules:
the rule known as SRC_OVER. Here is what Flanagan has to say
about this rule.
“By far the most commonly used compositing rule. It draws the source on top of the destination. The source and destination are combined based on the transparency of the source. Where the source is opaque, it replaces the destination. Where the source is transparent, the destination is unchanged. Where the source is translucent, the source and destination colors are combined so that some of the destination color shows through the translucent source.” |
SRC_OVER as per Sun
Here is Sun’s formal definition of this rule from the JDK 1.2.2 documentation.
SRC_OVER
public static final int SRC_OVER Porter-Duff Source Over Destination rule. The source is composited over the destination. Fs = 1 and Fd = (1-As), thus: Cd = Cs + Cd*(1-As) Ad = As + Ad*(1-As) |
SRC_OVER as per Flanagan
However, Sun’s explanation doesn’t agree with Flanagan’s explanation.
According to Flanagan, the equations for this rule should be written as
follows:
Fs = As and Fd = (1-As), thus:
Cd = Cs*As + Cd*(1-As) |
In these lessons, the thing that will be most obvious will be the color resulting from overlapping two or more geometric figures with varying degrees of transparency.
Which is correct?
Although I’m not qualified to tell you which of the two explanations is the correct one, experimental results seem to favor Flanagan’s explanation over Sun’s explanation.
If As is set to zero...
For example, if As is set to zero, Sun’s explanation would cause the destination color, Cd, to contain equal contributions of the source color and the destination color.
Flanagan’s explanation for this case would eliminate all of the source color from the final destination color, which agrees with experimental results.
May be a matter of interpretation
However, these apparent discrepancies may simply be a matter of interpretation of the various terms used in the equations and the explanations.
public static AlphaComposite
getInstance( int rule, float alpha) Creates an AlphaComposite object with the specified rule and the constant alpha to multiply with the alpha of the source. The source is multiplied with the specified alpha before being composited with the destination. Parameters:
|
How do I specify the rule?
You specify the rule by passing an int value given by one of the symbolic constants of the AlphaComposite class, such as SRC_OVER described earlier.
public abstract void
setComposite(Composite comp) Sets the Composite for the Graphics2D context. The Composite is used in all drawing methods such as drawImage, drawString, draw, and fill. It specifies how new pixels are to be combined with the existing pixels on the graphics device during the rendering process. Parameters:
|
You could define your own class
The required parameter is a reference to any object that implements the Composite interface, meaning that you could define your own class to implement this interface.
In this lesson, I elected to make use of the existing AlphaComposite class described above.
A screen shot of the output
In case you are unable to compile and execute the program, a screen shot of the output follows. Note, however that this screen shot has been reduced to about 70-percent of its original size in pixels, so some of the quality has been lost in the process.
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.
A large circle
After drawing the X and Y-axes, the program draws a circle with a thick border centered on the origin. This circle is used later to provide visual cues relative to transparency.
Transparent ellipses
After the large circle is drawn, three ellipses are drawn on top of one another in each quadrant.
Each ellipse has a common center, and is rotated by sixty degrees relative to the ellipse beneath it.
The red ellipse is on the bottom of the stack, the green ellipse is in the center, and the blue ellipse is on the top of the stack.
The different ellipses are given various transparency values in the different quadrants to illustrate the effect of the alpha parameter to the setComposite() method.
Transparency by quadrant
Here is the transparency given to each of the ellipses in the different
quadrants.
TRANSPARENCY
Upper-left quadrant No transparency Upper-right quadrant
Lower-left quadrant
Lower-right quadrant
|
As you can see from the information given above, the red ellipse is opaque in all four quadrants. As a result, the large black circle doesn’t show through the red ellipse in any of the quadrants.
Upper-left quadrant
All three ellipses are opaque in the upper-left quadrant, so nothing shows through, and the stacking order of the ellipses is pretty obvious.
Green and blue ellipses become transparent
The green and blue ellipses are made progressively more transparent as you move through the other three quadrants. As a result, you can “see through” the green and blue ellipses and see the geometric figures that lie beneath them (the other ellipses and the large black circle).
Upper-right quadrant
In this quadrant, the blue ellipse is transparent, but the green ellipse and the red ellipse are opaque. Neither the large circle nor the red ellipse can be seen through the green ellipse.
However, both the green and red ellipses show through the blue ellipse in the upper-right quadrant.
Lower-left quadrant
In the lower-left quadrant, both the green and blue ellipses are transparent to some degree, with the blue ellipse being the more transparent of the two.
Both the green and the red ellipses show through the blue ellipse, which is on the top of the stack.
Both the red ellipse and the large circle show through the green ellipse.
The large circle would also show through the blue ellipse as well except that the red ellipse, which is opaque, hides the circle in the area of the blue ellipse.
Lower-right quadrant
In the lower-right quadrant, both the green and blue ellipses are ninety-percent transparent, and the large circle shows through the blue ellipse. Again, the red ellipse, which is opaque, hides the circle.
The lower-right quadrant also produces an optical illusion. On my screen, it looks like the red ellipse is partially transparent with the green and blue ellipses showing through the red ellipse. However, that is not the case. The red ellipse is opaque in this quadrant, as evidenced by the fact that the large circle does not show through the red ellipse. The opaque red ellipse is still on the bottom of the stack, and the transparent blue ellipse is still on the top of the stack.
Illustrates rotation and translation
Although this isn’t the primary purpose of this lesson, the lesson also provides a good illustration of the benefits of rotation and translation.
As we will see when we examine the code, the task of rotating the ellipses relative to each other and the task of translating them into the various quadrants was made much easier (even possible) through the use of the AffineTransform to rotate and translate the ellipses.
The normal caveat regarding inches
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 1 speak for themselves.
The large circle
Figure 2 draws the large circle with a border width of 0.1 inches. There is nothing new here, so I won’t provide an explanation.
An ellipse reference variable
Figure 3 simply declares a reference variable of the class Ellipse2D.Double. This reference variable will be used repeatedly in subsequent code for the instantiation of ellipse objects.
Now things get interesting
At this point, things get interesting. I need to draw the three filled ellipses in the upper-left quadrant. One way to do this (the hard way) would be to calculate the coordinates of the ellipses in the quadrant and define them according to those coordinates.
The easier way is to translate the origin to the center of what was previously the upper-left quadrant, and to define the bounding rectangle for the ellipses centered on the new origin.
Figure 4 translates the origin to the center of what was previously the upper-left quadrant. After this statement is executed, any geometric figure that is drawn centered on the origin will actually be rendered in the center of what was earlier the upper-left quadrant.
Opaque red ellipse
The next several fragments draw and fill a red opaque ellipse centered on the new origin.
The code in Figure 5 has been covered in previous lessons, so I won’t discuss it further in this lesson.
setComposite() and AlphaComposite
Figure 6 shows the use of the setComposite() method and the AlphaComposite class.
An understanding of this single statement is pretty much the heart of this entire lesson.
As mentioned earlier, the parameter to the setComposite() method must be a reference to an object that implements the Composite interface. This requirement can be satisfied by passing a reference to an object of the AlphaComposite class.
Also, as mentioned earlier, such an object can be obtained only by invoking the factory method, named getInstance() of the AlphaComposite class.
Parameters to getInstance()
The getInstance method requires two parameters. The first parameter is an int whose value specifies the compositing rule that will be used. The second is the alpha value that will be used with that rule to establish how compositing will be accomplished.
This fragment specifies the SRC_OVER rule by passing that symbolic constant from the AlphaComposite class. You might want to use this program to experiment with the other rules of the AlphaComposite class and view the results.
Specifying transparency
This fragment also specifies that the source object will be opaque.
For this compositing rule, an alpha parameter value of 1.0f specifies opaque while a value of 0.0f specifies total transparency.
It is primarily the value of the alpha parameter that will be varied throughout the remainder of this program to achieve the desired results.
Rendering the red ellipse
Figure 7 simply renders the ellipse according to the paint and composite properties previously established. There is nothing new here.
Green opaque ellipse at sixty degrees
To some extent, the use of translation above was for convenience. I could have placed the red ellipse in the upper-left quadrant by specifying a bounding rectangle at that location.
However, the code in Figure 8 will render a green opaque ellipse rotated by an angle of sixty degrees. This rotational transformation is not simply for convenience. I don’t know of any other way to draw an ellipse whose major axis is neither horizontal nor vertical, except for use of the GeneralPath class to construct the ellipse piecemeal. I certainly don’t want to create the ellipse using GeneralPath.
The code in Figure 8
Recall from the earlier lesson on the use of the AffineTransform that successive calls to the rotate() method produce cumulative angles of rotation. Therefore, in order to rotate the blue ellipse by a total of 120 degrees, the following fragment invokes another sixty-degree rotation.
Otherwise, the code in Figure 9 is straightforward, producing an opaque blue ellipse in the upper-left quadrant rotated by a total of 120 degrees relative to the red ellipse.
As mentioned earlier, the blue ellipse is on the top of the stack, the red ellipse is on the bottom of the stack, and the green ellipse is in the middle.
Rotation can produce complexity
The combination of rotation and translation can produce very complicated results. The objective of the next few fragments is to produce ellipses having different transparency values in the upper-right quadrant.
Translate origin to upper-right quadrant
To avoid the complexity mentioned above, before translating the origin to the center of the original upper-right quadrant, the code in Figure 10 reverses the rotation previously imposed by the code discussed above.
Note that the angle of rotation is negative, and amount of rotation is sixty degrees multiplied by two.
Following reversal of the previous rotation, the origin is translated. In this case, the new origin is translated two inches to the right of the previous origin, but at the same vertical position. This places the new origin in the center of what was originally the upper-right quadrant.
Repetitive code
From this point forward, the code becomes very repetitive. Therefore, I am going to show only that code that distinguishes the rendering of the ellipses in one quadrant from the rendering in the other quadrants, with respect to transparency.
You can view the entire program at the end of the lesson.
Upper-right quadrant
Figure 11 shows the invocation of the setComposite() methods for the red, green, and blue ellipses respectively.
As you can see, the first two are opaque (alpha equals 1.0f) while the blue ellipse is fifty-percent transparent (alpha equals 0.5f).
Lower-left quadrant
Figure 12 shows the invocation of the setComposite() methods for the red, green, and blue ellipses respectively in the lower-left quadrant.
The red ellipse is opaque (alpha equals 1.0f). The green ellipse is fifty-percent transparent (alpha equals 0.5f), while the blue ellipse is ninety-percent transparent (alpha equals 0.1f).
Lower-right quadrant
Figure 13 shows that the red ellipse is opaque (alpha equals 1.0f). The green and blue ellipses are ninety-percent transparent (alpha equals 0.1f).
In addition, you have seen some additional uses for the translate and rotate transforms.
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-