Jim Lawless' Blog


Screen Captures with Java and Clojure

Originally published on: Fri, 14 Aug 2009 02:38:36 +0000

Like a fair number of other developers, I am trying to acclimate myself to programming in one or more dialects of Lisp. I've recently begun to toy with Clojure, a modern Lisp that runs on the Java Virtual Machine.

Before really engaging in some sort of all-out project in Clojure, I thought that I could ease my way into the language by attempting to use it as an embedded scripting language for a simple Java application. I looked through some of the online documentation on Java/Clojure interoperability. The task didn't seem to be too much to attempt in a short amount of time.

I decided to first create a small Java program that performs a simple task in a controlled loop. This program took shape as a small screen-capture utility.

ScrCapture.java


// Screen Capture Example
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.


import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.*;
import java.io.*;

public class ScrCapture {
   public static void main(String[] arg) {
      Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
      int i;
        
      try {
         //create the Robot object
         Robot robot = new Robot();
            
         //capture the screen
         for(i=0;i<10;i++) {
            BufferedImage image = robot.createScreenCapture(
               new Rectangle(0, 0, (int)screenDim.getWidth(),
                  (int)screenDim.getHeight()));
            File fp=new File("capture_" + i + ".jpg");
            ImageIO.write(image,"jpg",fp);
            Toolkit.getDefaultToolkit().beep();
            System.out.println("Captured as capture_" + i + ".jpg");
               // Sleep 10 seconds after
            Thread.sleep(10000);
         }
      }
      catch(Exception e) {
         System.err.println(e);
      }
   }
}

The above program iterates ten times, incrementing a counter from zero to nine inclusively. The entire screen dimensions are retrieved. Then, the entire screen is captured into a file with the iteration number in the file. A beeping sound is generated followed by a ten second delay before iterating through the loop again.

If you execute the above program, you should see the following output: java ScrCapture Captured as capture_0.jpg Captured as capture_1.jpg Captured as capture_2.jpg Captured as capture_3.jpg Captured as capture_4.jpg Captured as capture_5.jpg Captured as capture_6.jpg Captured as capture_7.jpg Captured as capture_8.jpg Captured as capture_9.jpg I wanted to move all of the different elements of the above ... the number of iterations, the sound, the delay, the image filename, the image dimensions ... into an external Clojure script.

The resulting Java program is as follows:

ScreenScript.java


// ScreenScript
// Screen capture with Clojure scripting facility
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

package com.mailsend_online;

import clojure.lang.RT;
import clojure.lang.Var;

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.*;
import java.io.*;

public class ScreenScript {

   public static void main(String[] args) throws Exception {
      
      int i,max;
      // Objects that will abstract the Clojure function we'll call later in the script.
      Var captureLoop,maxIterations;

if(args.length<1) {
         System.err.println("Syntax: java ScreenScript filename.clj\n");
         System.exit(0);
      }
         // Load the specified script and initialize the Clojure
         // run-time environment
      RT.loadResourceScript(args[0]);
 
         // Look for the functions in the user namespace
      captureLoop = RT.var("user", "capture-loop");
      maxIterations = RT.var("user", "max-iterations");
                  
         // Now, replace the main loop with a call to Clojure
      try {
         max=Integer.parseInt(maxIterations.invoke().toString());
         for(i=0;i<max;i++) {
            captureLoop.invoke(i);
         }
      }
      catch(Exception e) {
         System.err.println(e);
      }
   }

