JavaFX Image Pixelator (Using Lambdas)

I wanted to play around a bit with Lambdas in Java 8 and I had a little demo of image pixelation in JavaFX that I based on some code posted by Gerrit Grunwald (@hansolo_) some time ago and decided this was a good place to start!

So for those who don’t like to wait, here’s a little video of the end result:

So what are Lambdas I hear you ask?  Lambdas are basically a more concise way of defining anonymous inner classes which have only a single method and are very common in Java programming (and in many other languages).  Possibly the most familiar anonymous inner class with a single method for Java developers is an implementation of the Runnable interface which has just the one method run().  Swing programmers will have encountered many such classes every time they add a Listener to a Component such as an ActionListener with its method actionPerformed().  Another common form is an implementation of the Comparator interface with the method compare().

In all these cases, the anonymous inner classes are implementing an interface with a single method and these interfaces are known in Java 8 parlance as “functional interfaces” (and known previously as a Single Abstract Method type or SAM).  Lambdas are a very concise way of implementing these functional interfaces.

For the more academic readers, the formal syntax of a Lambda is:

()|x|(x,..,z) -> expression|statement

where x and z are arguments and -> is akin to the “new” operator.  In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body and a return statement returns control to the caller of the anonymous method.

Here are some simple examples:

(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }

Here’s a more detailed example that compares the anonymous inner class way with the Lambda way and highlights the conciseness of the latter:

public class RunnableTest {
    public static void main(String[] args) {

        // Runnable implemented as anonymous inner class (the old way).
        Runnable r1 = new Runnable() {

            @Override
            public void run() {
               System.out.println("There must be a better way!");
            }
        };

        // Runnable implemented as a Lambda (the new way).
        Runnable r2 = () -> { System.out.println("This is the better way!"); };

        // Running them is identical.
        r1.run();
        r2.run();
    }
}

Let’s have a look at the actual use of Lambdas in the code used in this example.

As with all code I release as part of my blog, this is the “license” I stipulate:

/*
 * This code is completely free of any restrictions on usage.
 *
 * Feel free to study it, modify it, redistribute it and even claim it as
 * your own if you like!
 *
 * Courtesy of Bembrick Software Labs in the interest of promoting JavaFX.
 */

I hope that’s OK with you :-)

First, here I use a Lambda to define a listener on the value property of a JavaFX Slider:

/**
* Creates a <code>Slider</code> to allow manual control over the level of pixelation.
*/
private Slider createSlider(final WritableImage wi) {
    final Slider result = new Slider();
    result.setMin(SLIDER_MIN);
    result.setMax(SLIDER_MAX);
    result.setMajorTickUnit(5);
    result.setShowTickLabels(true);
    result.setShowTickMarks(true);
    result.setSnapToTicks(true);

    // Use lambdas to bind pixelation of the image to a change in the value of the slider.
    result.valueProperty().addListener((observable, oldValue, newValue) -> {
        Pixelator.pixelate(wi, IMAGE, newValue.intValue());
    });

    return result;
}

And here I use a Lambda to define a handler for an action event on a JavaFX Button:

/**
* Creates the <code>Button</code> used to start and pause the animation. The button will
* either start the animation or pause it.
*/
private Button createButton(final Timeline timeline) {
    final Button result = new Button(START_LABEL);

    // Use lambdas to assign the handler for the "action" event.
    result.setOnAction(e -> {
        if (timeline.getStatus() == Animation.Status.RUNNING) {
            timeline.pause();
            result.setText(START_LABEL);
        } else {
            timeline.play();
            result.setText(PAUSE_LABEL);
        }
    });

    return result;
}

As for the JavaFX bits, well the demo includes a simple Stage with a Button, an ImageView and a Slider.  The Slider allows the user to select a level of pixelation which is applied to the image in real-time.  The demo makes use of a Timeline to control an animation that automatically sets the slider’s value, ranging from the minimum value to the maximum value and then back again.  The Button enables the user to either start the animation or pause it.

Here’s the code that creates the Timeline:

/**
* Creates a <code>Timeline</code> to control the animation of pixelation.
* The timeline will oscillate the value of the slider from minimum to maximum
* and back again.
*/
private Timeline createTimeline(final Slider slider) {
    final Timeline result = new Timeline();
    result.setAutoReverse(true);
    result.setCycleCount(Animation.INDEFINITE);
    result.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(slider.valueProperty(), SLIDER_MIN)),
      new KeyFrame(new Duration(1000), new KeyValue(slider.valueProperty(), SLIDER_MAX)));

    return result;
}

It uses two KeyFrame instances, one with the slider’s value property at the minimum value (at zero duration) and another with this property at the maximum value (at 1000ms duration).  Using this Timeline will therefore cause this property to gradually change from the minimum to the maximum value over the duration of 1000ms (or 1 second).  By setting the auto-reverse property and also the cycle count property to INDEFINITE we get an animation that continually auto-reverses and repeats indefinitely (or at least until it is paused manually).

Note: Gerrit’s original code had the pixelate() method return a Group with a series of Rectangle nodes as its children, one for each pixelated block.  This version has a parameter of type WritableImage which it uses to write each pixel in the image to be displayed in an effort to make it more efficient.  The performance of the algorithm or the PixelWriter itself is not fantastic and if larger images are selected for pixelation then the performance degrades significantly.  I have not investigated this to any great extent although I am sure that an OpenGL or Direct3D shader could accomplish the same task much faster.

In conclusion, this is another example of the capabiltities of JavaFX and when combined with the new language features coming in Java 8 such as Lambdas, we really do have a powerful platform and toolkit for building cross-platform graphics applications.  Why would you use anything else? ;-)

You can try it for yourself with the JavaFX Image Pixelator Using Lambdas Source including the image file used.

Note: You will require Java 8 to run this example.  This example was tested with JDK 8 b106 which is available here.

By the way, the animal in the image is a Spotted Quoll – a small carnivorous marsupial from Australia.  Quolls were once common across the entire Australian continent but have become endangered over recent years due to predation by feral cats and foxes and also habitat destruction.

Please leave feedback to let me know how this example works for you and any other comments you may have!

Just my 2 bits

Felix

Posted on September 12, 2013, in JavaFX and tagged , , , . Bookmark the permalink. 2 Comments.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 141 other followers

%d bloggers like this: