ScreenshotMaker: Capture, Edit, and Upload a Screen to Flickr or Save It to Disk

By Jean-Francois Denise, October 6, 2008

With the JavaFX technology it is easy to build your own "picture retouching" tool and couple it with Restful Web Services. This sample application enables you to capture your screen, edit the screenshot by adding Arrows and Text, and finally save it to disk or upload it to Flickr. The full image or a portion of it can be uploaded.

This sample only works on the Desktop platform.

Understanding the Code

This sample is composed of six files:

  • ScreenshotMaker.fx: The main source of this sample. Constructs a "one button frame" to capture the screen and a Frame to retouch the captured screen picture.
  • Palette.fx: The palette that contains the buttons which enable image editing.
  • FlickrPhotoUpload.fx: A class that extends javafx.io.HttpRequest to handle Flickr photo upload.
  • Flickr.java: A class that handles Flickr photo content creation. Mainly request signing and HTTP message construction.
  • ScreenCapturer.java: A simple class that abstracts screenshot capture.
  • Util.java: A simple class that offers some utility features.

Retouching the Image

The Palette

The top right corner palette enables you to select the tool to apply to the image. The buttons are the result of the compositions of basic Java FX shapes (Line, Rectangle, Polygone, PolyLine) grouped in Group. Positioning exactly where you want shapes is very simple. You just need to provide the coordinates of each points. The file Palette.fx contains the layout.

The actual size of the palette is much bigger than the displayed palette. Expanding the palette to the actual size enables you to design highly detailed icons. When the Palette is added to the Stage, scaling is adjusted to fit the display.

Each time the mouse pointer enters or exits a button, an effect is applied to the button.

Source Code
        button.effect = DropShadow{ offsetX: 0 offsetY: 0 color: Color.WHITE radius: 30 };
    

Each time a button is selected, a shadow is applied to enable the selection. The class Palette.ButtonFeedback implements the selection logic.

Tooltips (instances of the class Palette.Tooltip) are provided for each button. Tooltips delay is implemented using Timeline.

Source Code
    timeline = Timeline {
        repeatCount: Timeline.INDEFINITE
        keyFrames :
            KeyFrame {
                time : 1000ms
                action: function() {
                    currentToolTip.visible = true;
                }
            }
        };
When the mouse pointer exits the button, the timeline is stopped and the tooltip is hidden.

Dragging an Image Inside a Frame

The challenge when capturing a screenshot is that it is as big as your screen. So, how can you display a window that contains the captured image, the toolbar to edit it, and the window decoration? You really can't without resizing the image. Perhaps displaying these three components is something that you don't want to do in the first place. That is why the image editor displays part of the image in its window. It displays 85 percent of the original screen. Some parts of the image are then hidden. You can drag the image by clicking the Drag Image button. Click and drag the image. The hidden parts are then displayed. When you are satisfied with the visible part, release the mouse button. Technically, you can achieve this series of actions by translating the ImageView and enlarging or reducing its viewPort.

Source Code
        imgView = ImageView{
            // When the mouse is dragged, the viewPort is resized in order to
            // display the image zone that enters the visible zone.
            viewport :bind translatedRect
            // Translates the image in the visible / non visible zone.
            transforms:Translate {x: bind movedX y : bind movedY}
            image : Image {
                url: imgURL
            }
        }
    

When the mouse is dragged, the new ImageView coordinates and the viewPort size are recomputed. A positive or negative increment is injected in the ImageView. The maximum hidden zone is used to stop the translation when no more hidden zones can be displayed.

Dynamically Adding Node Instances to a Scene

Arrows and Text are added to the Group located in the Scene. A local reference to the group (groupRef) is kept. Each time an Arrow or Text is created, it is added to the group content:

Source Code
        insert currentArrow into groupRef.content;
    

Adding Arrows

Click the Add Arrow button. Move your mouse pointer to the place where you want the arrow to start, and press and drag. An arrow is drawn. The Arrow is a Java FX CustomNode that is a group of a main Line and two small Line instances that form the arrowhead. The head rotates according to the mouse-dragging direction. The pivot is the line endX.

