私の歴史と今

振り返ると恥ずかしくなるのが私の歴史。だけどそのときは真面目に書いていた訳でね。そんな今の私を書いていく。

Javaで定期画面キャプチャアプリを作る

java.awt.Robotを使えば簡単にスクリーンキャプチャを取れるようなので、簡単なアプリを作ってみた。

    1. 画面キャプチャ
    2. ファイル出力
package capture;

import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.imageio.ImageIO;

public class CaptureTest {

	private static SimpleDateFormat format = new SimpleDateFormat(
			"yyyyMMdd_HHmm");

	public static void main(String[] args) {
		try {
			// キャプチャの範囲
			Rectangle bounds = new Rectangle(0, 0, 1280, 800);

			// これで画面キャプチャ
			Robot robot = new Robot();
			BufferedImage image = robot.createScreenCapture(bounds);

			// 以下、出力処理
			String dirName = "C:\\Users\\ken\\Desktop\\";
			String fileName = "test_" + format.format(new Date()) + ".jpg";
			ImageIO.write(image, "jpg", new File(dirName, fileName));

		} catch (AWTException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

これを実行すると、下記画像ファイルが作成される。

だけど難点がひとつ。それはモニタサイズを1280*800固定にしているところ。自分用だから困ることはないけど気持ち悪い。
解決方法を検索してみると、「【Java】モニタ解像度に合わせてフォームを最大化するサンプル。【Swing】」というページにGraphicsEnviromentクラスを利用するとうまくいくことが書かれていた。

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle rect = env.getMaximumWindowBounds();

この方法でRectangleインスタンスを取得した場合の画像は次のようになる。

タスクバーが見えない・・・。getMaximumWindowBoundsというメソッド名から推測すると、タスクバー部分を除外した範囲を取得するメソッドなのかな。。。便利だけど、タスクバーをキャプチャーできなければ、要件を満たせない。
やりたいことは、画面キャプチャを定期的に取得しておいて、「何時何分に自分が何をしていたか」をあとで知りたいということ。つまり工数管理するための材料が欲しいということ。タスクバーは大事な判断材料なので妥協できない。
そこでまた解決方法を検索したのだけど、簡単だった(そりゃそうだよね)。java.awt.Toolkitという超基本クラスらしきものがあり、そのgetScreenSize()メソッドで取得できるとのこと。Rectangleインスタンスを取得するコードは次のようになる。

	Rectangle bounds = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());

まずデフォルトのToolkitを取得しているが、デフォルトというのはプラットフォーム固有という意味だそうだ。私の環境であればWindows Vistaになる。また、getScreenSize()メソッドで取得できるサイズは、マルチモニタの場合は主モニタになる。

スクリーンサイズを返します。複数のディスプレイを持つシステムでは、主ディスプレイが使用されます。マルチスクリーン対応ディスプレイのサイズは GraphicsConfiguration と GraphicsDevice から入手できます。

http://java.sun.com/javase/ja/6/docs/ja/api/java/awt/Toolkit.html#getScreenSize()

マルチモニタの場合の各モニターサイズの取得方法はAPIによると次のようになるみたい。

      Rectangle virtualBounds = new Rectangle();
      GraphicsEnvironment ge = GraphicsEnvironment.
              getLocalGraphicsEnvironment();
      GraphicsDevice[] gs =
              ge.getScreenDevices();
      for (int j = 0; j < gs.length; j++) { 
          GraphicsDevice gd = gs[j];
          GraphicsConfiguration[] gc =
              gd.getConfigurations();
          for (int i=0; i < gc.length; i++) {
              virtualBounds =
                  virtualBounds.union(gc[i].getBounds());
          }
      } 

しかし、内容は理解できない。物理的に2台のモニタを接続していれば、GraphicsDevice配列の要素数は2になるはず。実際、私のディスプレイは1台だから要素数が1になった。だけど、GraphicsConfiguration配列の要素数は6個になった。これは何の数だ? すべてのサイズは1280×800になっていた。その他の設定が異なるのかな。しかもunionしてるのはなぜ? API読んでも理解できなかった。まあ、実現したいことは済んでるから放っておくか。。
あとは、定期的に実行されるようにタイマーを作るだけ。
不慣れだけど、次のようになった。
ポイント

    • TimerとTimerTaskを使用して定期的にキャプチャタスクが動くようにした。
    • 出力フォルダとキャプチャ間隔(秒数)は引数で指定。
    • 出力ファイルは「引数で指定したフォルダ\日付(yyyyMMdd)\capture_日付(yyyyMMdd)_時間(HHmm).jpg」。
    • キャプチャ画像の左上隅に「yyyy/MM/dd HH:mm:ss」形式の日時を埋め込んだ。
    • 停止方法は「java」コマンドの場合はCtrl+C、「javaw」コマンドの場合はタスクマネージャからプロセスの停止
    • クラス名は、TimerTaskを継承したのでScreenCaptureTaskに変更した。
    • JDK6.0でコンパイル。1.4で動くかどうかは不明。
package capture;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import javax.imageio.ImageIO;

public class ScreenCaptureTask extends TimerTask {

	/** ディレクトリのフォーマット */
	private static SimpleDateFormat dirFormat = new SimpleDateFormat("yyyyMMdd");
	/** ファイルのフォーマット */
	private static SimpleDateFormat fileFormat = new SimpleDateFormat(
			"yyyyMMdd_HHmmss");
	/** ファイルのフォーマット */
	private static SimpleDateFormat textFormat = new SimpleDateFormat(
			"yyyy/MM/dd HH:mm:ss");

	/** 実行メソッド */
	public static void main(String[] args) {

		// 入力チェック
		if (args.length != 2) {
			throw new IllegalArgumentException(
					"引数の数が異なります(usage:javaw capture.ScreenCaptureTask dir interval)");
		}

		// 入力チェック(1):出力先ディレクトリ
		File baseDir = new File(args[0]);
		if (baseDir.exists() == false) {
			throw new IllegalArgumentException(
					"dirが存在しません(usage:javaw capture.ScreenCaptureTask dir interval)");
		}

		// 入力チェック(2):インターバル(秒)
		int interval;
		try {
			interval = Integer.parseInt(args[1]);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(
					"intervalが整数ではありません(usage:javaw capture.ScreenCaptureTask dir interval)");
		}
		if (interval <= 0) {
			throw new IllegalArgumentException(
					"intervalが1以上の整数ではありません(usage:javaw capture.ScreenCaptureTask dir interval)");
		}

		TimerTask task = new ScreenCaptureTask(baseDir);
		Timer timer = new Timer("auto_screen_capture");
		timer.schedule(task, 2 * 1000, interval * 1000);
	}

	/** 出力ベースディレクトリ */
	private File baseDir;

	public ScreenCaptureTask(File baseDir) {
		this.baseDir = baseDir;
	}

	/** タスクの本体 */
	public void run() {
		try {
			// 日付
			Date today = new Date();

			// スクリーンサイズの取得
			Rectangle bounds = new Rectangle(Toolkit.getDefaultToolkit()
					.getScreenSize());

			// これで画面キャプチャ
			Robot robot = new Robot();
			BufferedImage image = robot.createScreenCapture(bounds);
			Graphics g = image.getGraphics();
			// 背景(もっといいやり方ないのか?)
			g.setColor(Color.WHITE);
			g.fillRoundRect(10, 25, 230, 25, 5, 5);
			// 画像に日付を入れる(背景との調整が面倒。簡単にできないのか?)
			g.setFont(new Font("SansSerif", Font.BOLD, 24));
			g.setColor(Color.RED);
			g.drawString(textFormat.format(today), 15, 45);

			// 以下、出力処理
			File dir = new File(baseDir, dirFormat.format(today));
			dir.mkdir(); // yyyyMMddのディレクトリを作成する。
			String fileName = "capture_" + fileFormat.format(today) + ".jpg";
			File file = new File(dir, fileName);

			ImageIO.write(image, "jpg", file);
			System.out.println("出力完了:" + file.getAbsolutePath());

		} catch (AWTException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

技術的な不明点は、画像に日時を埋め込んだところ。最初は青で文字を埋め込んだんだけど、キャプチャした画像が黒だと黒と青の相性が悪くて文字が読めなかった。だから文字の背景色を設定しようと思ったのだけど、そんな設定は見つからない。だから文字とは別に背景色を設定。だけど、背景色と文字を独立して設定しているから座標の指定が面倒だった。簡単に設定できないのかなあ?
まあでも望んでいたものができたので、その点は解決した。