ScreenshotMaker: Capture, Edit, and Upload a Screen to Flickr or Save It to Disk
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.
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.
timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames :
KeyFrame {
time : 1000ms
action: function() {
currentToolTip.visible = true;
}
}
};
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.
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:
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.
Line {
transforms: Rotate{angle: angle pivotX:line.endX pivotY:line.endY }
...
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:
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):
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.
// 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.
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.
// 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.
Jean-Francois DeniseSoftware Engineer,
Sun Microsystems