      // Methods that could be called from the Clojure script
   public static int screenWidth() {
      return (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
   }

   public static int screenHeight() {
      return (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight();
   }
   
   public static void captureRectangle(String filename,
      int top, int left, int width, int height) {
      try {
         Robot robot = new Robot();
         BufferedImage image = robot.createScreenCapture(
            new Rectangle(top,left,width,height));
         File fp=new File(filename);
         ImageIO.write(image,"jpg",fp);
      }
      catch(IOException e) {
         System.err.println(e);
      }
      catch(AWTException e) {
         System.err.println(e);
      }
   }
   
   public static void beep() {
        Toolkit.getDefaultToolkit().beep();
    }
    
   public static void sleep(int millis) {
      try {
         Thread.sleep(millis);
      }
      catch(InterruptedException e) {
         System.err.println(e);
      }
   }
}

One of the first things I'd like to note is that I included a package definition in ScreenScript.java. I didn't have a formal package name for ScrCapture.java and all worked well. I ran into some issues in Clojure with the . special form.

I tried to use the . special form to invoke static methods I'd provided in the ScreenScript class from the Clojure script. An example would be this call to the sleep method I provided:

(. com.mailsend_online.ScreenScript sleep 2000 )

Until I placed the ScreenScript class in a formal package, Clojure had real issues finding the class. I tried various classpath changes to no avail.

Onward...

One goal in writing ScreenScript was to provide a series of static methods available for use by the Clojure script. I provided the methods screenWidth(), screenHeight(),captureRectangle,sleep(),and beep(). The Clojure script would mimic the main loop from the ScrCapture.java.

Although I had intended to create a loop in Clojure to perform all of the screen capture operations, I found that I really need practice with Clojure's flow-control facilities. This is one of the areas of Lisp where I hope to grasp the beauty of recursion to control iterations. I made a few small attempts to no avail, so I ended up keeping a for-loop in the ScreenScript program.

To compile the above program, make sure that clojure-1.0.0.jar is in your classpath.

ScreenScript first loads the Clojure script specified on the command-line. The method RT.loadResourceScript() loads the specified script and also initializes the Clojure runtime environment.

After the script is loaded, two Var objects are initialized ( captureLoop and maxIterations ) so that I can invoke their like-named functions in the specified Clojure script.

The code invokes maxIterations first to determine the number of times to loop. This is one area of the code that I'd like to change so that it will, in a future incarnation, leverage Clojure's flow-control.

In the loop body, the Clojure method capture-loop is invoked.

Please consider the following Clojure script that is used in conjunction with ScreenScript:

cap.clj


(ns user)

(defn capture-loop [i]
   (println (str "Capture number " i))
   (. com.mailsend_online.ScreenScript captureRectangle (str "cap_" i ".jpg") 0 0 400 200)
   (. com.mailsend_online.ScreenScript beep )
   (. com.mailsend_online.ScreenScript sleep 2000 ))
   

(defn max-iterations []
   10)

The call to max-iterations in the above script yields a value of ten. I don't yet know how to simply make a symbol that I can read, so I left this mechanism in the form of a function.

The capture-loop function accepts on parameter named i. The calling Java program passes in the iteration number ( relative to zero ). The capture-loop function uses i to form up the name of the output file.

Note that in the Clojure script above, we only capture a 400 by 200 pixel section of the screen, we use a different file naming convention option for shorter names, we only delay 2 seconds between each iteration, and we output a different message that does not contain the generated filename.

The output from the above might look like this:

java com.mailsend_online.ScreenScript cap.clj Capture number 0 Capture number 1 Capture number 2 Capture number 3 Capture number 4 Capture number 5 Capture number 6 Capture number 7 Capture number 8 Capture number 9

If we wanted to completely replicate the functionality of ScrCapture.java, we could make calls to screenWidth and screenHeight so that we capture all of the screen and could change all of the Clojure code to parallel the parameters used in the original program.

The primary goals I had were to learn how to call a Clojure function from within Java and also to learn how to call a Java method from within Clojure. The resulting Clojure host isn't quite perfect yet, but I'll be exercising Java/Clojure interoperability again in the near future.

Unless otherwise noted, all code and text entries are Copyright ©2009 by James K. Lawless



Views expressed in this blog are those of the author and do not necessary reflect those of the author's employer. Views expressed in the comments are those of the responding individual.

stumbleupon Save to StumbleUpon
digg Digg it
reddit Save to Reddit
facebook Share on Facebook
twitter Share on Twitter
aolfav More bookmarks


Previous post: A Command Line Scheduler
Next post:Java in a Windows EXE with launch4j


About Jim ...


Click **here**
to try out MailWrench;
a command-line SMTP /
SMTPS (Google Gmail)
mailer for Windows.


Follow me on Twitter

http://twitter.com/lawlessGuy


Recent Posts

A JavaScript REPL for Android Devices

MailSend is Free

My Blog Engine

The October 10th Bug

A Review of Kevin Mitnick's Book Ghost in the Wires

Spellbound by Web Programming

Backlinks to my Blog Posts

Play MP3 Files with Python on Windows


Random Posts

Send GMail From the Windows Command-Line with MailWrench

An Interview with Brad Templeton

A JavaScript REPL for Android Devices

Thwarting HTTP Referer Trackbacks

E-mail cleansing

Mad Schemes : Learning Lisp via SICP

Structuring my Thinking

A Quine in Forth

A Command-Line CD Tray Opener

A Command Line Scheduler


Full List of Posts

http://www.mailsend-online.com/bloglist.htm


Recent Posts from my Other Blog

Remembering Dr. San Guinary

Why Some Web Sites will go Dark on Jan 18th

SNL Superhero Skit

More Ruby Games

My Ruby Game Challenge Entry

Steal this Bookmarklet

Nerd Toys

Learn New Jargon, You Must

Spot the Wiebe

Tech Magazine Glory Days

Book Review : Paull Allen - Idea Man

A 90's Experiment in Online Systems - The U.S. West CommunityLink Service