Wikipedia's Embossing entry defines embossing as "the process of creating a three-dimensional image or design in paper and other ductile materials." A few years back, I presented an algorithm for embossing images in my Java Tech: Image Embossing article.
Subsequent to its revelation in the aforementioned article, I presented an improved version of this algorithm in my Java Tech: Process Images with Imagician article. Figure 1 excerpts from that article an image showing the metallic-looking, three-dimensional result of applying the algorithm to a non-embossed image.
Figure 1: An embossed flower. (Click to enlarge.)
JavaFX offers a nice assortment of effects, along with the ability to create new effects by chaining some of them together, but omits an emboss effect. I recently corrected this oversight by making my emboss algorithm available to JavaFX 1.1 and 1.1.1 nodes via a new javafx.scene.effect.Effect subclass (Emboss) and supporting Java code. I present Emboss and its Java infrastructure in this blog post.
Because Emboss's implementation relies on JavaFX's undocumented Java runtime infrastructure, it's highly unlikely that Emboss will work with future JavaFX releases. Although I'll try to update the implementation as needed, I'd prefer that Sun's next JavaFX release include an emboss effect (or at least some kind of documented plugin-based architecture that lets Java developers easily introduce their own effects).
I found it easier to first code the JavaFX side of the emboss effect, and then code the Java side. After decompiling JavaFX's Reflection.class and some of its other effects classfiles, to discover how their underlying Java code connects to the rest of JavaFX's effects infrastructure, I wrote Listing 1's Emboss.fx source code.
Listing 1: Emboss.fx
/*
* Emboss.fx
*/
package embosseffectdemo;
import javafx.scene.effect.Effect;
public class Emboss extends Effect
{
var embosser = new Embosser ();
override function impl_getImpl ()
{
embosser
}
public var input: Effect on replace
{
embosser.setInput (input.impl_getImpl ())
}
}
The Emboss class relies on javafx.scene.effect.Effect's public abstract impl_getImpl(): com.sun.scenario.effect.Effect function to connect the emboss effect's JavaFX side to its Java implementation. Because this function can be called multiple times, it must return a reference to a pre-created instance of the Java implementation class, which Listing 2 describes.
Listing 2: Embosser.java
/*
* Embosser.java
*/
package embosseffectdemo;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import com.sun.scenario.effect.*;
public class Embosser extends FilterEffect
{
public Embosser ()
{
this (DefaultInput);
}
public Embosser (Effect input)
{
super (input);
}
public final Effect getInput ()
{
return ((Effect) getInputs ().get (0));
}
public void setInput (Effect input)
{
setInput (0, input);
}
@Override
public ImageData filterImageDatas (FilterContext fctx,
AffineTransform transform,
ImageData [] inputDatas)
{
Rectangle inputbounds = inputDatas [0].getBounds ();
int srcW = inputbounds.width;
int srcH = inputbounds.height;
Image imDst = getCompatibleImage (fctx, srcW, srcH);
if ((!(inputDatas [0].validate (fctx))) || (imDst == null))
return new ImageData (fctx, null, inputbounds);
Image imSrc = inputDatas [0].getImage ();
BufferedImage biTmp = new BufferedImage (srcW, srcH,
BufferedImage.TYPE_INT_ARGB);
Graphics g = biTmp.getGraphics ();
g.drawImage (imSrc, 0, 0, null);
g.dispose ();
int [] srcPixels = biTmp.getRGB (0, 0, srcW, srcH, null, 0, srcW);
for (int i = 0, offset = 0; i < srcH; i++)
for (int j = 0; j < srcW; j++, offset++)
{
int current = srcPixels [offset];
int upperLeft = 0;
if (i > 0 && j > 0)
upperLeft = srcPixels [offset-srcW-1];
int opacity = (current >> 24)&255;
int rDiff = ((current >> 16)&255)-((upperLeft >> 16)&255);
int gDiff = ((current >> 8)&255)-((upperLeft >> 8)&255);
int bDiff = (current & 255)-(upperLeft & 255);
int diff = rDiff;
if (Math.abs (gDiff) > Math.abs (diff)) diff = gDiff;
if (Math.abs (bDiff) > Math.abs (diff)) diff = bDiff;
int grayLevel = Math.max (Math.min (128+diff, 255), 0);
biTmp.setRGB (j, i, (opacity << 24)+(grayLevel << 16)+
(grayLevel << 8)+grayLevel);
}
srcPixels = null;
g = imDst.getGraphics ();
g.drawImage (biTmp, 0, 0, null);
g.dispose ();
Rectangle newbounds = new Rectangle (inputbounds.x, inputbounds.y, srcW,
srcH);
return new ImageData (fctx, imDst, newbounds);
}
@Override
public boolean operatesInUserSpace ()
{
return true;
}
@Override
public AccelType getAccelType (FilterContext paramFilterContext)
{
return AccelType.INTRINSIC;
}
}
Embosser.java is a good example of "monkey see, monkey do." I arrived at this code after studying decompiled output from JavaFX's Reflection class (the version derived from Reflection.java, not from Reflection.fx), and decided to mimic this class as much as possible.
The filterImageDatas() method is key to Embosser. After obtaining source and destination Images, it creates a BufferedImage intermediary, copies the source Image to the buffer, embosses the buffer's content, and copies the buffer to the destination Image. A BufferedImage is needed because the source and destination Images are sun.java2d.pipe.hw.AccelTypedVolatileImage instances.
After creating Emboss.fx and Embosser.java, I needed a JavaFX application to test the emboss effect. I created Listing 3's Main.fx source code, as part of an EmbossEffectDemo project, to test unscaled and scaled versions of an Embossed image, and to test Emboss's effect-chaining support (via its input attribute).
Listing 3: Main.fx (from an EmbossEffectDemo project)
/*
* Main.fx
*/
package embosseffectdemo;
import javafx.scene.Scene;
import javafx.scene.effect.Reflection;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
Stage
{
title: "Emboss Effect Demo"
width: 823
height: 430
scene: Scene
{
fill: Color.BLACK
content: HBox
{
spacing: 5.0
var imageViewRef1: ImageView
var imageViewRef2: ImageView
content:
[
ImageView
{
image: Image
{
url: "{__DIR__}bunny.jpg"
}
effect: Reflection
{
input: SepiaTone
{
level: 1.0
}
fraction: 1.0
topOffset: 5.0
}
}
ImageView
{
image: Image
{
url: "{__DIR__}bunny.jpg"
}
effect: SepiaTone
{
level: 1.0
input: Reflection
{
fraction: 1.0
topOffset: 5.0
}
}
}
imageViewRef1 = ImageView
{
image: Image
{
url: "{__DIR__}bunny.jpg"
}
effect: Reflection
{
input: Emboss {}
fraction: 1.0
topOffset: 5.0
}
onMouseEntered: function (me: MouseEvent): Void
{
imageViewRef2.scaleX = 0.0;
imageViewRef2.scaleY = 0.0;
imageViewRef1.transforms = Scale { x: 2.0 y: 2.0 }
}
onMouseExited: function (me: MouseEvent): Void
{
imageViewRef1.transforms = null;
imageViewRef2.scaleX = 1.0;
imageViewRef2.scaleY = 1.0;
}
}
imageViewRef2 = ImageView
{
image: Image
{
url: "{__DIR__}bunny.jpg"
}
effect: Emboss
{
input: Reflection
{
fraction: 1.0
topOffset: 5.0
}
}
}
]
}
}
}
Listing 3 renders a row of four instances of the same underlying image. The first two renderings show this image by first applying SepiaTone followed by Reflection, and then by applying Reflection followed by SepiaTone. The last two renderings, where Emboss replaces SepiaTone, employ the same approach.
Figure 2: Sepia toned and embossed reflected images. (Click to enlarge.)
Although each pair of renderings looks identical, look closer at the reflections and you'll notice subtle differences. Order matters! Interestingly, if you replace SepiaTone with Glow, you'll notice a big difference: Applying Glow before Reflection causes the reflection to also glow. However, the reflection doesn't glow when you reverse the order of these effects.
It's possible to transform an embossed node without screwing up the embossed appearance. For example, you can scale the third-from-the-left bunny image by a factor of two by moving the mouse over this image. (The image reverts to its original size when you move the mouse off of the image.) Figure 3 shows the resulting scaled-up image.
Figure 3: A giant bunny. (Click to enlarge.)
Before you can build and run this application, you need to add Scenario.jar (which contains com.sun.scenario.effect.Effect, com.sun.scenario.effect.FilterEffect, and other effects infrastructure classes) to the EmbossEffectDemo script's classpath. Assuming that you're using NetBeans IDE 6.5, complete the following steps to accomplish this task:
Project Properties dialog box via the File menu, or by right-clicking the project name and selecting Properties from the popup menu.
Libraries category and click the resulting pane's Add JAR/Folder button.
Scenario.jar. On my platform, this file locates in the C:\Program Files\NetBeans 6.5\javafx2\javafx-sdk\lib\desktop\ directory.
As with everything user interface-related, you'll want to be sensible about using the emboss effect. However, I couldn't resist trying out this effect with the javafx.scene.media.MediaView class to emboss a movie -- I wanted to visually inspect the effect's performance, and I've never seen an embossed movie. Listing 4 presents the source code to my embossed media player.
Listing 4: Main.fx (from an EmbossedMediaPlayer project)
/*
* Main.fx
*/
package embossedmediaplayer;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
Stage
{
title: "Embossed Media Player"
width: 400
height: 300
var mediaURI = "https://swinghelper.dev.java.net/bin/blog/sam/jfx/movie.avi"
var mediaPlayerRef: MediaPlayer
var sceneRef: Scene
scene: sceneRef = Scene
{
fill: Color.BLACK
var mediaViewRef: MediaView
content: mediaViewRef = MediaView
{
effect: Emboss {}
mediaPlayer: mediaPlayerRef = MediaPlayer
{
media: Media
{
source: mediaURI
}
}
onMouseClicked: function (me: MouseEvent): Void
{
if (mediaPlayerRef.paused)
mediaPlayerRef.play ()
else
mediaPlayerRef.pause ()
}
translateX: bind (sceneRef.width-mediaViewRef.boundsInLocal.width)/2
translateY: bind (sceneRef.height-mediaViewRef.boundsInLocal.height)/2
}
}
onClose: function ()
{
mediaPlayerRef.pause ();
}
}
This simple media player requires that you click the scene to start playing the movie, click again to pause the movie, click yet again to resume the movie, and so on. For convenience, I've hardwired the player to always play the same short space shuttle launch movie located at https://swinghelper.dev.java.net/bin/blog/sam/jfx/movie.avi. Figure 4 reveals one of its frames.
Figure 4: A strange-looking space shuttle launch. (Click to enlarge.)
I've taken liberties with JavaFX's runtime to make my emboss algorithm available as a JavaFX effect. However, I don't advise you to do the same for your own effects because the runtime is undocumented, subject to change, and is more complex than I've shown. It's simply too easy to create buggy effect implementation classes -- I think Embosser is okay. Hopefully, a future JavaFX will facilitate introducing custom effects.
Download a source file: csj42809-src.zip
Like this blog? Subscribe to the CSJ Explorer RSS feed
I recently corrected this
I recently corrected this oversight by making my emboss algorithm available to JavaFX 1.1 and 1.1.1 nodes via a new javafx.scene.effect.Effect subclass (Emboss) and supporting Java code. I present Emboss and its Java infrastructure in this blog post. college degree AND Science Degree
* Activate the Project
* Activate the Project Properties dialog box via the File menu, or by right-clicking the project name and selecting Properties from the popup menu.
* Select this dialog box's Libraries category and click the resulting pane's Add JAR/Folder button.
* Use the resulting file chooser to navigate to Scenario.jar. On my platform, this file locates in the C:\Program Files\NetBeans 6.5\javafx2\javafx-sdk\lib\desktop\ directory.
computer school AND Online Science Degree AND online Bachelor degrees
Post new comment