Monday, August 2, 2010

Screen Snapshots with Java's Robot

One of the Java programming language's advantages that I really appreciate is its rich set of standard libraries in the SDK.  I use some of these all the time and use others less frequently, but still appreciate these less used ones when they are needed.  In this blog post, I look at a feature that I don't use often, but really appreciate when I do need it: the ability to take screen snapshots with Java's Robot class (this is the first post in which I'm using the nicer URLs with Oracle in the URL: http://download.oracle.com/javase/6/docs/api/java/awt/Robot.html).

There are numerous online resources covering the Java Robot: Introduction to the Java Robot Class in Java, How to Use Robot Class in Java, Capture the Screen, Full Screen Capture with Java, How to Take Screen Shots in Java, and many, many more.  The main code behind the screen capture is fairly simple and is shown here without try/catch blocks and other "overhead":

Main Java Code for Capturing Screen Snapshot
  1. final Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();  
  2. final Rectangle screenRectangle = new Rectangle(screenDimension);  
  3. final Robot robot = new Robot();  
  4. final BufferedImage screenImage = robot.createScreenCapture(screenRectangle);  
  5. ImageIO.write(screenImage, newFileFormat, new File(newFileName));  

I'll include a much lengthier code listing shortly that wraps this code with a "try" block, allows a delay to be set before taking the screen snapshot, and provides basic command-line user interaction. However, the "meat" of this is all available in the five lines shown above.

I have named the full class JavaRobotExample. Its code listing is shown next.

