Sunday 15 March 2015

Capturing Screenshots from the Command Line using Java

A few weeks ago I spent most of a day trying to work out how to capture a screen shot from the command line on Windows.  MS don't provide anything to do this.  You can manually [Prtscr], open Paint, Ctrl-V, Save As, but you can't automate it.  And you can't seem to automate the SnippingTool - or even run it from a script.  There are tools you can download from dodgy websites, but I don't want to do that.

It was a bit annoying to realize that I should have started with Java.  The follow captures all attached screens with JDK 1.7:
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class Snapshot {

    public static void main(String[] args) throws Exception {
        Robot robot = new Robot();
        GraphicsEnvironment ge =
                GraphicsEnvironment.getLocalGraphicsEnvironment();
        int screenNumber = 1;
        for(GraphicsDevice gd : ge.getScreenDevices()){
            DisplayMode dm = gd.getDisplayMode();
            Rectangle rect = new Rectangle(dm.getWidth(),
                                              dm.getHeight());

            BufferedImage image = robot.createScreenCapture(rect);
            File f = new File("screen" + screenNumber + ".png");
            ImageIO.write(image, "PNG", f);
        }
    }
}

Using Maven and Eclipse with Native Shared Libraries

Looking around the web I found that Maven can manage native shared libraries to an extent. It can allow you to package up .dll or .so files as dependencies and put them in jars in your repo. 

However, to be useful shared libraries have to be unpacked to disk and have their original name, before they can be loaded when your java program or tests run. Some clever people have ignored the maven "dll" classifier and the "so" type, and written code to unpack their libraries from the applications own jars into temporary directories on disk on start up.

I have yet to see a way to consistently arrange for shared libraries to be unpacked when my code is deployed in different target environments.  I might want to use it from within a WAR deployed into a Tomcat server running in Linux within the cloud.

When you come to deploy on unix like systems, the platform will have its own system for managing natives, and trying to do this in your maven pom files will be a wasted effort.

So my approach is to assume that the shared natives will be available in the target environment, and I will arrange for them to be there separately.  I will also do the same for my maven build, and provide an environment variable to tell the build where to find them.  Maven will also set up my Eclipse workspace.

I am using OpenCV 3.0.0-beta which has a number of shared native libraries and a JNI wrapper jar to allow access to them.  If you don't have a JNI jar, then you may need to create an empty one.

In my project, I have a parent pom and a sub-project that wraps up my dependency on OpenCV.  The parent pom provides a check that the required set-up has been done, and sub-projects will use this as appropriate.  If the required environment variable is missing - it outputs a message telling the user what to do.
<project ...>

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.yourcompany.product</groupId>
  <artifactId>parent</artifactId>
  <packaging>pom</packaging>
  <version>${product-version}</version>

  <modules>
    <module>opencv</module>
    ...
  </modules>

  <properties>
    <product-version>0.1-SNAPSHOT</product-version>
    ...
    <opencv-version>3.0.0-beta</opencv-version>
    <opencv-libraries>${env.OPENCV_LIBRARIES}</opencv-libraries>
    <eclipse-opencv-libname>OPENCV-3.0.0-BETA</eclipse-opencv-libname>
  </properties>

  <build>

    <plugins>
      ...
    </plugins>

    <pluginManagement>
      ...
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-enforcer-plugin</artifactId>
          <executions>
            <execution>
              <id>check-opencv-libraries</id>
              <goals>
                <goal>enforce</goal>
              </goals>
              <configuration>
                <rules>
                  <requireEnvironmentVariable>
                    <variableName>OPENCV_LIBRARIES</variableName>
                    <message><![CDATA[
OPENCV_LIBRARIES
================
The OPENCV_LIBRARIES environment variable needs to be set to the directory
containing the OpenCV dynamic shared libraries required for your platform.

You can download these from http://opencv.org/downloads.html.
These may need to be built for some platforms.

For example on 64bit Windows you might create the directory:
  C:\tools\opencv\3.0.0-beta\x64
and copy the following DLL files from the distribution to it:
  opencv\build\java\x64\opencv_java300.dll
  opencv\build\x64\vc12\bin\opencv_world300.dll
  opencv\build\x64\vc12\bin\opencv_ffmpeg300_64.dll

On Debian Linux you may install the libraries with:
  sudo apt-get install opencv
and set OPENCV_LIBRARIES to /usr/shared/lib or wherever.

OpenCV JNI jar
==============
The prebuilt OpenCV JNI jar for these dynamic libraries can be found in the
Windows distribution and put into your local maven repository with the command:

  mvn install:install-file -Dfile=opencv/build/java/opencv-300.jar \
     -DgroupId=org.opencv -DartifactId=opencv-java-api  \
     -Dversion=3.0.0-beta -Dpackaging=jar

Eclipse Setup
=============
Go to Window -> Preferences -> Java -> Build Path -> User Libraries
Create a new library called ${eclipse-opencv-libname}
Add the external jar from your local repo (opencv-java-api-3.0.0-beta.jar).
Expand the library and jar, highlight "Native library location", then "Edit"
and set it to the location of the OPENCV_LIBRARIES directory.
]]>
                    </message>
                  </requireEnvironmentVariable>
                </rules>
              </configuration>
            </execution>
          </executions>
        </plugin>

        <!-- Makes the natives available to unit tests -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <configuration>
            <argLine>-Djava.library.path=${opencv-libraries}</argLine>
          </configuration>
        </plugin>

    </pluginManagement>
  </build>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.yourcompany.product</groupId>
        <artifactId>opencv</artifactId>
        <version>${product-version}</version>
      </dependency>

      <!-- See note above concerning OPENCV_LIBRARIES -->
      <dependency>
        <groupId>org.opencv</groupId>
        <artifactId>opencv-java-api</artifactId>
        <version>${opencv-version}</version>
      </dependency>

      ...
    </dependencies>
  </dependencyManagement>

</project>
Then in the opencv sub-project you need to use the rule enforcer, and supplement the Eclipse classpath generated by "mvn eclipse:eclipse".
<project ...>

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.yourcompany.product</groupId>
    <artifactId>parent</artifactId>
    <version>${product-version}</version>
  </parent>

  <artifactId>opencv</artifactId>
  <packaging>jar</packaging>

  <build>
    <plugins>
      <!-- Enforce parent rules about libraries being available -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
          <!--
             In eclipse we use the opencv JNI jar via a user library,
             which will include the native shared libraries.
          -->
          <excludes>
            <exclude>org.opencv:opencv-java-api</exclude>
          </excludes>
          <classpathContainers>
            <classpathContainer>
              org.eclipse.jdt.launching.JRE_CONTAINER
            </classpathContainer>
            <classpathContainer>
              org.eclipse.jdt.USER_LIBRARY/${eclipse-opencv-libname}
            </classpathContainer>
          </classpathContainers>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.opencv</groupId>
      <artifactId>opencv-java-api</artifactId>
    </dependency>
    ...
  </dependencies>

</project>
Now when you build the sub-project, it will fail and give you instructions on how to set up the native libraries.  You should be able to follow these instructions yourself to do the one-off install of the native shared libraries and Eclipse workspace set up.