Home > Articles > Programming > Java

📄 Contents

  1. Effects Overview
  2. The JavaFX Effects Classes
  3. Blending
  4. Lighting
This chapter is from the book

This chapter is from the book

The JavaFX Effects Classes

The JavaFX SDK provides 17 different effects that can be applied to any node. This section describes and illustrates all the effects, with the exception of Blend and Lighting, which have sections of their own at the end of the chapter. Each effect has a set of variables that you can use to customize it. As we examine each effect, we'll take a look at the variables available and roughly consider what each of them does. There are too many combinations to illustrate them all in this chapter, so in most cases we limit ourselves to some typical examples. It is easy to experiment with these effects—all you need to do is modify the example source code. You can also use the Effects Playground application that you'll find among the samples at http://javafx.com.

GaussianBlur

The GaussianBlur effect produces a blurred version of its input. The "Gaussian" part of the name refers to the fact that the output pixels are calculated by applying a Gaussian function to the source pixel and a group of pixels surrounding it. If you are interested in the details, you'll find them at http://en.wikipedia.org/wiki/Gaussian_blur. The size of the group of adjacent pixels that are used to calculate the result is controlled by the radius variable (see Table 20-1). The larger the value of the radius variable, the greater the blur effect will be. When the value of this variable is 0, there is no blur at all.

Table 20-1. Variables of the GaussianBlur Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

radius

Number

RW

10.0

The radius of the area containing the source pixels used to create each target pixel, in the range 0 to 63, inclusive

Two example of the GaussianBlur effect applied to the image used in the previous two sections are shown in Figure 20-6. Here's the code used to create this effect, which you'll find in the file javafxeffects/GaussianBlur1.fx:

ImageView {
    image: Image { url: "{__DIR__}image1.jpg" }
    effect: GaussianBlur {
        
   radius: bind radiusSlider.value
    
   }
}
Figure 20-6

Figure 20-6 The GaussianBlur effect

The image on the left has a blur radius of 10, while the one on the right has radius 40.

BoxBlur

GaussianBlur is a high-quality effect, but it is also a relatively expensive one. The BoxBlur effect is a cheaper way to produce a blur, albeit one of lower quality. The variables that you can use to control the BoxBlur effect are listed in Table 20-2.

Table 20-2. Variables of the BoxBlur Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect.

height

Number

RW

5.0

The vertical size of the box used to create the blur, in the range 0 to 255, inclusive.

width

Number

RW

5.0

The horizontal size of the box used to create the blur, in the range 0 to 255, inclusive.

iterations

Number

RW

1

The number of averaging iterations, in the range 0 to 3, inclusive. Higher values produce a smoother blur effect.

This effect works by replacing each pixel of the input by the result of averaging its value with those of its neighboring pixels. The pixels that take part in the operation are those in a rectangular area surrounding the source pixel, the dimensions of which are given by the width and height variables. You can see the effects of changing these variables by running the code in the file javafxeffects/BoxBlur1.fx. This example applies the BoxBlur effect to the same image as we used to illustrate GaussianBlur. The width, height, and iterations variables are set from three sliders that allow you to test the full ranges of values for each variable. Here's how the BoxBlur is applied:

ImageView {
    image: Image { url: "{__DIR__}image1.jpg" }
    effect: BoxBlur {
        height: bind heightSlider.value
        width: bind widthSlider.value
        iterations: bind iterationSlider.value
    }
}

Increasing the value of the height variable produces a vertical blur, as shown on the left of Figure 20-7. Similarly, the width variable controls the extent of the blur in the horizontal direction.

Figure 20-7

Figure 20-7 The Box Blur effect

You can use the iterations variable to increase the quality of the blur at the expense of greater CPU utilization. When this variable has the value 2 or 3, the averaging operation is repeated the specified number of times. On the second iteration, the averaged pixels are averaged against each other, which tends to smooth out any sharp differences that might exist near to edges in the input source. A third iteration produces an even smoother result. You can see the result of applying three iterations to a horizontal blur of the input image on the right of Figure 20-7. A BoxBlur with three iterations produces a result that is close to that of a GaussianBlur, but at a slightly lower cost.

MotionBlur

MotionBlur creates the effect that you would see if you look out of the window of a fast-moving vehicle. Like GaussianBlur, it has a radius variable that determines how much of a blur is to be applied. It also has an angle variable that lets you specify the direction of the motion. These variables are described in Table 20-3.

Table 20-3. Variables of the MotionBlur Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

angle

Number

RW

0

The angle of the motion blur

radius

Number

RW

10.0

The radius of the area containing the source pixels used to create each target pixel, in the range 0 to 63 inclusive

There are no restrictions on the value of the angle variable, but values greater than 360 are treated modulo 360, while negative values are first reduced modulo 360 and then have 180 added to them, so that -90 is the same as 270. The following extract shows how to apply a MotionBlur to a node.

    image: Image { url: "{__DIR__}image1.jpg" }
    effect: MotionBlur {
        
   angle: bind angleSlider.value
        
   radius: bind radiusSlider.value
    
   }
}

