Explore Java and more with Jeff 'JavaJeff' Friesen

Playing with Perspective

The javafx.scene.effect.PerspectiveTransform class implements an effect that provides a non-affine transformation of its input content, which is specified by this class's input variable -- null implies that the content is taken from the node to which this effect is attached.

An affine transform (such as a translation) transforms straight lines into straight lines. Furthermore, it guarantees that parallel lines remain parallel after the transformation. Although the perspective transform also transforms straight lines into straight lines, it typically doesn't transform parallel lines into parallel lines.

PerspectiveTransform is useful for giving content a "faux" three-dimensional appearance. Whether the content appears somewhat three-dimensional or not depends upon the values that are assigned to the following variables:

  • llx identifies the x coordinate of the output location onto which the lower-left corner of the content is mapped.
  • lly identifies the y coordinate of the output location onto which the lower-left corner of the content is mapped.
  • lrx identifies the x coordinate of the output location onto which the lower-right corner of the content is mapped.
  • lry identifies the y coordinate of the output location onto which the lower-right corner of the content is mapped.
  • ulx identifies the x coordinate of the output location onto which the upper-left corner of the content is mapped.
  • uly identifies the y coordinate of the output location onto which the upper-left corner of the content is mapped.
  • urx identifies the x coordinate of the output location onto which the upper-right corner of the content is mapped.
  • ury identifies the y coordinate of the output location onto which the upper-right corner of the content is mapped.

According to PerspectiveTransform's documentation, it doesn't adjust the coordinates of mouse input events, or adjust the results of any methods that measure containment on a Node to compensate for perspective.

I've created an application that lets you play with the PerspectiveTransform class by dynamically modifying the previously listed variables. Figure 1 reveals this application's user interface.

The user interface presents an image (courtesy of Petr Kratochvil) followed by eight slider controls.

Figure 1: The user interface presents an image (courtesy of Petr Kratochvil) followed by eight slider controls.

Adjust each slider to change the X or Y perspective of an image corner from 0 (the slider's leftmost value, which indicates no perspective) to half the width or height. Figure 2 reveals how the perspective changes as you manipulate the slider controls.

The image's perspective changes as you move the sliders.

Figure 2: The image's perspective changes as you move the sliders.

I created this application as a NetBeans IDE 6.5.1 with JavaFX 1.2 PTDemo project. In addition to the image shown in Figure 1, PTDemo consists of a single Main.fx source file, which is presented in Listing 1.

/*
 * Main.fx
 */

package ptdemo;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.Slider;

import javafx.scene.effect.PerspectiveTransform;

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

import javafx.stage.Stage;

var image: Image = Image
                   {
                       url: "{__DIR__}res/still_life.jpg"
                   }

var llx: Slider;
var lly: Slider;
var lrx: Slider;
var lry: Slider;
var ulx: Slider;
var uly: Slider;
var urx: Slider;
var ury: Slider;

Stage
{
    title: "PerspectiveTransform Demo"

    var scene: Scene
    scene: scene = Scene
    {
        width: 350
        height: 450

        var iv: ImageView
        var hb: HBox
        content:
        [
            iv = ImageView
            {
                translateX: bind (scene.width-iv.layoutBounds.width)/2
                translateY: 20
                
                image: image

                effect: PerspectiveTransform
                {
                    llx: bind llx.value
                    lly: bind image.height-lly.value
                    lrx: bind image.width-lrx.value
                    lry: bind image.height-lry.value
                    ulx: bind ulx.value
                    uly: bind uly.value
                    urx: bind image.width-urx.value
                    ury: bind ury.value
                }
            }

            hb = HBox
            {
                spacing: 30
                
                translateX: bind (scene.width-hb.layoutBounds.width)/2
                translateY: iv.layoutBounds.height+40

                content:
                [
                    VBox
                    {
                        spacing: 10
                        
                        content:
                        [
                            Label
                            {
                                text: "ULX"
                            }

                            Label
                            {
                                text: "ULY"
                            }

                            Label
                            {
                                text: "URX"
                            }

                            Label
                            {
                                text: "URY"
                            }

                            Label
                            {
                                text: "LRX"
                            }

                            Label
                            {
                                text: "LRY"
                            }

                            Label
                            {
                                text: "LLX"
                            }

                            Label
                            {
                                text: "LLY"
                            }
                        ]
                    }

                    VBox
                    {
                        spacing: 10

                        content:
                        [
                            ulx = Slider
                            {
                                min: 0
                                max: image.width/2
                            }

                            uly = Slider
                            {
                                min: 0
                                max: image.height/2
                            }

                            urx = Slider
                            {
                                min: 0
                                max: image.width/2
                            }

                            ury = Slider
                            {
                                min: 0
                                max: image.height/2
                            }

                            lrx = Slider
                            {
                                min: 0
                                max: image.width/2
                            }

                            lry = Slider
                            {
                                min: 0
                                max: image.height/2
                            }

                            llx = Slider
                            {
                                min: 0
                                max: image.width/2
                            }

                            lly = Slider
                            {
                                min: 0
                                max: image.height/2
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

Listing 1: Main.fx

Although the listing should be mostly straightforward, there are two items that deserve clarification:

  • I place the eight Label instances in one VBox, and the eight Slider instances in a second VBox because I found doing so to be convenient for vertically aligning the sliders as well as the labels.
  • Because of a bug in the Slider implementation (which will hopefully be fixed in the next JavaFX release), I'm forced to assign 0 to min, and image.width/2 or image.height/2 to max. I then bind either the slider's result as is, or subtracted from the image's width or height to the appropriate PerspectiveTransform variable. Without the bug, the subtractions wouldn't have been necessary. For example, given the lly Slider instance, I'd assign image.height/2 to min and image.height to max (and value), and specify lly: lly.value in the PerspectiveTransform class literal.

Conclusion

Because this article's example provides only a basic demonstration of PerspectiveTransform, I refer you to Josh Marinacci's 3-D Display Shelf With the PerspectiveTransform JavaFX sample for a more advanced demonstration.


Download code.zip

Note: Application created with JavaFX 1.2 (via NetBeans IDE 6.5.1) on top of Java SE 6u16.


LEARN JAVA FROM APRESS