Javaで艦これ用スクショツールを作ってみた その1

※一覧：
その1→ここ
その2→[http://qiita.com/YSRKEN/items/6772903bf6242d8a86b7](http://qiita.com/YSRKEN/items/6772903bf6242d8a86b7)
その3→[http://qiita.com/YSRKEN/items/ceb75d86650b030c4044](http://qiita.com/YSRKEN/items/ceb75d86650b030c4044)

#概要
　そもそもの動機は、**あーMacやLinuxとかで使える艦これ用のスクショツール無いかなー**と思ったことからでした。
　既にHSPで某スクショツールをうｐしていたのですが、その際にユーザーの方から時たま出ていたのは**「MacやLinuxで動きませんか？」**といった意見でした。
　有名な艦これ用スクショツールと言えば[艦これ一覧めいかー](https://twitter.com/kanahiron_dev?lang=ja)や[おりこうさんな秘書](https://twitter.com/orikou_hisyo)などがありますが、これらはHSPで作られたソフトウェアなので、**Windowsでしか動きません**。[他環境用のHSPも無いわけではない](http://lhsp.s206.xrea.com/hsp_tips1.html#3)のですが、スクショ機能のために[WinAPIを直接叩いている](http://chokuto.ifdef.jp/advanced/capturescreen.html)ので移植は困難でしょう。
　というわけで、[30億のデバイスで動作する](https://twitter.com/hashtag/30%E5%84%84%E3%81%AE%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%A7%E5%8B%95%E3%81%84%E3%81%A6%E5%AE%A2%E5%85%88%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%84java)らしいJavaを使って、[艦これ用スクショツールを作成してみました](https://github.com/YSRKEN/cap_nano)。

#そもそもスクショを撮るにはどうしたらいいのさ日向？
　軽くJavaのサンプル(Hello Worldなど)を触った後に、真っ先に調べたのは**どうやってスクショを撮るか**でした。まあ、当然ですよね？
　あちこちのページを調べた結果、全てのディスプレイからスクショを取得するコードは次のようになりました。

```code1.java
try{
	// すべてのグラフィックデバイスを取得する
	GraphicsDevice[] all_gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
	for(GraphicsDevice gd : all_gd){
		// 各グラフィックデバイスにおけるグラフィックス特性を取得する
		GraphicsConfiguration[] all_gc = gd.getConfigurations();
		// 各グラフィックス特性に従い、その座標を取得してスクショを撮る
		Robot robot = new Robot(gd);
		for(GraphicsConfiguration gc : all_gc){
			BufferedImage images = robot.createScreenCapture(gc.getBounds());
		}
	}
}
catch(Exception error){
	error.printStackTrace();
}
```

　ここでtry節を使用しているのは、[Robotを使うからです](http://docs.oracle.com/javase/jp/7/api/java/awt/Robot.html)。Javaの場合、**RobotやImageIO.writeなど特定の操作を行う際は必ずtry～catch節を書く必要があります**のでこう書いています。
「なぜわざわざGraphicsDeviceとかGraphicsConfigurationとか考えなきゃいけないんだよ！」と最初思いましたが、**Javaはあらゆるデバイスで動くことを想定している**ので、仮想デスクトップや複数ウィンドウなどで**「1デバイスごとに複数の画面が存在しえる状況」**を想定しているのでしょう。同様に、[Windowsのように「全ディスプレイがくっついた仮想画面」がある](http://umezawa.dyndns.info/wordpress/?p=4114)とは必ずしも言い切れませんので、**ディスプレイを跨る位置に艦これの画面があったら当然分割されます**。

#画像認識、それは今時のレディの嗜みの一つでもありますわ
　次に、取得したスクショから艦これの画面を抽出する必要があります……が、**一体どのようにすれば位置が分かるのでしょう？**
　HSPでスクショツールを組んでいた際は、OpenCVを利用して認識していました。なぜか[HSPには簡単にOpenCVが使えるライブラリがあります](http://www.onionsoft.net/hsp/ref/docs/hspcv.txt)し、このライブラリを使えば**png画像の保存・マッチングによる画像認識なども使える**ので必須級に便利だったからです。
　ただ、一応[JavaにもOpenCVが使えるライブラリはあった](http://junkato.jp/ja/blog/2014/01/26/use-opencv-from-java-rev2/)のですが、OS毎にライブラリが分かれてたりして若干使いづらそうだったので、あえて原始的な画像処理で位置を検出することにしました。

　そもそも、ブラウザに艦これの画面(100％表示だと800x480ピクセル)が存在する際は、その周囲が白色(#FFFFFF)に囲まれています。つまり、**角の色情報だけ拾ってマッチングすれば、それほどコストが掛からずに処理できるのではないか**と考えました。実際に試したところ、ディスプレイを3つ繋げた自環境でもサクサク認識しました。これは美味しい……！
　詳しい処理については、[ソースコードの550行目以降](https://github.com/YSRKEN/cap_nano/blob/master/source/nano.java)をご覧ください。

#私が画像処理したって、いいわよね？
　艦これの画面の位置が分かったら、後はこっちのものです。[30fpsで記録とかするとAPI直叩きの倍以上重い](https://twitter.com/YSRKEN/status/650934940779966464)ので自重するべきですが、時々スクショを保存する分には問題ありません。
　また、編成まとめ画像を作成したい時には**画像を切り出したり結合したりしたい**と考えるものですが、Javaの場合は**getSubimageメソッドで切り出し、BufferedImageからGraphicsクラスを取得してからdrawImageメソッドで書き込む**のが基本的な流れになります。
　ただし、getSubimageで切り出したBufferedImageha元のBufferImageとデータを共有している(ただの参照でしか無い)ので、画像の一部分を入れ替える際には**新しいBufferedImageクラスのインスタンスを作ってそこに書き込むようにしないと上手くいきません**。
　この辺に気づかずしばらくハマったものですが、**コピーなのか参照なのか分かりづらい**Javaの仕様も悪いんじゃないですかね……？
(※[getSubimageメソッドのヘルプ](https://docs.oracle.com/javase/jp/1.3/api/java/awt/image/BufferedImage.html#getSubimage%28int,%20int,%20int,%20int%29)には、参照を返すことが明記されています)

#記録は大切なの.jar
そんなこんなで組み上げたのが[こちらになります](https://github.com/YSRKEN/cap_nano/releases)。Linuxでも動作は確認しています。ぜひお試しください。
![cap.png](https://qiita-image-store.s3.amazonaws.com/0/29802/81f1967b-6c35-de28-9d93-3939905b881c.png)