If you run the code in the file javafxeffects/MotionBlur1.fx, you can experiment with the effects of different radius and angle values. Two examples with different angles are shown in Figure 20-8. The angle slider lets you vary the value of this variable from -180 when the thumb is at the far left to +180 at the far right. In the example on the left of the figure, the angle variable is 0, which gives a horizontal blur. In the example on the right, the angle variable has the value 90, and the result is a vertical blur. As is the case elsewhere in the JavaFX API, angles are measured with 0 at the 3 o'clock position and increase as you move in a clockwise direction.

Figure 20-8

Figure 20-8 The MotionBlur effect

DropShadow

As you have already seen, the DropShadow effect draws a shadow that appears to be behind and partly obscured by the node to which it is applied. By using this effect with the appropriate variable settings, you can give the impression that the node is floating above a nearby surface or one slightly farther away. You can also change the nature of the shadow to indicate whether the light source is close to or a long way from the node. The variables that you can use to configure the DropShadow class are listed in Table 20-4.

Table 20-4. Variables of the DropShadow Class

Variable

Type

Access

Default

Description

blurType

BlurType

RW

THREE_PASS_BOX

The type of blur to be used

color

Color

RW

Color.BLACK

The color to be used for the shadow

offsetX

Number

RW

0.0

The x-offset of the shadow

offsetY

Number

RW

0.0

The y-offset of the shadow

radius

Number

RW

10

The radius of the blur effect if a GaussionBlur is used

width

Number

RW

21

The width of the blur if BoxBlur is used

height

Number

RW

21

The height of the blur if BoxBlur is used

spread

Number

RW

0.0

The proportion of the radius (or box for BoxBlur) over which the shadow is fully opaque (see text)

The variables that you will most commonly set are color, offsetX, and offsetY. The color variable simply determines the color of the solid part of the shadow, which will generally be slightly darker than the background behind the node. By default, the shadow is black. The offsetX and offsetY variables control the displacement of the shadow relative to the node itself.

The blurType variable controls which of the supported types of blur is used at the edges of the shadow. This variable is of type javafx.scene.effects.BlurType, which has the following possible values:

  • BlurType.GAUSSIAN: A GaussianBlur
  • BlurType.ONE_PASS_BOX: A BoxBlur with one iteration
  • BlurType.TWO_PASS_BOX: A BoxBlur with two iterations
  • BlurType.THREE_PASS_BOX: A BoxBlur with three iteration

The code in the file javafxeffects/DropShadow1.fx creates a scene containing a rectangle with a DropShadow effect and a GaussianBlur. There are four sliders that let you control some of the variables listed in Table 20-5. You can use this program to experiment with various settings to see how they work. Figure 20-9 shows a typical example.

Table 20-5. Variables of the Shadow Class

Variable

Type

Access

Default

Description

blurType

BlurType

RW

THREE_PASS_BOX

The type of blur to be used

input

Effect

RW

null

The input to this effect

color

Color

RW

Color.BLACK

The color to be used for the shadow

radius

Number

RW

10.0

The radius of the blur effect if a GaussianBlur is used

width

Number

RW

21

The width of the blur if a BoxBlur is used

height

Number

RW

21

The height if the blur if a BoxBlur is used

Figure 20-9

Figure 20-9 Configuring a DropShadow effect

The size of the shadow is determined by the values of the offsetX, offsetX, and radius variables. When the radius is 0, the shadow has a sharp edge as shown on the left of Figure 20-10. In this case, the offsetX and offsetY values are both 15, so the shadow is offset by 15 pixels to the right of and below the top-left corner of the node, which gives the impression of a light source that is to the left of and above the top of the node. Negative values for the offsetX variable would be used for a light source to the right of the node, and negative offsetY values for a light source that is below the node.

Figure 20-10

Figure 20-10 Effects of the offsetX, offsetY, and radius variable of the DropShadow effect

When the radius value is non-0, the edge of the shadow is blurred by blending pixels of the shadow color with those of the background color. The radius determines the size of this blurred area. Increasing the radius value makes the blurred region, and the size of the shadow, larger, as shown on the right of Figure 20-10. As you can see, the blurring fades out with increasing distance from the original shadow area. The radius value can be anywhere between 0 and 63, inclusive.

By default, the blurred area starts with the shadow color on its inside edge and progresses to the background color on its outside edge. If you want, you can arrange for a larger part of the blurred area to have the shadow color, resulting in a larger, darker shadow. You do this by setting the spread value, which ranges from 0.0, the default, to 1.0. This value represents the proportion of the blurred area into which the shadow color creeps. On the right side of Figure 20-10, the spread variable has value 0, and you can see that the shadow gets lighter very rapidly as you move your eyes away from the edge of the rectangle. On the left side of Figure 20-11, the spread variable has been set to 0.5. Now you can see that the darker region of the shadow has increased in size as it encroaches into the blurred area. On the right of Figure 20-11, the spread is at of 0.9, and you can see that almost all the blurred area has been taken over by the shadow color.

Figure 20-11

Figure 20-11 The effect of the spread variable

The idea of the spread variable is to allow a proper emulation of what would happen if you moved a light source quite close up to the node. A light source nearby would cause a wide shadow, corresponding to a larger blurred area, but it would also cause the darker part of the shadow to increase in size. You simulate the former effect by increasing the blur radius and the latter by increasing the spread.

