Effects
Another exciting aspect of Project Scene Graph is its support for various visual effects: reflections, drop shadows, glows, Gaussian blurs, sepia tones, and so on. These effects can improve an otherwise dull-looking user interface. For example, Listing 1's use of the drop-shadow effect makes the white text stand out more from the reddish background (refer to Figure 1).
Project Scene Graph supports visual effects via the abstract com.sun.scenario.effect.Effect class and its subclasses, which are also located in the com.sun.scenario.effect package. Additional classes that are relevant to the PhongLighting subclass are located in the com.sun.scenario.effect.light package. Here are a few of the many effect subclasses:
- Blend: Blend two inputs together using the current blending mode, which is an instance of the Blend.Mode enumeration.
- Crop: Return a cropped version of the input.
- DropShadow: Render a shadow of the input behind the input.
- GaussianBlur: Blur the input according to a Gaussian convolution kernel.
- Glow: Make the input glow.
- PhongLighting: Apply diffuse and specular lighting to the input using a positionable light source.
- Reflection: Render a reflected version of the input below the input.
- SepiaTone: Render a sepia tone effect that's similar to antique photographs.
Many of these classes provide configurable properties. For example, DropShadow provides shadow color, offset, and radius properties, which are configured via public void setColor(java.awt.Color color), public void setOffsetX(int xoff), public void setOffsetY(int yoff), and public void setRadius(float radius) methods (and accessed via equivalent getter methods).
It's possible to chain certain visual effects to other visual effects, in which the output from one or more Effect instances becomes the input to another instance. Chaining is accomplished by passing an Effect instance to a constructor or method with an Effect parameter. Examples: public Crop(Effect input) and public void setInput(Effect input).
Whether or not you chain Effect instances, you cannot add them directly to a scene graph. Instead, you must wrap them inside com.sun.scenario.scenegraph.SGEffect instances by invoking this class's public void setEffect(Effect effect) method (the companion public Effect getEffect() method returns the Effect) and then add the SGEffect instances to the scene graph.
Reflecting an Image
Many web pages and the user interfaces of software programs such as media players feature reflections to add a degree of professionalism. It's common to reflect an image, with the reflection appearing immediately below (rather than side by side, for example), and this is exactly what Project Scene Graph's Reflection class accomplishes. Listing 4's ReflectedImage.java source code demonstrates this class.
Listing 4 ReflectedImage.java
// ReflectedImage.java import java.awt.*; import java.awt.geom.*; import java.awt.image.*; import java.io.*; import java.util.*; import javax.imageio.*; import javax.swing.*; import com.sun.scenario.effect.*; import com.sun.scenario.scenegraph.*; public class ReflectedImage { final static int WIDTH = 600; final static int HEIGHT = 480; final static int IMAGE_TOP = 30; final static int IMAGE_SEP = 15; public static void main (String [] args) { Runnable r = new Runnable () { public void run () { createAndShowGUI (); } }; EventQueue.invokeLater (r); } public static void createAndShowGUI () { JFrame f = new JFrame ("Reflected Image"); f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); // JSGPanel is a JComponent that renders a scene graph. final JSGPanel panel = new JSGPanel (); panel.setPreferredSize (new Dimension (WIDTH, HEIGHT)); // The scene consists of a grouping of two nodes -- a background // rectangle that renders a gradient, and an effect whose transformed // child is an image. SGGroup sceneNode = new SGGroup (); sceneNode.setID ("sceneNode: SGGroup"); // Create the background shape node. Because the node's rectangular // shape will be filled with a gradient, it doesn't make sense to also // render an outline, so set the shape mode to FILL. SGShape backgroundNode = new SGShape (); backgroundNode.setID ("backgroundNode: SGShape"); backgroundNode.setMode (SGShape.Mode.FILL); // Add the background node first, so it will appear behind the reflected // image. sceneNode.add (backgroundNode); // Create the image node. SGImage imageNode = new SGImage (); // Load next image. try { BufferedImage origImage = ImageIO.read (new File ("flower.jpg")); imageNode.setImage (origImage); imageNode.setID ("imageNode: SGImage"); } catch (Exception e) { System.err.println ("Image loading error: "+e); } // Center the image horizontally via a translation node, which is the // parent of the image node. Rectangle2D bounds = imageNode.getBounds (); SGTransform.Translate imageTNode; imageTNode = SGTransform.createTranslation (-bounds.getX ()+ (WIDTH- bounds.getWidth ())/2.0, IMAGE_TOP, imageNode); imageTNode.setID ("imageTNode: SGTransform.Translate"); // Create a reflection effect node as the parent of the translation // node. SGEffect effectNode = new SGEffect (); effectNode.setID ("effectNode: SGEffect"); Reflection reflection = new Reflection (); reflection.setTopOffset (IMAGE_SEP); effectNode.setEffect (reflection); effectNode.setChild (imageTNode); // Add the effect node last, so it will appear in front of the gradient // background. sceneNode.add (effectNode); // Establish the scene. panel.setScene (sceneNode); // Dump the scene graph hierarchy to the standard output. dump (panel.getScene (), 0); // Install the panel into the Swing hierarchy, pack it to its preferred // size, and don't let it be resized. f.setContentPane (panel); f.pack (); Insets insetsa = f.getInsets (); f.setResizable (false); Insets insetsb = f.getInsets (); // On some platforms (Windows XP SP2, for one), passing false to // setResizable shrinks the frame window's insets. The horizontal and // vertical differences between the before/after inset values must be // taken into account when creating the background node and its // gradient. Otherwise, there will be a small region of empty space on // the right and bottom of the main window. int diffleft = Math.abs (insetsa.left-insetsb.left); int diffright = Math.abs (insetsa.right-insetsb.right); int difftop = Math.abs (insetsa.top-insetsb.top); int diffbottom = Math.abs (insetsa.bottom-insetsb.bottom); backgroundNode.setShape (new Rectangle (0 ,0, WIDTH+diffleft+diffright, HEIGHT+difftop+diffbottom)); backgroundNode.setFillPaint (new GradientPaint (0f, 0f, Color.BLACK, 0f, HEIGHT+difftop+ diffbottom, Color.DARK_GRAY)); // Center and display the frame window on the screen. f.setLocationRelativeTo (null); f.setVisible (true); } public static void dump (SGNode node, int level) { for (int i = 0; i < level; i++) System.out.print (" "); System.out.println (node.getID ()); if (node instanceof SGParent) { java.util.List<SGNode> children = ((SGParent) node).getChildren (); Iterator<SGNode> it = children.iterator (); while (it.hasNext () dump (it.next (), level+1); } } }
Listing 4 uses the Reflection public void setTopOffset(float topOffset) method to specify the distance between the bottom of the original image and the top of its reflection—the default and minimum topOffset values are 0.0f. The companion public float getTopOffset() method returns this value. Additional property methods for configuring a reflection are as follows:
- public void setBottomOpacity(float bottomOpacity): Set the opacity of the reflection's bottom extreme. The bottomOpacity value ranges from 0.0f through 1.0f (an IllegalArgumentException is thrown if this value lies outside this range) and defaults to 0.0f (transparent). The companion public float getBottomOpacity() method returns this value.
- public void setFraction(float fraction): Set the fraction of the image that is visible in the reflection. For example, 0.5f means that the reflection reveals only the bottom half of the image. The fraction value ranges from 0.0f through 1.0f (an IllegalArgumentException is thrown if this value lies outside this range), with 0.75f being the default. The companion public float getFraction() method returns this value.
- public void setTopOpacity(float topOpacity): Set the opacity of the reflection's top extreme. The topOpacity value ranges from 0.0f through 1.0f (an IllegalArgumentException is thrown if this value lies outside this range), and defaults to 0.5f. The companion public float getTopOpacity() method returns this value.
Compile the source code via javac -cp Scenario-0.6.jar ReflectedImage.java; run the application via java -cp Scenario-0.6.jar;. ReflectedImage. Figure 4 reveals the resulting user interface.
Figure 4 The image is centered horizontally in front of a black-to-dark gray gradient background.
This application also sends the following nodes hierarchy to the standard output—a group to store the background and effect nodes, with the effect's translate and image children (the background is rendered first, followed by the translated and reflected image):
sceneNode: SGGroup backgroundNode: SGShape effectNode: SGEffect imageTNode: SGTransform.Translate imageNode: SGImage