Source Code
         Line {
            transforms: Rotate{angle: angle pivotX:line.endX pivotY:line.endY }
            ...
    
Click an Arrow instance and drag it to change its location.

Adding Text

Click on Add Text button. Move your mouse pointer to the place where you want to type text. Click and then type some text. A text is an EditabelText that extends CustomNode. It is composed of a javafx.scene.control.TextBox to edit and display the typed text. When you double-click the text, you are returned to edit mode. Typing on return or clicking anywhere else makes the text change to display mode. Click an EditableText instance and drag it to change its location.

To be active, you must set the focus to the newly created TextBox. You make this change by calling:

Source Code
    tb.requestFocus();
    

Selecting Part of the Image

Click the Select Region button. Move your mouse pointer to the top left corner of the zone that you want to select. When you press and drag, a chartreuse rectangle is painted. On top of the rectangle, the width and height of the current region are displayed. The rectangle covers the zone that will be uploaded to Flickr.

The class SelectRegionRectangle is a CustomNode composed of a main Rectangle, a Text, and a small Rectangle to help visualize the width and height label.

The tricky part of developing this Selection Rectangle is to retrieve the absolute coordinates (within the screen) of the Rectangle. To do so, you need to obtain the Stage coordinates that are absolute to the screen, add the Stage.scene coordinates to remove the window decoration, and finally, add the Rectangle coordinates. This work is performed by using the selectionRectangle method.

NB:If no zone has been selected, the displayed image is uploaded or saved.

Saving the Image to Disk

An image is saved by clicking the Save To Disk button. A javax.swing.JFileChooser is displayed, enabling you to provide the file to store the image to. If the file already exists, its content is rewritten. After the image is saved, the main window is closed.

Uploading the Image to Flickr

To be able to upload images to Flickr, you need a valid Flickr API Key, a Shared Secret, and an Authentication Token. To better understand Flickr Web Services, you should read the Flickr online documentation on API usage.

You can provide these credentials in the upload pop-up window (displayed after you clicked flickr Upload. If you want to persist these credentials, you can update the following definitions (located in the ScreenshotMaker.fx file):

Source Code
        var SECRET:String = "<Your Shared Secret>";
        var API_KEY:String = "<Your API Key>";
        var AUTH_TOKEN:String = "<Your Authentication Token>";
    

When you press flickr Upload, a window is displayed that enables you to provide tags and credentials. Click the Upload button and the image is uploaded to your account. When the upload is terminated, the main window is closed. In case a failure occurs during the upload, an error message is displayed underneath the Flickr Upload button and the main window is not closed.

Java FX offers a new API to handle RestFul Web Services. This API is located in javafx.io.http package. This event-driven API enables you to link your graphical components to your HTTP requests. In this case, a FlickrPhotoUpload class has been defined that handles request sending and translates the received response by a success status, as shown in Figure 1. All the HttpRequest class offers is a done on the replace trigger that you can use to associate the HTTP response status with the UI.

Source Code
        // Construct an instance in charge to send the upload request.
        // User inputs are provided.
        var req = FlickrPhotoUpload {
                api_key:api_key.text
                secret:api_secret.text
                auth_token:auth_token.text
                tags:tags.text
                file:file
                type:"jpg"
                // Link the response with the status of the graphical elements.
                override var done on replace {
                    if(done) {
                        enable();
                        if(not success) {
                            status = "Upload failed";
                        } else {
                            widgetFrame.visible = true;
                            f.visible = false;
                        }
                    }
                }
            }
        // Enqueue the request to process it.
        req.enqueue();
    

Figure 1: Sending the Flickr Upload Request

Tip:The best place to set the request HTTP headers is the enqueue method. You need your subclass to override enqueue. For example, see Figure 2.

Source Code
        override function enqueue(): Integer {
            content = Flickr.computePhotoUploadContent(api_key,
                secret,
                auth_token,
                tags,
                file,
                type);
            setHeader("Content-Type", content.contentType);
            setHeader("Content-Length", String.valueOf(content.content.length));
            super.enqueue();
        }
    

Figure 2: Setting the Request HTTP Headers

Controlling Event Order With java.lang.FX.deferAction

This sample highlights the cases for which events must be ordered. The chartreuse selection rectangle must be hidden before the rectangle is captured. The black "waiting" rectangle must be displayed after the rectangle is captured. The Flickr upload processing must be operated outside the main Java FX UI thread. The method java.lang.FX.deferAction enables you to run a task after the current event is terminated. It is similar to calling SwingUtilities.invokeLater.

Source Code
        // Capture the screenshot and display the Retouch window after the current
        // task is terminated.
        FX.deferAction(function() {
            captureAndRetouch();
            });
        }
    

Capturing a New Screenshot

Click the Back to Capture button to close the main window and make the "single-button" window appear again at the bottom of your screen. Closing the main window also ensures that this "single-button" window is displayed.