InnerShadow

InnerShadow is very similar to DropShadow, the difference being that the shadow is inside the boundaries of the node to which it is applied, rather than outside. This gives the impression of depth within the node, because it appears to have built-up sides. The variables of this class, which are listed in Table 20-6, are almost the same as those of DropShadow.

Table 20-6. Variables of the InnerShadow Class

Variable

Type

Access

Default

Description

blurType

BlurType

RW

THREE_PASS_BOX

The type of blur to be used

color

Color

RW

Color.BLACK

The color to be used for the shadow

offsetX

Number

RW

0.0

The x-offset of the shadow

offsetY

Number

RW

0.0

The y-offset of the shadow

radius

Number

RW

10

The radius of the blur effect if a GaussianBlur is used

width

Number

RW

21

The width of the blur if a BoxBlur is used

height

Number

RW

21

The height of the blur if a BoxBlur is used

choke

Number

RW

0.0

The proportion of the radius (or box for BoxBlur) over which the shadow is fully opaque (see text)

You can see an example of this effect in Figure 20-12. This screenshot shows the result of running the code in the file javafxeffects/InnerShadow1.fx. As with the DropShadow examples, you can use the sliders to vary the effect parameters and see the results. The choke variable is equivalent to the spread variable of the DropShadow class.

Figure 20-12

Figure 20-12 Configuring an InnerShadow effect

Shadow

The Shadow effect produces a single-colored and blurred shadow from the node or input effect on which it operates. The extent of the blur depends on the value of the radius, which is one of the three variables that control this effect, all of which are listed in Table 20-5 on page 661.

You can see an example of this effect as applied to some text in Figure 20-13. You can experiment with different radius values by running this example, which you'll find in the file javafxeffects/Shadow1.fx:

Text {
    textOrigin: TextOrigin.TOP
    x: 30 y: 30
    content: "JavaFX Shadow Effect"
    font: Font { size: 24 }
    effect: Shadow {
        
   color: Color.BLUE
        
   radius: bind radiusSlider.value
    
   }
}
Figure 20-13

Figure 20-13 The Shadow effect

Unlike the other two shadow effects, this one replaces its input instead of augmenting it, so the original text node is not drawn.

Bloom

The Bloom effect adds a glow to those areas of its input that are made up of pixels for which the luminosity value is above a given threshold. This effect has only two controlling variable, which are listed in Table 20-7.

Table 20-7. Variables of the Bloom Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect.

threshold

Number

RW

0.3

The luminosity above which the glow effect will be applied, from 0.0 (all pixels will glow) to 1.0. (No pixels will glow.)

The luminosity of a pixel is a measure of how bright it seems to the human eye. You can see an example of this effect in Figure 20-14, which shows the Bloom effect applied to an ImageView node 3:

ImageView {
     image: Image { url: "{__DIR__}image1.jpg" }
     effect: bloom = Bloom {
         
   threshold: bind (thresholdSlider.value as Number) / 10
     
   }
}
Figure 20-14

Figure 20-14 The Bloom effect

In the image on the left, the threshold value is 1.0. Because no pixel has a luminosity that is greater than 1.0, what you see here is the original image. On the right of the figure, the slider has been moved so that the threshold is now set to 0.3. The blue regions of the image, in particular the sky, are now noticeably brighter. Notice that this effect spills over onto adjacent pixels so that the leaves on the trees near the top of the image have also been brightened.

Glow

Glow is very similar to Bloom, except that the controlling parameter works in the reverse order. The glow effect makes bright pixels appear brighter. The more of the effect that you apply, as determined by the value of the level variable, the brighter those pixels appear. The two variables that control this effect are listed in Table 20-8.

Table 20-8. Variables of the Glow Effect

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect.

level

Number

RW

0.3

Controls the intensity of the glow effect. 0.0 gives no glow; 1.0 gives maximum glow.

You'll find an example that allows you to vary the level parameter in the file javafxeffects/Glow1.fx. The following extract from that file shows how the glow effect is applied to a node:

ImageView {
    image: Image { url: "{__DIR__}image1.jpg" }
    effect: Glow {
        level: bind (levelSlider.value as Number) / 10
    }
}

Figure 20-15 shows this effect applied to the same image as that used in our discussion of bloom in the previous section. In the image on the left of the figure, the level variable is 0, so no glow is applied. In the image on the right, the level is set to 0.6, and the result is almost exactly the same as the result of applying a small amount of bloom to the image, which you can see at the bottom of Figure 20-14. To apply more glow in this example, you move the slider farther to the right, whereas to apply more bloom in the example in the previous section, you moved it farther to the left.

Figure 20-15

Figure 20-15 The Glow effect

Identity

The Identity effect is a little different from the effects that you have seen so far—its sole purpose is to allow an Image object to be used as the input to another effect. It is always linked to a node, but that node does not appear in the scene; the result of applying one or more effects to the source image is seen instead. Table 20-9 lists the variables that control the behavior of this class.

Table 20-9. Variables of the Identity Class

Variable

Type

Access

Default

Description

source

Image

RW

null

The source Image

x

Number

RW

0

The x coordinate of the Image relative to the source Node

y

Number

RW

0

The y coordinate of the Image relative to the source Node