JavaRobotExample.java
  1. package dustin.examples;  
  2.   
  3. import java.awt.AWTException;  
  4. import java.awt.Dimension;  
  5. import java.awt.Rectangle;  
  6. import java.awt.Robot;  
  7. import java.awt.Toolkit;  
  8. import java.awt.image.BufferedImage;  
  9. import java.io.File;  
  10. import java.io.IOException;  
  11. import java.util.Arrays;  
  12. import java.util.List;  
  13. import javax.imageio.ImageIO;  
  14.   
  15. import static java.lang.System.err;  
  16. import static java.lang.System.out;  
  17.   
  18.   
  19. /** 
  20.  * Simple class demonstrating the general utility of the java.awt.Robot class 
  21.  * in capturing screen snapshots. 
  22.  * 
  23.  * @author Dustin 
  24.  */  
  25. public class JavaRobotExample  
  26. {  
  27.    /** Collection of informal file format names understood by registered readers. */  
  28.    private final static List<String> INFORMAL_FILE_FORMAT_NAMES;  
  29.   
  30.    /** Operating system independent new line. */  
  31.    private final static String NEW_LINE = System.getProperty("line.separator");  
  32.   
  33.    /** Default delay in milliseconds. */  
  34.    private final static int DEFAULT_DELAY_MS = 2500;  
  35.   
  36.    /** Default filename base when no filename base is provided. */  
  37.    private final static String DEFAULT_FILE_NAME_BASE = "screenshot";  
  38.   
  39.    /** Default file format for generated image file used when none is provided. */  
  40.    private final static String DEFAULT_INFORMAL_FILE_FORMAT_NAME;  
  41.   
  42.    static  
  43.    {  
  44.       INFORMAL_FILE_FORMAT_NAMES = Arrays.asList(ImageIO.getReaderFormatNames());  
  45.       DEFAULT_INFORMAL_FILE_FORMAT_NAME =  
  46.            isFormatRecognized("png")  
  47.          ? "png"  
  48.          : provideSingleInformalFileFormatName();  
  49.    }  
  50.   
  51.    /** 
  52.     * Capture snaphot of the current screen and write it to an output file with 
  53.     * the provided file name. 
  54.     * 
  55.     * @param newFileName Name of file (without prefix) to which screen snapshot 
  56.     *    should be written. 
  57.     * @param newFileFormat The informal file format name to be used to specify 
  58.     *    the format of the file the screen snapshot is written to. 
  59.     * @param newDelayInMs Delay (measured in milliseconds) that should be employed 
  60.     *    before taking screen snapshot to allow screen adjustment (such as 
  61.     *    closing the terminal in which this application is executed). 
  62.     */  
  63.    public static void captureScreenShot(  
  64.       final String newFileName, final String newFileFormat, final int newDelayInMs)  
  65.    {  
  66.       final Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();  
  67.       final Rectangle screenRectangle = new Rectangle(screenDimension);  
  68.       try  
  69.       {  
  70.          final Robot robot = new Robot();  
  71.          robot.delay(newDelayInMs);  
  72.          final BufferedImage screenImage = robot.createScreenCapture(screenRectangle);  
  73.          ImageIO.write(screenImage, newFileFormat, new File(newFileName));  
  74.       }  
  75.       catch (AWTException awtEx)  
  76.       {  
  77.          if (System.console() == null)  
  78.          {  
  79.             err.println("Not supported for headless console - " + awtEx.toString());  
  80.          }  
  81.          else  
  82.          {  
  83.             err.println("Not supported for this environment - " + awtEx.toString());  
  84.          }  
  85.       }  
  86.       catch (IOException ioEx)  
  87.       {  
  88.          err.println(  
  89.               "Unable to write screen shot to file " + newFileName + " - "  
  90.             + ioEx.toString());  
  91.       }  
  92.    }  
  93.   
  94.    /** Print the supported informal file format names to standard output. */  
  95.    public static void printInformalFileFormatNames()  
  96.    {  
  97.       for (final String formatName : INFORMAL_FILE_FORMAT_NAMES)  
  98.       {  
  99.          out.println(formatName);  
  100.       }  
  101.    }  
  102.   
  103.    /** 
  104.     * Provide a single informal file format name that is supported by my 
  105.     * readers. 
  106.     * 
  107.     * @return Single informal file format name support by my readers; may be 
  108.     *    empty String if I don't have any valid informal file format names. 
  109.     */  
  110.    public static String provideSingleInformalFileFormatName()  
  111.    {  
  112.       return  INFORMAL_FILE_FORMAT_NAMES != null && !INFORMAL_FILE_FORMAT_NAMES.isEmpty()  
  113.             ? INFORMAL_FILE_FORMAT_NAMES.get(0)  
  114.             : "";  
  115.    }  
  116.   
  117.    /** 
  118.     * Indicates whether the provided String format represents a valid informal 
  119.     * file format name for my registered readers. 
  120.     * 
  121.     * @param candidateInformalFormatName String of possible file format. 
  122.     * @return {@code true} if the provided String represents a valid informal 
  123.     *    file format name for my registered readers. 
  124.     */  
  125.    public static boolean isFormatRecognized(final String candidateInformalFormatName)  
  126.    {  
  127.       return INFORMAL_FILE_FORMAT_NAMES.contains(candidateInformalFormatName);  
  128.    }  
  129.   
  130.    /** 
  131.     * Extracts file name and format information from the provided arguments, 
  132.     * assumed to have come from the command-line or other source.  It is also 
  133.     * assumed that the provided array of Strings has two elements with the first 
  134.     * String representing the base of the file name (no extension) and the 
  135.     * second representing the file format.  If either the base file name or the 
  136.     * format is not specified, defaults are used for both file name and format. 
  137.     * 
  138.     * @param arguments Strings with first String representing the base file name 
  139.     *    (no extension) and the second String representing the file format. 
  140.     * @return Two Strings with the first String representing the file name to be 
  141.     *    written to (without extension and may be the same as provided) and the 
  142.     *    second String representing a valid informal file format name. 
  143.     */  
  144.    public static String[] extractFileNameAndFileFormat(final String[] arguments)  
  145.    {  
  146.       String fileNameBase;  
  147.       String fileFormat;  
  148.       if (arguments.length < 2)  
  149.       {  
  150.          out.println(  
  151.               "If specified arguments are to be used, both must be specified."  
  152.             + NEW_LINE);  
  153.          out.println(  
  154.               "Because either file name or file format was not specified, "  
  155.             + "defaults are used for both ('" + DEFAULT_FILE_NAME_BASE  
  156.             + "' for the generated base filename of file format "  
  157.             + DEFAULT_INFORMAL_FILE_FORMAT_NAME + " with delay of "  
  158.             + DEFAULT_DELAY_MS + ")." + NEW_LINE + NEW_LINE  
  159.             + "To explicitly specify them, provide them as command-line arguments:"  
  160.             + NEW_LINE  
  161.             + "     dustin.examples.JavaRobotExample <<file_name_base>> <<file_format>> <<delay_ms>>"  
  162.             + NEW_LINE + NEW_LINE  
  163.             + "where file_name_base is name of generated image file without its suffix "  
  164.             + "and file_format is the image format ('PNG', 'GIF', 'JPG', etc.).");  
  165.          fileNameBase = DEFAULT_FILE_NAME_BASE;  
  166.          fileFormat = DEFAULT_INFORMAL_FILE_FORMAT_NAME;  
  167.       }  
  168.       else  
  169.       {  
  170.          fileNameBase = arguments[0];  
  171.          final String candidateFileFormat = arguments[1];  
  172.          fileFormat =  isFormatRecognized(candidateFileFormat)  
  173.                      ? candidateFileFormat  
  174.                      : DEFAULT_INFORMAL_FILE_FORMAT_NAME;  
  175.       }  
  176.       return new String[] {fileNameBase, fileFormat};  
  177.    }  
  178.   
  179.    /** 
  180.     * Extract the delay in milliseconds from the provided arguments. 
  181.     * 
  182.     * @param arguments Arguments, most likely from the command line, that are 
  183.     *    expected to include the number of milliseconds of delay before screen 
  184.     *    capture as the third argument. 
  185.     * @return The extract delay in milliseconds before screen capture should 
  186.     *    take place. 
  187.     */  
  188.    public static int extractDelayInMilliseconds(final String[] arguments)  
  189.    {  
  190.       final int defaultDelayInMs = DEFAULT_DELAY_MS;  
  191.       int requestedDelayInMs;  
  192.       if (arguments.length > 2)  
  193.       {  
  194.          try  
  195.          {  
  196.             requestedDelayInMs = Integer.valueOf(arguments[2]);  
  197.             if (requestedDelayInMs < 0 || requestedDelayInMs > 60000)  
  198.             {  
  199.                requestedDelayInMs = defaultDelayInMs;  
  200.                err.println(  
  201.                     "Specified delay of " + requestedDelayInMs  
  202.                   + " is NOT between 0 and 60000 ms; setting to " + defaultDelayInMs);  
  203.             }  
  204.          }  
  205.          catch (NumberFormatException nfe)  
  206.          {  
  207.             requestedDelayInMs = defaultDelayInMs;  
  208.             err.println(arguments[2] + " is not a valid number of milliseconds for a delay.");  
  209.          }  
  210.       }  
  211.       else  
  212.       {  
  213.          requestedDelayInMs = defaultDelayInMs;  
  214.       }  
  215.       return requestedDelayInMs;  
  216.    }  
  217.   
  218.    /** 
  219.     * Main executable method. 
  220.     *  
  221.     * @param arguments Command-line arguments: none expected. 
  222.     */  
  223.    public static void main(final String[] arguments)  
  224.    {  
  225.       final String[] fileNameAndFormat = extractFileNameAndFileFormat(arguments);  
  226.       final String fileFormat = fileNameAndFormat[1];  
  227.       final String fileName = fileNameAndFormat[0] + "." + fileFormat.toLowerCase();  
  228.       final int delayInMs = extractDelayInMilliseconds(arguments);  
  229.       captureScreenShot(fileName, fileFormat, delayInMs);  
  230.    }  
  231. }  

The above code listing is for the complete class. When this class is built, its main() function can be run to take a screen snapshot. This is demonstrated in the next two images. The first image is a snapshot of the DOS terminal in which the Java class is being executed. The second image is the actual screenshot snapped by the Java application when I left the terminal open until the screen snapshot was taken.


The image above shows that the defaults were used and the screen snapshot was captured as a file called screenshot.png.  The next image shows the actual captured screen shot:


Without the delay, or if the console was not closed before the delay, the screen snapshot includes the console.  Because this is seldom desirable, the delay was added to give a sufficient amount of time to close the console and get it out of the picture.  When this is done, the screen snapshot taken appears like the example shown next (run with the format of JPG rather than PNG).


With a 3 second delay (not shown here) I had plenty of time to minimize the console before the screen snapshot took place.

Taking screen shots with Java is pretty cool, but the Robot class can do much more than that.  The Javadoc for the Robot class states: "The primary purpose of Robot is to facilitate automated testing of Java platform implementations."  The class supports this via screen snapshot and delay functionality as demonstrated here along with support for pressing a key (and releasing) and handling a mouse (move, press, release).

No comments: