※一覧:
その1→ここ
その2→http://qiita.com/YSRKEN/items/6772903bf6242d8a86b7
その3→http://qiita.com/YSRKEN/items/ceb75d86650b030c4044
#概要
そもそもの動機は、あーMacやLinuxとかで使える艦これ用のスクショツール無いかなーと思ったことからでした。
既にHSPで某スクショツールをうpしていたのですが、その際にユーザーの方から時たま出ていたのは**「MacやLinuxで動きませんか?」**といった意見でした。
有名な艦これ用スクショツールと言えば艦これ一覧めいかーやおりこうさんな秘書などがありますが、これらはHSPで作られたソフトウェアなので、Windowsでしか動きません。他環境用のHSPも無いわけではないのですが、スクショ機能のためにWinAPIを直接叩いているので移植は困難でしょう。
というわけで、30億のデバイスで動作するらしいJavaを使って、艦これ用スクショツールを作成してみました。
#そもそもスクショを撮るにはどうしたらいいのさ日向?
軽くJavaのサンプル(Hello Worldなど)を触った後に、真っ先に調べたのはどうやってスクショを撮るかでした。まあ、当然ですよね?
あちこちのページを調べた結果、全てのディスプレイからスクショを取得するコードは次のようになりました。
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を使うからです。Javaの場合、RobotやImageIO.writeなど特定の操作を行う際は必ずtry~catch節を書く必要がありますのでこう書いています。
「なぜわざわざGraphicsDeviceとかGraphicsConfigurationとか考えなきゃいけないんだよ!」と最初思いましたが、Javaはあらゆるデバイスで動くことを想定しているので、仮想デスクトップや複数ウィンドウなどで**「1デバイスごとに複数の画面が存在しえる状況」**を想定しているのでしょう。同様に、Windowsのように「全ディスプレイがくっついた仮想画面」があるとは必ずしも言い切れませんので、ディスプレイを跨る位置に艦これの画面があったら当然分割されます。
#画像認識、それは今時のレディの嗜みの一つでもありますわ
次に、取得したスクショから艦これの画面を抽出する必要があります……が、一体どのようにすれば位置が分かるのでしょう?
HSPでスクショツールを組んでいた際は、OpenCVを利用して認識していました。なぜかHSPには簡単にOpenCVが使えるライブラリがありますし、このライブラリを使えばpng画像の保存・マッチングによる画像認識なども使えるので必須級に便利だったからです。
ただ、一応JavaにもOpenCVが使えるライブラリはあったのですが、OS毎にライブラリが分かれてたりして若干使いづらそうだったので、あえて原始的な画像処理で位置を検出することにしました。
そもそも、ブラウザに艦これの画面(100%表示だと800x480ピクセル)が存在する際は、その周囲が白色(#FFFFFF)に囲まれています。つまり、角の色情報だけ拾ってマッチングすれば、それほどコストが掛からずに処理できるのではないかと考えました。実際に試したところ、ディスプレイを3つ繋げた自環境でもサクサク認識しました。これは美味しい……!
詳しい処理については、ソースコードの550行目以降をご覧ください。
#私が画像処理したって、いいわよね?
艦これの画面の位置が分かったら、後はこっちのものです。30fpsで記録とかするとAPI直叩きの倍以上重いので自重するべきですが、時々スクショを保存する分には問題ありません。
また、編成まとめ画像を作成したい時には画像を切り出したり結合したりしたいと考えるものですが、Javaの場合はgetSubimageメソッドで切り出し、BufferedImageからGraphicsクラスを取得してからdrawImageメソッドで書き込むのが基本的な流れになります。
ただし、getSubimageで切り出したBufferedImageha元のBufferImageとデータを共有している(ただの参照でしか無い)ので、画像の一部分を入れ替える際には新しいBufferedImageクラスのインスタンスを作ってそこに書き込むようにしないと上手くいきません。
この辺に気づかずしばらくハマったものですが、コピーなのか参照なのか分かりづらいJavaの仕様も悪いんじゃないですかね……?
(※getSubimageメソッドのヘルプには、参照を返すことが明記されています)
#記録は大切なの.jar
そんなこんなで組み上げたのがこちらになります。Linuxでも動作は確認しています。ぜひお試しください。