The simplest way to explain how these variables work is by using an example. The following code, which you'll find in the file javafxeffects/Identity1.fx, applies a GaussianBlur effect to an image and places it in the Scene.

1      Stage {
2          title: "Identity #1"
3          scene: Scene {
4              width: 380
5              height: 280
6              var image = Image { url: "{__DIR__}image1.jpg" }
7              content: [
8                  Circle {
9                      centerX: 100 centerY: 100
10                     effect: GaussianBlur {
11                         input: Identity {
12                             source: image
13                             x: 10 y: 10
14                         }
15                         radius: 10
16                     }
17                 }
18             ]
19         }
20     }

The result of running this code is shown in Figure 20-16.

Figure 20-16

Figure 20-16 The Identity effect

The Identity effect on lines 11 to 14 converts the image to an Effect that is then used as the input to the GaussianBlur, resulting in a blurred version of the image. These two effects are both linked with a circle, but because the circle is not an input to either of the effects, it does not influence the output, and the blurred image appears instead of it. The only property of the circle that is inherited is its coordinate system, which, in this case, is the same as the coordinate system of the scene. The x and y variables of the Identity effect, which are set on line 13, determine where its output would be drawn relative to the circle's coordinate system. In this case, these values cause the image to be placed a little to the right of and below the coordinate origin.

The result of an Identity effect, like that of the Flood effect that is described in the next section, is often used as one of the inputs to a Blend effect, which is discussed later in this chapter.

Flood

Like Identity, the purpose of the Flood effect is to create an input to another effect, in this case a rectangular area filled with a Paint or a solid color. The variables that determine the fill color and the bounds of the filled area are listed in Table 20-10.

Table 20-10. Variables of the Flood Class

Variable

Type

Access

Default

Description

paint

Paint

RW

Color.RED

The Paint used to flood the area

x

Number

RW

0

The x coordinate of the filled area relative to the source Node

y

Number

RW

0

The y coordinate of the filled area relative to the source Node

width

Number

RW

0

The width of the area to be filled

height

Number

RW

0

The height of the area to be filled

The coordinates and lengths are specified in the coordinate system of the node that this effect is linked with. As with Identity, the node itself is replaced in the scene by the result of the effect. The code in the file javafxeffects/Flood1.fx uses the Flood effect to fill an area with a solid blue color and then applies a MotionBlur, giving the result shown in Figure 20-17.

Figure 20-17

Figure 20-17 The Flood effect

ColorAdjust

The ColorAdjust effect produces an output that is the result of adjusting some or all the hue, saturation, brightness, and contrast values of its input. The input may be either another effect or a node of any kind, but most commonly an image in an ImageView object. The variables of this class are listed in Table 20-11.

Table 20-11. Variables of the ColorAdjust Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect.

hue

Number

RW

0.0

The amount by which the hue of each pixel should be adjusted, in the range -1.0 to 1.0. Value 0 leaves the hue unchanged.

saturation

Number

RW

0.0

The amount by which the saturation of each pixel should be adjusted, in the range -1.0 to 1.0. Value 0 leaves the saturation unchanged.

brightness

Number

RW

0.0

The amount by which the brightness of each pixel should be adjusted, in the range -1.0 to 1.0. Value 0 leaves the brightness unchanged.

contrast

Number

RW

1.0

The amount by which the contrast should be adjusted, in the range 0.25 to 4. Value 1 leaves the contrast unchanged.

You can experiment with this effect by running the example in the file javafxeffects/ColorAdjust.fx, which binds a slider to each of the hue, saturation, brightness, and contrast variables of a ColorAdjust object that is associated with an ImageView node. The values of the hue, saturation, and brightness sliders range from -1.0 on the left to 1.0 on the right, while the contrast slider provides the value 0.25 in its minimum position and 4.0 at its maximum position. On the left of Figure 20-18, you can see the result of applying almost the maximum brightness, and on the right you see the result of applying the maximum contrast.

Figure 20-18

Figure 20-18 The ColorAdjust effect

InvertMask

The InvertMask effect takes another Effect as its input and produces a result in which all the transparent pixels from the input are opaque and all the opaque pixels are transparent. The output is typically used as one of the inputs to a Blend effect, which is discussed later in this chapter. The variables of the InvertMask class are listed in Table 20-12.

Table 20-12. The Variables of the InvertMask Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

pad

Number

RW

0

The padding to add to the sides of the resulting image

Reflection

The Reflection effect provides an easy way to create a reflection of a node or group of nodes. The variables that you can use to specify the required characteristics of the reflection are listed in Table 20-13.

Table 20-13. The Variables of the Reflection Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

topOffset

Number

RW

0

The distance between the bottom of the input and the beginning of the reflection

fraction

Number

RW

0

The fraction of the input that is used to create the reflection

topOpacity

Number

RW

0.5

The opacity used for the topmost row of pixels in the reflection

bottomOpacity

Number

RW

0

The opacity of the bottom row of pixels in the reflection

The example code in the file javafxeffects/Reflection1.fx allows you to experiment with different values of these variables. A typical result, which is equivalent to the following code, is shown in Figure 20-19.

