I try to add emboss effect (also called bump mapped) to a complex clipped image. (Namly a puzzle piece.) This could be done by a per pixel operation, but it is very expensive: the exact form of the clipping is runtime determined, so even the bump map would have to be generated runtime.
The lighting effect of JavaFX offers a similar effect out-of-box:
// Create clipping
pieceClip = createPiece(topEdge, leftEdge, bottomEdge, rightEdge);
pieceClip.setFill(Color.WHITE);
pieceClip.setStroke(null);
// Create clipped image view
imageView.setImage(image);
imageView.setClip(pieceClip);
// Create ambient light
Light.Distant light = new Light.Distant();
light.setAzimuth(-135.0);
// Create lighting effect
Lighting lighting = new Lighting();
lighting.setLight(light);
lighting.setSurfaceScale(4.0);
// Create shape to apply lighting on (the shape is the same as the clipping)
Shape pieceStroke1 = createPiece(topEdge, leftEdge, bottomEdge, rightEdge);
pieceStroke1.setFill(new Color(1, 1, 1, 0.4));
pieceStroke1.setStroke(null);
// Apply lighting
pieceStroke1.setEffect(lighting);
// Add clipped image and lighting overlay to the shape
getChildren().addAll(imageView, pieceStroke1);
This works almost well, the lighting effect:
However, it has a side effect: due to the overlay being white and only semi-transparent, it dims the image (makes its a littly foggy, much less vivid as it was).
I tried to play with Blend (COLOR_BURN, DARKEN, etc.) to restore the image colors, but being not quite at home with these effects, I failed.
How could I reserve my image color saturation, but applying the emboss effect?
I have several idea how to do it, but I have no idea which one would work:
Any idea would be deeply appreciated.
Solution
Apply a ColorAdjust effect to the Lighting effect in an effect chain.
The lighting effect is going to change the appearance and color of the image to some extent no matter what you do (because that is what lighting does), but by applying the additional color adjustment, you can get an image which is recognizably pretty close to the original coloring.
If you need to adjust the shape of the image, snapshot the clipped image and apply the effect to the snapshot if that is what you want, as explained in the answer to: Border-Radius and Shadow on ImageView.
Example
// increase brightness and contrast.
ColorAdjust brightLight = new ColorAdjust(0, 0, .25, 0.25);
// chain in your lighting effect.
brightLight.setInput(lighting);
// apply the chained effect to your image.
ImageView litAdjusted = new ImageView(image);
litAdjusted.setEffect(brightLight);
Executable sample
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
// Java 8 code
public class HereBeDragons extends Application {
@Override public void start(final Stage stage) {
Image image = new Image(imageLoc);
Image clipImage = new Image(CLIP_IMAGE_LOC);
ImageView plain = new ImageView(image);
ImageView lit = new ImageView(image);
Lighting lighting = createLighting();
lit.setEffect(lighting);
ImageView litAdjusted = new ImageView(image);
ColorAdjust brightLight = new ColorAdjust(0, 0, .25, 0.25);
brightLight.setInput(lighting);
litAdjusted.setEffect(brightLight);
plain.setClip(new ImageView(clipImage));
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
WritableImage clippedImage = plain.snapshot(params, null);
ImageView litAdjustedClip = new ImageView(clippedImage);
litAdjustedClip.setEffect(brightLight);
plain.setClip(null);
HBox layout = new HBox(
10,
new CaptionedImage(plain, "Plain"),
new CaptionedImage(lit, "Lit"),
new CaptionedImage(litAdjusted, "Lit and Adjusted"),
new CaptionedImage(litAdjustedClip, "Clipped,\nLit and Adjusted")
);
layout.setPadding(new Insets(20));
layout.setStyle("-fx-background-color: lightblue;");
stage.setTitle("Here be Dragons");
stage.setScene(new Scene(layout, Color.LIGHTBLUE));
stage.setResizable(false);
stage.show();
}
private Lighting createLighting() {
// Create ambient light
Light.Distant light = new Light.Distant();
light.setAzimuth(-135.0);
// Create lighting effect
Lighting lighting = new Lighting();
lighting.setLight(light);
lighting.setSurfaceScale(4.0);
return lighting;
}
private class CaptionedImage extends Label {
public CaptionedImage(ImageView imageView, String caption) {
setText(caption);
setGraphic(imageView);
setContentDisplay(ContentDisplay.TOP);
setTextAlignment(TextAlignment.CENTER);
setStyle(
"-fx-text-fill: midnightblue; " +
"-fx-font-size: 16px; " +
"-fx-font-family: palatino; " +
"-fx-font-style: italic;"
);
}
}
public static void main(String[] args) {
launch(args);
}
private static final String imageLoc =
"http://icons.iconarchive.com/icons/custom-icon-design/round-world-flags/128/Wales-icon.png";
// icon license: Free for non-commercial use.
// Buy commercial license here:
// http://www.customicondesign.com/free-icons/flag-icon-set/flat-round-world-flag-icon-set
private static final String CLIP_IMAGE_LOC =
"http://icons.iconarchive.com/icons/gpritiranjan/simple-christmas/128/star-icon.png";
// star icon license: freeware, commercial usage allowed.
}