Creating Game Screen Snapshots

If the visible screen was accessible as some kind of system Java object (System, Runtime) and such a hypothetical class implemented the Serializable interface, then taking screen snapshots could be handled in exactly the same way as taking snapshots of any other Java objects: via serialization. Unfortunately, Java does not map the screen to a convenient object, so serialization can't help with your screen snapshots requirement. With 1.3.0 came a new class in the AWT package, Robot. This, at first sight, oddly named class contains the solution to your screenshot requirement:

public BufferedImage createScreenCapture(Rectangle screenRect)


This method isn't static, as you might expect, so you need to first instantiate a Robot using one of the two available Robot constructors:

public Robot() throws AWTException public Robot(GraphicsDevice screen) throws AWTException


The possibly thrown AWTExceptions neatly help explain why Robot is called "Robot": This class was designed to help automated testing of GUIs. If you consult Robot's complete API (via the online Java API documentation), you'll see that the API allows a program to trigger GUI input (key and mouse events) as if they were generated by a human interacting with a physical keyboard and mouse (you used the Robot class to implement mouselook in , "Interactivity and User Interfaces"). The createScreenCapture() method was never designed to support games development, but it was included to allow an automated GUI testing framework to visually check that programmed user interface inputs produce the required graphical response from the GUI. So, Robot is supposed to represent a GUI-testing robot (presumably little more than a mechanical arm, in this case) that can hit keys, move the mouse about, and view the results of these actions. So, what about those AWTExceptions in Robot's constructor signatures? Well, unfortunately, not all host platforms support the direct injection of fake input events into their GUI event subsystem. On these systems, attempting to instantiate a Robot will fail with an AWTException. Now that we've explained the purpose of and legitimacy behind the Robot name, let's call createScreenCapture() to satisfy ourselves that it does what it says on the label. The GrabScreen example shown in Listing 15.4 contains the following grabScreen() method.

Listing 15.4 grabScreen() Method
/*************************************************************************
 * Grab the visible screen
 *
 * @return a BufferedImage containing the specified screen sub-rectangle
*************************************************************************/
private BufferedImage grabScreen(Rectangle screenRect) {
 Robot robot = null;
 try {
 robot = new Robot();
 } catch (AWTException awtException) {
 System.out.println(awtException);
 return null;
 }
 BufferedImage screenImage = robot.createScreenCapture(screenRect);
 return screenImage;
}


If you run the demo, you'll see your screen being overlayed with a darkening filter effect. This illusion is achieved by making a copy of the original screen, via the previous grabScreen() method, and then overwriting the visible screen with pixels that have had their intensity halved (red, green, and blue components divided by two; see Listing 15.5).

Listing 15.5 Halving the Intensity of RGB
screenImage = grabScreen(screenRect);
darkenImage(screenImage);
...
/*************************************************************************
 * Overridden Component.paint()
 *
 * @param g the Graphics context to paint to.
*************************************************************************/
public void paint(Graphics g) {
 if ( screenImage != null ) {
 g.drawImage(screenImage, 0,0, null);
 }
}
/*************************************************************************
 * Darkens a BufferedImage
*************************************************************************/
public void darkenImage(BufferedImage image) {
 int width = image.getWidth();
 int height = image.getHeight();
 for (int y=0; y < height; y++) {
 for (int x=0; x < width; x++) {
 int color = image.getRGB(x,y);
 color = color >> 1;
 color = color & 0x7F7F7F;
 image.setRGB(x,y, color);
 }
 }
}
}


Clearly, making copies of the screen is a no-brainer on any Java platform that supports a full Robot implementation. The next goal is to transform this screen snapshot into a thumbnail image.

Creating Screen Thumbnail Images

Given a BufferedImage holding a full-screen snapshot of the current game screen, the next goal is to transform this image into a thumbnail image file. This comprises two subtasks: resizing the original image and saving the resulting thumbnail as either a GIF, a PNG, or a JPEG file. To resize an image, you can draw a scaled version of the image onto a new thumbnail-size image, like this:

BufferedImage screenThumbnail = new BufferedImage(width, height,
 BufferedImage.TYPE_INT_RGB);
Graphics2D g = screenThumbnail.createGraphics();
g.drawImage(screenImage, 0, 0, width, height, null);
g.dispose();


Here, width and height define the size of the thumbnail. Scaling from a large image to a small one could look rather pixelated using the default rendering hints, however. You can use bilinear interpolation, described in , "Texture Mapping and Lighting," to help smooth out the resulting thumbnail so it doesn't look so pixilated. Just set the interpolation rendering hint for the Graphics2D object before you scale the image:

g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
 RenderingHints.VALUE_INTERPOLATION_BILINEAR);


Now that you have a thumbnail, you need a way to save it to disk.

Saving an Image

To save an image to disk, you can use the Image I/O framework. The nontrivial architecture of this framework consists of a highly pluggable system of image readers and writers (encoders and decoders), associated listeners, and "service providers." Luckily for your immediate needs, you do not need to learn the inner workings of this framework to save a few JPEGs. The javax.imageio root package of the Image I/O API contains a utility class called ImageIO that features some high-level static convenience methods to save images to various destinations, in easily selectable image formats:

public static boolean write(RenderedImage im, String formatName, File output)
public static boolean write(RenderedImage im, String formatName, OutputStream output)
public static boolean write(RenderedImage im, String formatName, ImageOutputStream output)


All three methods return a boolean indicating whether the image-write operation succeeded; they can also throw an IOException if any operation failure is due to an I/O problem. These methods' RenderedImage argument is not the same as the Image type, but it's close: BufferedImage implements the RenderedImage interface, and your thumbnail is a BufferedImage, so you have what you need to save the image thumbnail. The formatName argument needs to be a simple String such as jpeg, png, or gif. With the help of the previous write() methods, saving a single thumbnail becomes the following piece of code:

File snapshotFile = new File( .. );
try {
 ImageIO.write(thumbnailImage, "jpeg", snapshotFile);
} catch (Exception exception) {
 // appropriate exception handling here
}


By default, this saves the image as a medium-quality JPEG file. Putting all the previous pieces together, you obtain the following refreshingly high-level saveScreenSnapshotAsThumbnail() routine to save game screen snapshots as JPEG thumbnail image files (see Listing 15.6).

Listing 15.6 saveScreenSnapshotAsThumbnail() Method
/*************************************************************************
 * Grab the visible screen, turn into thumbnail, and save as JPEG.
 *
 * @param screenRect the Rectangle defining the screen
 * @param thumbnailDimension the desired thumbnail Dimension
 * @param thumbnailFilename the generated JPEG filename
*************************************************************************/
private void saveScreenSnapshotAsThumbnail( Rectangle screenRect,
 Dimension thumbnailDimension,
 String thumbnailFilename) {
 BufferedImage screenImage = grabScreen(screenRect);
 BufferedImage screenThumbnail = new BufferedImage(
 thumbnailDimension.width,
 thumbnailDimension.height,
 BufferedImage.TYPE_INT_RGB);
 // scale the image
 Graphics2D g = screenThumbnail.createGraphics();
 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 g.drawImage(screenImage, 0, 0,
 thumbnailDimension.width,
 thumbnailDimension.height, null);
 g.dispose();
 try {
 ImageIO.write(screenThumbnail, "jpeg", new File(thumbnailFilename));
 } catch (Exception ignored) {
 System.out.println("OOPS " + ignored);
 }
}


   
Comments