Text {
    content: "JavaFX Developer's Guide"
    x: 20 y: 50
    fill: Color.WHITE
    font: Font { size: 24 }
    effect: Reflection {
        topOffset:0
        fraction: 0.8
        topOpacity: 0.3
        bottomOpacity: 0.0
    }
}
Figure 20-19

Figure 20-19 The Reflection effect

The topOffset variable lets you set the distance between the source object, here the text "JavaFX Developer's Guide" (and its reflection). Increasing this distance makes it seem that the source is further away from the reflecting surface. In Figure 20-19, the topOffset value is 0, which places the reflection as close as possible to the original. In this case, the reflected text might seem to be farther away than it should be with this value—that is because of the descender on the letter p, which is the closest point of contact with the reflection.

The fraction variable determines how much of the source appears in the reflection. Typically, unless the reflecting surface is very shiny, you will not want the whole of the source object to be reflected. In Figure 20-19, the fraction variable has the value 0.8, so about 80% of the source is reflected.

The topOpacity and bottomOpacity values give the opacity of the reflection at its top and bottom extents, respectively. In Figure 20-19, the topOpacity has been set to 0.3 and bottomOpacity to 0.0.

SepiaTone

The SepiaTone effect is used to give images (or any group of nodes) an "Olde Worlde" look, as if they have been photographed by an old black-and-white camera, or washed out by the effects of exposure to sunlight over a long period. This effect provides only the two variables listed in Table 20-14.

Table 20-14. Variables of the SepiaTone Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

level

Number

RW

1.0

The level of this effect, from 0.0 to 1.0

The level variable determines the extent to which the image is affected. The example code in the file javafxeffects/SepiaTone1.fx creates a Scene containing an image and a slider that lets you vary the value of the level variable and observe the result. The screenshot on the left of Figure 20-20 has level set to the value 0.4, while the one on the right has level set to 1.0.

Figure 20-20

Figure 20-20 The SepiaTone effect

PerspectiveTransform

The PerspectiveTransform is a useful effect that you can use to create the impression of a rotation in the direction of the z-axis—that is, into and out of the screen. It operates by deforming a node or group of nodes by moving its corners to specified locations and relocating the other pixels in such a way that straight lines drawn on the original nodes are mapped to straight lines in the result. Unlike the affine transforms that you saw in Chapter 17, "Coordinates, Transforms, and Layout," this effect does not guarantee that lines that are parallel in the source will be parallel in the result and, in fact, the perspective effect requires that some parallel lines be made nonparallel.

The variables that control the perspective effect are listed in Table 20-15.

Table 20-15. Variables of the PerspectiveTransform Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect.

llx

Number

RW

0

The x coordinate of the location to which the lower-left corner of the input is moved

lly

Number

RW

0

The y coordinate of the location to which the lower-left corner of the input is moved

ulx

Number

RW

0

The x coordinate of the location to which the upper-left corner of the input is moved

uly

Number

RW

0

The y coordinate of the location to which the upper-left corner of the input is moved

lrx

Number

RW

0

The x coordinate of the location to which the lower-right corner of the input is moved

lry

Number

RW

0

The y coordinate of the location to which the lower-right corner of the input is moved

urx

Number

RW

0

The x coordinate of the location to which the upper-right corner of the input is moved

ury

Number

RW

0

The y coordinate of the location to which the upper-right corner of the input is moved

To see what these variables represent, refer to Figure 20-1. Here, imagine that the image is mounted vertically and can rotate about its vertical axis, as shown by the white dashed line. In the figure, the black outline represent the view of the image after it has been rotated a few degrees so that the right edge has moved closer to the viewer and the left edge farther away. This would cause the right edge to appear larger and the left edge correspondingly smaller, giving the impression of perspective.

You can use a PerspectiveTransform to create the rotated image from the original by moving the corners of the original to the new positions, as shown in Figure 20-21. The corner at the top left is the upper-left corner, and its position is given by the ulx and uly variables. The corner at the top right is the upper-right corner, and its position is specified by the urx and ury variables, and so on.

Figure 20-21

Figure 20-21 Illustrating the variables of the PerspectiveTransform class

It's easy to create a PerspectiveTransform that will rotate an image (or any other node or group) around a vertical axis that is a specified distance along its horizontal edge. It requires only a small amount of mathematics. We'll deal with the x and y coordinates separately, to make it easier to understand what is going on. The information needed to work out how to calculate the values of the x coordinates is shown in Figure 20-22.

Figure 20-22

Figure 20-22 Rotating an image (top-down view)

Here, we are looking down at the image from above. The thick horizontal line, labeled APB, is the image before rotation, whereas the diagonal line, labeled A'PB', is the image after it has been rotated through an angle (shown here as angle) about a pivot point, marked P, that is l1 pixels from its left side and l2 pixels from its right side. In this case, the pivot point is almost equidistant from the sides of the image, but the same calculation works even if this is not the case. The x-axis is shown at the bottom of the figure.

The x coordinate of the left side of the image after rotation is given by the distance AC, while the x coordinate of the right side is given by A. The distance AC is the same as AP - CP. Because AP has the value l1, elementary trigonometry gives the following:

AC = AP - CP = l1 - l1 * cos(angle)

Similarly,

AD = BP + PD = l1 + l2 * cos(angle)

AC is actually the value of both ulx and llx, while AD is the value that we need for urx and ulx. To make this simpler when using the PerspectiveTransform, we introduce two new parameters:

  • imgWidth: The width of the image. This corresponds to the length AB and is equal to l1 + l2.
  • pivot: The position of the pivot point along the line AB, as a ratio of l1 to the total length AB. To place the pivot point in the center, set pivot to 0.5.

Given these parameters, we so far have the following PerspectiveTransform:

PerspectiveTransform {
    ulx: l1 - l1 * Math.cos(angle)
    uly: ?? // Not yet determined
    llx: l1 - l1 * Math.cos(angle)
    lly: ?? // Not yet determined
    urx: l1 + l2 * Math.cos(angle)
    ury: ?? // Not yet determined
    lrx: l1 + l2 * Math.cos(angle)
    lry: imgHeight ?? // Not yet determined
}

Now let's move on to the y coordinates. This part is slightly easier. Essentially, what we need to do is make the length of the side of the image that moves toward us larger and that of the side that moves away from us smaller. We can choose by how much we want each side to grow or shrink—the closer we are to the image, the more each side would grow or shrink. We'll make this a parameter of the transform and say that we want each side to grow or shrink by htFactor of its actual value at each end. That means, for example, that if the image is 100 pixels tall and we choose htFactor to be 0.2, the side of the image that is nearer to us after the image has rotated through 90 degree will be larger by 0.2 * 100 = 20 pixels at each end, or a total of 140 pixels tall. Similarly, the side that is farther away will shrink to 60 pixels in height.

Now refer to Figure 20-23. Here, we are looking at the image from the front again. The solid shape is the image after it has been rotated. The dashed vertical line is the axis of rotation, and the dashed extension that is outside the rectangle represents the maximum apparent height of the image when it has rotated through 90 degrees—that is, when it is edge-on to the viewer.

Figure 20-23

Figure 20-23 Rotating an image (front view)

In its current position, the y coordinate of the upper-right corner would be -B'D. This coordinate is negative because the y-axis runs along the top of the image, as shown. The length of B'D is l2 * sin(angle), but because we are limiting the maximum vertical extension of each side by htFactor, we use the value htFactor * l1 * sin(angle) instead. Applying the same logic to each of the four corners gives us the following as the final transform, installed in an ImageView and with specific values assigned for the variables pivot and htFactor:

ImageView {
    translateX: bind (scene.width - imgWidth) / 2
    translateY: bind (scene.height - 30 - imgHeight) / 2
    image: image = Image { url: "{__DIR__}image1.jpg" }
    var angle = bind Math.toRadians(slider.value);
    var pivot = 0.5;
    var htFactor = 0.2;
    var l1 = bind pivot * imgWidth;
    var l2 = bind imgWidth - l1;
    effect: bind PerspectiveTransform {
        
   ulx: l1 - l1 * Math.cos(angle)
        
   uly: htFactor * l1 * Math.sin(angle)
        
   llx: l1 - l1 * Math.cos(angle)
        
   lly: imgHeight - l1 * htFactor * Math.sin(angle)
        
   urx: l1 + l2 * Math.cos(angle)
        
   ury: -l2 * htFactor * Math.sin(angle)
        
   lrx: l1 + l2 * Math.cos(angle)
        
   lry: imgHeight + l2 * htFactor * Math.sin(angle)
    
   }
}

The file javafxeffects/PerspectiveTransform1.fx contains an example that incorporates this transform and provides a slider that allows you to vary the value of the angle variable from -90 degrees to +90 degrees. Figure 20-24 shows a couple of screenshots taken from this example with the image rotated by two different angular amounts. You can experiment with this example by changing the value of the pivot variable to get a rotation about a different point. Setting pivot to 0 causes a rotation around the left edge, while the value 1 gives rotation about the right edge.

Figure 20-24

Figure 20-24 Examples of images rotated using a PerspectiveTransform

DisplacementMap

The DisplacementMap effect is, at first glance, the most complex of the effects that are provided by the JavaFX SDK, but it is also one of the most powerful. As its name suggests, this effect displaces pixels from their locations in the input image to different positions in the output image. Let's begin by listing the variables that you can use to parameterize the effect (see Table 20-16), and then we'll take a look at how they work.

Table 20-16. Variables of the DisplacementMap Class

Variable

Type

Access

Default

Description

input

Effect

RW

null

The input to this effect

mapData

FloatMap

RW

Empty map

The map that determines how input pixels are mapped to output pixels

offsetX

Number

RW

0.0

A fixed displacement along the x-axis applied to all pixel offsets

offsetY

Number

RW

0.0

A fixed displacement along the y-axis applied to all pixel offsets

scaleX

Number

RW

1.0

A scale factor applied to the map data along the x-axis

scaleY

Number

RW

1.0

A scale factor applied to the map data along the y-axis

wrap

Boolean

RW

false

Whether the displacement operation should wrap at the boundaries

How the DisplacementMap Effect Works

The reason for the apparent complexity of this effect is the equation that controls how the pixels are moved:

dst[x, y] = src[x + (offsetX + scaleX * map[x, y][0]) * srcWidth,
                y + (offsetY + scaleY * map[x, y][1]) * srcHeight]

At first sight, this probably looks quite daunting, but in fact it turns out to be quite simple. Basically, it says each pixel in the output (here represented by the symbol dst) derives from a single pixel in the input (represented by src). The pixel value at coordinates (x, y) in the output is obtained from a source pixel whose coordinates are displaced from those of the destination pixel by an amount that depends on a value obtained from a map, together with some scale factors and an offset. The values srcWidth and srcHeight are respectively the width and height of the input source.

Let's start by assuming that the offset values are both 0 and the scale values are both 1. In this simple case, the equation shown above is reduced to this more digestible form:

dst[x, y] = src[x + map[x, y][0] * srcWidth,
                y + map[x, y][1] * srcHeight]

The map is a two-dimensional data structure that is indexed by the x and y coordinates of the destination point, relative to the top-left corner of the output image. Each element of this structure may contain a number of floats, which is why the class that holds these values is called a FloatMap. The FloatMaps that are used with a DisplacementMap must have two floats in each position, 5 the first of which is used to control the displacement along the x-axis and the second the displacement along the y-axis. Suppose, for the sake of argument, that we have a FloatMap in which every element has the values (-0.5, -0. 5). In this case, the equation above can be written as follows:

dst[x, y] = src[x - 0.5 * srcWidth, y - 0.5 * srcHeight]

Now, you should be able to see that the pixel at any given position in the output is obtained from the source pixel that is a half of the width or height of the source away from it. If we assume that the source is 100 pixels square, we can make our final simplification:

dst[x, y] = src[x - 50, y - 50]

This says that the output pixel at any point comes from the source pixel that is 50 pixels above it and to its left. The reason for using srcWidth and srcHeight as multipliers is that the values in the map can then be encoded as fractions of the width and height of the input respectively and therefore would normally be in the range -1 to +1. A map value of -1 or +1 would move a point by the complete width or height of the input source.

A Simple Example

Let's look at how you would implement the example that you have just seen. You'll find the code in the file javafxeffects/DisplacementMap1.fx. Let's start by creating the map:

1      var image: Image = Image { url: "{__DIR__}image1.jpg" };
2      var imgWidth = image.width as Integer;
3      var imgHeight = image.height as Integer;
4      var map: FloatMap = FloatMap {
5          width: imgWidth
6          height: imgHeight
7      }
8
9      for (i in [0..<map.width]) {
10         for (j in [0..<map.height]) {
11             map.setSample(i, j, 0, -0.50);
12             map.setSample(i, j, 1, -0.50);
13         }
14     }

In this example, we are going to use an image as the input source, so we create a map that has the same dimensions as the image itself. The code on lines 4 to 6 declares the FloatMap, setting its dimensions from the width and height of the image. The nested loops on lines 9 to 14 initialize the FloatMap, assigning two samples for each element. Each sample has the value -0.5, which is the offset that we require. Note how these samples are installed:

map.setSample(i, j, 0, -0.50);   // The x offset
map.setSample(i, j, 1, -0.50);   // The y offset

FloatMap has several overloaded variants of the setSample() function that you can use. In the variant that we use here, the first two arguments are the x and y coordinates of the element, the third argument is the band number, and the fourth argument is the offset for that band. Band 0 is used for the x-offset and band 1 for the y offset. 6

Now, here's the code that creates and uses the DisplacementMap effect:

var scene: Scene;
Stage {
    title: "DisplacementMap #1"
    scene: scene = Scene {
            width: 500
            height: 380
            fill: Color.BLACK
            content: [
                ImageView {
                    translateX: bind (scene.width - imgWidth) / 2
                    translateY: bind (scene.height - 30 - imgHeight) / 2
                    image: image
                    effect: DisplacementMap {
                        
                        mapData: map
                    
                     }

                }

            ]

        }

}

As you can see, the effect is applied simply by creating a DisplacementMap based on the map data and installing it in an ImageView that contains the source image. We don't need to set the scale or offset values because we are using the defaults in this case. You can see the result in Figure 20-25.

Figure 20-25

Figure 20-25 A simple DisplacementMap effect

The original image is shown on the left of the figure and the result of applying the DisplacementMap on the right. As you can see, the image has been moved halfway across and halfway down the area occupied by the source. It's easy to see why this has happened if you look back at the equation that describes this effect:

dst[x, y] = src[x - 0.5 * srcWidth,
                y - 0.5 * srcHeight]

This says that the pixel at (x, y) comes from the source pixel that is half the source width to its left and half the source height above it. In other words, the image is moved down and to the right. To make this more obvious still, let's add some concrete numbers. We'll start by with the pixel at (0, 0) in the destination image. According to the equation above, the color for this pixel comes from the pixel at (0 - 0.5 * 340, 0 - 0.5 * 255) = (-170, -127). Because there is no such point, this pixel is not set, so this part of the destination is transparent. In fact, every pixel for which either of the source coordinates is negative will be transparent. The first pixel in the destination image that will not be transparent is the one at (170, 127), which gets its color from the pixel at (0, 0) in the source. By following this reasoning for any given pixel in the destination image, it is easy to see why the result of this effect is to move the source down and to the right, as shown in Figure 20-25.

The wrap Variable

You can achieve a slightly different effect to that shown above by setting the wrap variable of the DisplacementMap object to true. When you do this, the parts of the destination that would have been transparent because they correspond to points in the source image that are outside of its bounds (for example, those with negative coordinates) are populated by wrapping the coordinates modulo the size of the source. This means, for example, that the pixel at (0,0), which should come from (-170, -127) in the source, will actually come from (-170 + 340, -127 + 128), or (170, 1). You can see the overall effect of this by running the code in the file javafxeffects/DisplacementMap2.fx, which gives the result shown in Figure 20-26.

Figure 20-26

Figure 20-26 A DisplacementMap with wrap enabled

The offset Variables

Now that you have seen how the values in the map work in the simplest case, we'll make things a little more complex by adding back the offsetX and offsetY values. These values simply add a fixed offset to the distance between the destination pixel and the source pixel that supplies its color. Like the entries in the map, each offsets is scaled by the width or height of the source, as appropriate.

For example, let's suppose that we were to set the offsetX variable to 0.1 and leave offsetY as 0. Then, using the same map as we did for the previous example, the equations that relate the source and destination pixel locations would now be as follows:

dst[x, y] = src[x + 0.1 * srcWidth- 0.5 * srcWidth,
                y - 0.5 * srcHeight]

If, as before, the source is 340 pixels wide, this change would produce an additional offset of 34 pixels between the source and destination pixels.

You can see how the offsetX value works by running the code in the file javafxeffects/DisplacementMap3.fx. This example uses the same FloatMap as the previous one, but adds a slider that allows you to vary the offsetX value from 0 up to 1.0, with the initial value being 0.0. Initially, the result looks the same as before, because the offsetX value is still 0—compare the image on the left of Figure 20-27 with that on the right of Figure 20-25 to see that this is the case.

Figure 20-27

Figure 20-27 Varying the offsetX value of a DisplacementMap effect

Now if you move the offset slider to the right, you will see that the output image moves to the left. This is the offset at work. The farther you move the slider to the right, the more the result shifts to the left. The same effect would be seen along the y-axis if we had added a slider that allowed you to vary the offsetY value.

The scale Variables

The scaleX and scaleY variables are multipliers that are applied to the values from the FloatMap. If you use a scaleX value that is greater than 1, you make the offset between the source and destination pixels larger than that specified in the map. A scaleX value of 2 would double the offsets specified in the map. Similarly, if you use a value that is less than 1, the offset gets smaller. It is also possible to use a negative value, which would reverse the effect of the map.

The code in javafxeffects/DisplacementMap3.fx also includes a slider that lets you change the value of the scaleX variable over the range 0 to 2, with 1 as its initial value. If you move the slider, you will find that the output image also moves to reflect the magnified or reduced offset values. In this case, because every entry in the map has the same value, the effect is very similar to that obtained by changing the offsetX value, but this is not always the case, as you'll see later in this section.

Using the DisplacementMap to Create a Warp

The example that we have been using has the same value in every element of the map. This is a rather unusual case and it doesn't produce a very interesting effect. In this section, we'll take a look at how to create a warp effect by populating the map with values that depend on their position in the map. The completed effect is shown in Figure 20-28.

Figure 20-28

Figure 20-28 Using DisplacementMap to create a warped effect

As you can probably tell, the effect is produced by simulating the effect of a wave moving in the direction of the y-axis, which causes successive pixel rows to be displaced to the left or right of their initial positions. As there is no movement of any kind in the y direction, you can immediately conclude that all the y values (those in the second band) in the map are 0. The wave effect is, in fact, a sine wave. Here's the code that populates the map 7:

1      var image: Image = Image { url: "{__DIR__}image1.jpg" };
2      var imgWidth = image.width as Integer;
3      var imgHeight = image.height as Integer;
4      var map: FloatMap = FloatMap {
5          width: imgWidth
6          height: imgHeight
7      }
8
9      for (i in [0..<map.width]) {
10         for (j in [0..<map.height]) {
11             var value = (Math.sin(j/30.0 * Math.PI)/10;
12             map.setSample(i, j, 0, value);
13         }
14     }

The part that does all the interesting work is on line 11. It is obvious that this is creating a sine wave by supplying the horizontal displacement (in the first band of the map) for each row of the input source based on the value of the Math.sin() function. The value of this function varies from 0 at 0 radians to 1 at PI/2 radians, back to zero at PI radians, to -1 at 3*PI/2 radians, and then back to zero at 2 *PI radians, and so on. In the inner loop, the value represents the pixel row. We divide it by 30 and multiply it by PI so that we get a complete wave over the space of 30 pixels. If you make this number larger, you will find that the wave spaces out more. This code would place values ranging from +1 to -1 in every element of the map. Remembering that these offsets are multiplied by the width of the source, this would mean that the image would be distorted by up to its full width. To reduce the distortion, we divide every value by 10, so we end up with values in the range -0.1 to +0.1. That's all we need to do to create a warp effect.

If you run the code in the file javafxeffects/DisplacementMap4.fx, you can use the offset and scale sliders to change the parameters of the DisplacementMap. Notice that changing the scale increases or decreases the amplitude of the sine wave, which results in more or less distortion.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020