001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import java.awt.Rectangle; 019import java.awt.Robot; 020import java.awt.image.BufferedImage; 021import java.awt.AWTException; 022import java.awt.Toolkit; 023import java.awt.datatransfer.Clipboard; 024import java.awt.datatransfer.DataFlavor; 025import java.awt.datatransfer.StringSelection; 026import java.awt.datatransfer.FlavorListener; 027import java.awt.datatransfer.FlavorEvent; 028import java.awt.datatransfer.UnsupportedFlavorException; 029import java.io.IOException; 030import java.io.File; 031import javax.imageio.ImageIO; 032 033import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 034 035/** 036 * DisplayCapture.java は、画面イメージをキャプチャして、ファイルに書き出すためのクラスです。 037 * 038 * 基本的な使い方は、main メソッドから立ち上げて、クリップボードの状態を監視します。 039 * クリップボードに、"GUI:画面ID xxxxx.jsp" 形式の文字が書き込まれると、flavorsChanged イベントが 040 * 発生して、画面を、ファイルに書き出す処理が実行されます。 041 * エンジンの機能と連動すれば、画面IDやファイル名をクリップボード経由でこのアプリケーションに 042 * 渡すことで、画面操作を行うだけで、自動的に画面キャプチャを行うことが可能です。 043 * 044 * エンジンでは、jsp/indexc.jsp に、この機能が組み込まれているため、gf\BAT\displayCapture の 045 * DisplayCapture.bat を起動後、アプリケーションを、jsp/indexc.jsp で呼び出せば、自動的に 046 * 画面のキャプチャを開始できます。 047 * キャプチャは、全画面のみですので、IEを最大に広げて操作してください。 048 * また、「Prnt Scrn」ボタンにも対応していますので、操作中やポップアップ等の非自動化の 049 * 画面キャプチャも、手動で取得できます。 050 * 051 * 起動時の引数に応じて、処理を制御することが可能です。 052 * 053 * 書き出すフォルダは、BASE_DIR で指定します。 054 * すべてのキャプチャ画像は、ベースフォルダ に集約して保存します。 055 * キャプチャ画像の情報は、出力されるファイル名に反映されます。ファイル名の形式も2種類あり、 056 * キャプチャ順と、画面ID順が指定できます。 057 * 初期値は、Java起動時のフォルダになります。 058 * 059 * 書き出すファイル名の初期形式は、firstID の設定により異なります。 060 * firstID を gui に設定した場合 061 * ベースフォルダでフォルダを作成し、その中に、画面ID_JSPファイル名_連番.画像形式 ファイルを作成します。 062 * 画面ID 単位に、画面のキャプチャを整理、使用したい場合に便利です。 063 * ファイルのタイムスタンプ(作成時刻)で並び替えを行えば、キャプチャ順に並び替えできます。 064 * firstID を seq に設定した場合 065 * ベースフォルダでフォルダを作成し、その中に、連番_画面ID_JSPファイル名.画像形式 ファイルを作成します。 066 * ファイルは、キャプチャされた順番に、画面IDも混在して作成されます。つまり、ファイル名の順番に 067 * 再生すれば、リンクや他のシステムとの連携などで、画面が行き来しても、作業の順番にキャプチャできて 068 * いる事になります。 069 * 070 * このクラスは、これらを実現するために利用している、static メソッドをいくつか持っています。 071 * BufferedImage doCapture() 072 * 画面イメージをキャプチャします。これは、全画面です。 073 * void saveImage( File saveFile, BufferedImage img, String imgType ) 074 * 指定のファイルに、画面イメージを書き出します。 075 * imgType に、画像の種類(png|gif|jpg)を指定します(初期値:png)。 076 * String getClipboard() 077 * 現在のクリップボードの値を取り出します。ここでは、文字列のみ取り出すことが可能です。 078 * このメソッドの特徴的なところは、PrintScreenなどの文字列以外の値をクリップボードにセット 079 * した場合に、"GUI:PRINT SCREEN.img" という文字列を返すところです。つまり、その場合は、 080 * 全画面のキャプチャが行われるという事です。 081 * void setClipboard( String txt ) 082 * クリップボードに、文字列をセットします。 083 * 084 * このクラスが実装している FlavorListener は、クリップボードの"値"の更新には追従していません。 085 * 内部の Transferable オブジェクトが変更された場合に、flavorsChanged メソッドが呼び出されます。 086 * つまり、一度セットされた文字型データは、取り出した後、別のTransferable オブジェクトに変更して 087 * おかないと、次の文字列の変更が拾えなくなります。また、この別のTransferableオブジェクトの 088 * 設定で、再び、イベントが発生するので、そのままでは、無限ループになってしまいます。 089 * そこで、少し、トリッキーなのですが、setClipboard( String ) すると、再びイベントが呼び出され 090 * ないように、取得した文字列の先頭が、"GUI:" で始まる場合のみ、再設定するようにしています。 091 * 092 * Usage: java org.opengion.fukurou.util.DisplayCapture 093 * [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)] 094 * 095 * args[0] BASE_DIR : キャプチャファイルをセーブするベースとなるディレクトリ(初期値:起動フォルダ) 096 * args[1] firstID : キャプチャ画像をセーブするファイル方式を指定します(初期値:seq) 097 * gui (画面ID_JSPファイル名_連番.画像形式) 098 * seq (連番_画面ID_JSPファイル名.画像形式) 099 * args[2] imageFormat : 作成するイメージの形式。png|gif|jpg のどれか(初期値:png) 100 * args[3] startCnt : セーブファイル名をユニークにするためのカウント(初期値:100) 101 * 102 * この実装は同期化されません。 103 * 104 * @og.rev 5.1.7.0 (2010/06/01) 新規追加 105 * @og.rev 5.2.1.0 (2010/10/01) 実用性を重視した改修 106 * 107 * @version 5.0 108 * @author Kazuhiko Hasegawa 109 * @since JDK6.0, 110 */ 111public final class DisplayCapture implements FlavorListener { 112 private static final Clipboard CLIP_BOARD = Toolkit.getDefaultToolkit().getSystemClipboard(); 113 private static final Rectangle SCRN_SIZE = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() ); 114 115 private File baseDir = new File("."); // セーブするベースディレクトリ 116 private String firstID = "seq"; // 保存時のファイル名の形式(seq|gui) 117 private String imgType = "png" ; // 画像形式(png|gif|jpg) 118 private int cnt = 100; // ユニーク番号(セーブファイル名に付与) 119 120 /** 121 * デフォルトコンストラクター 122 * 123 * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません 124 */ 125 public DisplayCapture() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 126 127 /** 128 * キャプチャファイルをセーブするベースとなるディレクトリを設定します(初期値:java実行フォルダ)。 129 * 130 * クラスの内部的には、Java の実行フォルダ( new File(".") ) が初期値です。 131 * 132 * @param bsDir セーブするベースディレクトリ 133 * @throws RuntimeException セーブフォルダが作成できなかった場合 134 */ 135 public void setBaseDir( final String bsDir ) { 136 if( bsDir != null && bsDir.length() > 0 ) { 137 baseDir = new File( bsDir ); 138 139 // ベースフォルダの作成。初期値は起動フォルダなので、必ず存在する(はず) 140 if( !baseDir.exists() && !baseDir.mkdirs() ) { 141 final String errMsg = "ERROR:セーブフォルダが作成できませんでした。" + baseDir.getAbsolutePath() ; 142 throw new OgRuntimeException( errMsg ); 143 } 144 } 145 } 146 147 /** 148 * キャプチャ画像をセーブするファイル方式を指定します(初期値:seq)。 149 * 150 * seq (連番_画面ID_JSPファイル名.画像形式) 151 * gui (画面ID_JSPファイル名_連番.画像形式) 152 * 153 * 初期値は、seq です。 154 * 155 * @param firstID セーブするファイル方式(seq|gui) 156 * @throws RuntimeException ファイル方式の指定が間違っていた場合 157 */ 158 public void setFirstID( final String firstID ) { 159 if( firstID != null ) { 160 if( firstID.matches( "seq|gui" ) ) { 161 this.firstID = firstID; 162 } 163 else { 164 final String errMsg = "ERROR:firstID 属性は、(seq|gui)でお願いします。firstID=[" + firstID + "]" ; 165 throw new OgRuntimeException( errMsg ); 166 } 167 } 168 } 169 170 /** 171 * キャプチャ画像をセーブする画像形式を指定します(初期値:png)。 172 * 173 * キャプチャされたイメージをセーブするときの画像形式を指定できます。 174 * ここでは、png , gif ,jpg を指定できます。 175 * 176 * 初期値は、png 形式です。 177 * 178 * @param imgType セーブする画像形式(png|gif|jpg) 179 */ 180 public void setImageType( final String imgType ) { 181 if( imgType != null && imgType.matches( "png|gif|jpg" ) ) { 182 this.imgType = imgType; 183 } 184 } 185 186 /** 187 * キャプチャ画像をセーブするファイル名の先頭に付ける連番の開始数(初期値:100)。 188 * 189 * キャプチャされたイメージをセーブするとき、画面IDとJSPファイル名だけでは、前回分を 190 * 上書きしてしまうため、ファイル名の先頭に連番を付与しています。 191 * ここでは、その連番の開始番号を指定できます。 192 * 193 * 初期値は、100 です。 194 * 195 * @param startCnt 連番の開始数(初期値:100) 196 */ 197 public void setStartCnt( final String startCnt ) { 198 if( startCnt != null && startCnt.length() > 0 ) { 199 cnt = Integer.parseInt( startCnt ); 200 } 201 } 202 203 /** 204 * 全画面の画像イメージ(キャプチャ画像)を取得します。 205 * 206 * java.awt.Toolkit で、全画面のスクリーンサイズを取得し、java.awt.Robot の 207 * createScreenCapture( Rectangle ) メソッドで、BufferedImage を取得しています。 208 * 209 * @return 全画面の画像イメージ 210 * @throws RuntimeException AWTException が発生した場合 211 */ 212 public static BufferedImage doCapture() { 213 BufferedImage img = null; 214 try { 215 final Robot robo = new Robot(); 216 img = robo.createScreenCapture( SCRN_SIZE ); 217 } 218 catch( final AWTException ex ) { 219 final String errMsg = "ERROR:画像イメージ(キャプチャ画像)が取得できませんでした。" ; 220 setClipboard( errMsg ); 221 throw new OgRuntimeException( errMsg,ex ); 222 } 223 catch( final Throwable th ) { 224 final String errMsg = "ERROR:" + th.getLocalizedMessage() ; 225 setClipboard( errMsg ); 226 throw new OgRuntimeException( errMsg,th ); 227 } 228 229 return img; 230 } 231 232 /** 233 * キャプチャ画像をファイルにセーブします。 234 * 235 * ここでは、単純に、引数そのままで、ImageIO.write( BufferedImage,String,File ) しています。 236 * saveFile のディレクトリ存在チェックや、ファイル名の拡張子(png,gif,jpgなど)の修正、 237 * imgType の形式チェックなどは、行っていません。 238 * それらの処理は、事前に、調整しておいてください。 239 * 240 * @param img セーブする画像イメージ 241 * @param imgType セーブする画像形式(png|gif|jpg) 242 * @param saveFile セーブする画像ファイルオブジェクト 243 * @throws RuntimeException IOException が発生した場合 244 * @see javax.imageio.ImageIO#write( java.awt.image.RenderedImage , String , java.io.File ) 245 */ 246 public static void saveImage( final BufferedImage img , final String imgType , final File saveFile ) { 247 try { 248 ImageIO.write( img,imgType,saveFile ); 249 } 250 catch( final IOException ex ) { 251 final String errMsg = "ERROR:キャプチャ画像をファイルにセーブできませんでした。" + saveFile.getAbsolutePath() ; 252 setClipboard( errMsg ); 253 throw new OgRuntimeException( errMsg,ex ); 254 } 255 catch( final Throwable th ) { 256 final String errMsg = "ERROR:" + th.getLocalizedMessage() ; 257 setClipboard( errMsg ); 258 throw new OgRuntimeException( errMsg,th ); 259 } 260 } 261 262 /** 263 * システムのクリップボードの文字列を取得します。 264 * 265 * Toolkit.getDefaultToolkit().getSystemClipboard() で取得された Clipboard オブジェクトから 266 * 文字列情報(DataFlavor.stringFlavor)を取得します。 267 * 文字列情報が取得できない場合、(UnsupportedFlavorException が発生した場合) 例えば、 268 * PrntScrn ボタンが押された場合などは、文字列として、"GUI:PRINT SCREEN.img" を返します。 269 * これは、文字列が返せない場合でも、クリップボードに書き込まれたイベントで、全画面のキャプチャを 270 * 取得するための、特殊なコマンドに相当します。 271 * 272 * @return クリップボードの文字列 273 * @throws RuntimeException IOException が発生した場合 274 * @see java.awt.datatransfer.Clipboard#getData( DataFlavor ) 275 */ 276 public static String getClipboard() { 277 String strClip = null; 278 279 // 方法として、Transferable を取得後、getTransferData する事もできる。 280 // Transferable data = CLIP_BOARD.getContents(null); 281 282 try { 283 // クリップボードの値を取得 284 // strClip = (String)data.getTransferData(DataFlavor.stringFlavor); 285 strClip = (String)CLIP_BOARD.getData( DataFlavor.stringFlavor ); 286 } 287 catch( final UnsupportedFlavorException ex ) { 288 // PrintScreen が押された場合。 289 strClip = "GUI:PRINT SCREEN.img" ; // 形式をGUI:画面ID xxxxx.jsp 形式に合わす為 290 } 291 catch( final IOException ex ) { 292 final String errMsg = "ERROR:クリップボードの値を取得できませんでした。" ; 293 setClipboard( errMsg ); 294 throw new OgRuntimeException( errMsg,ex ); 295 } 296 catch( final Throwable th ) { 297 final String errMsg = "ERROR:" + th.getLocalizedMessage() ; 298 setClipboard( errMsg ); 299 throw new OgRuntimeException( errMsg,th ); 300 } 301 302 return strClip; 303 } 304 305 /** 306 * システムのクリップボードに文字列を書き込みます。 307 * 308 * システムの Clipboard オブジェクトに、StringSelection を セットします。 309 * 通常であれば、単純に、クリップボード経由でデータのやり取りをするだけの機構ですが、 310 * FlavorListener を実装している関係上、flavorsChanged が発生します。 311 * このイベントについては、#flavorsChanged( FlavorEvent ) を参照ください。 312 * 313 * @param txt クリップボードに書き込む文字列 314 * @see java.awt.datatransfer.StringSelection 315 * @see java.awt.datatransfer.Clipboard#setContents( Transferable , ClipboardOwner ) 316 */ 317 public static void setClipboard( final String txt ) { 318 final StringSelection strSel = new StringSelection( txt ); 319 CLIP_BOARD.setContents( strSel, null ); 320 } 321 322 /** 323 * リスナー対象の Clipboard で使用可能な DataFlavor が変更されたときに呼び出されます。 324 * 325 * これは、FlavorListener の イベントの実装です。 326 * DataFlavor が変更されたときであり、そのデータの内容が書き換えられた場合には、イベントが 327 * 発生しません。 328 * そのため、データを取り出したあとで、Transferable を再セットする処理を行っています。 329 * 330 * クリップボードで使用可能な一連の DataFlavors の変更によるものでない、余分な通知もあります。 331 * さらに、イベントを発生させるために、Transferable をセットする処理( #setClipboard(String) )を 332 * 実行しても、同様にイベントが発生します。 333 * 334 * ここでは、取得したクリップボードの文字列が、"GUI:" の場合のみ処理しています。 335 * これにより、取得後の Transferable の再セット時の文字列は、"GUI:" を削除しています。 336 * 337 * このメソッドでは、画面キャプチャを取得し、クリップボードの文字列から、画面ID とJSPファイル名を 338 * 抜き出し、セーブする一連の処理を行っています。 339 * 340 * @param fe イベントソース 341 * @see java.awt.datatransfer.FlavorListener#flavorsChanged( FlavorEvent ) 342 */ 343 @Override // FlavorListener 344 public void flavorsChanged( final FlavorEvent fe ) { 345 final String txt = getClipboard(); 346 347 // クリップボードの値をクリアしたときのイベントは、拾わないため。 348 if( txt != null && txt.length() > 0 && txt.startsWith( "GUI:" ) ) { 349 System.out.println( cnt + ":【" + txt + "】" ); 350 final BufferedImage img = doCapture(); 351 352 final File saveFile = makeSaveFile( txt ); 353 354 saveImage( img,imgType,saveFile ); 355 356 // クリップボードのFlavorを置換します。(Windwosからセットされた時にイベントを発生させるため。) 357 // 先頭の GUI: を取り除く。イベントの無限ループを防ぐ意味。 358 setClipboard( txt.substring( 4 ) ); 359 } 360 } 361 362 /** 363 * キャプチャ画像を書き出すファイルオブジェクトを作成します。 364 * 365 * 引数は、"GUI:画面ID xxxxx.jsp" 形式を想定した文字列です。 366 * 367 * ファイル名には、2種類あります。 368 * firstID を seq に設定した場合 369 * ベースフォルダでフォルダを作成し、その中に、連番_画面ID_JSPファイル名.画像形式 ファイルを作成します。 370 * ファイルは、キャプチャされた順番に、画面IDも混在して作成されます。つまり、ファイル名の順番に 371 * 再生すれば、リンクや他のシステムとの連携などで、画面が行き来しても、作業の順番にキャプチャできて 372 * いる事になります。 373 * firstID を gui に設定した場合 374 * ベースフォルダでフォルダを作成し、その中に、画面ID_JSPファイル名_連番.画像形式 ファイルを作成します。 375 * 画面ID 単位に、画面のキャプチャを整理、使用したい場合に便利です。 376 * ファイルのタイムスタンプ(作成時刻)で並び替えを行えば、キャプチャ順に並び替えできます。 377 * 378 * このメソッドで、フォルダの存在チェック、および、無ければ作成(mkdirs)も行います。 379 * 380 * @param txt ファイル名の元となる文字列("GUI:画面ID xxxxx.jsp" 形式) 381 * 382 * @return 書き出すファイルオブジェクト 383 */ 384 private File makeSaveFile( final String txt ) { 385 final int spc = txt.indexOf( ' ' ); // "GUI:画面ID xxxxx.jsp" をスペースで分離 386 final String gui = txt.substring( 4,spc ); // 画面ID の部分のみ切り出す。 387 final String jsp = txt.substring( spc+1,txt.length()-4 ); // xxxxx の部分のみ切り出す。 388 389 String saveFile = null; 390 if( "seq".equalsIgnoreCase( firstID ) ) { 391 saveFile = cnt++ + "_" + gui + "_" + jsp + "." + imgType ; 392 } 393 else if( "gui".equalsIgnoreCase( firstID ) ) { 394 saveFile = gui + "_" + jsp + "_" + cnt++ + "." + imgType ; 395 } 396 else { // 5.5.2.6 (2012/05/25) findbugs対応 397 saveFile = cnt++ + "_" + gui + "_" + jsp + "." + imgType ; // seqかguiしかないが、経路として、初期値(seq)を設定しておく。 398 } 399 400 final File svf = new File( baseDir,saveFile ); 401 // セーブフォルダの作成。 402 final File parent = svf.getParentFile(); 403 if( !parent.exists() && !parent.mkdirs() ) { 404 final String errMsg = "ERROR:セーブフォルダが作成できませんでした。" + parent.getAbsolutePath() ; 405 setClipboard( errMsg ); 406 throw new OgRuntimeException( errMsg ); 407 } 408 409 // セーブファイルの存在チェック。上書き禁止にしておきます。 410 if( svf.exists() ) { 411 final String errMsg = "ERROR:セーブファイルがすでに作成されています。" + svf.getAbsolutePath() ; 412 setClipboard( errMsg ); 413 throw new OgRuntimeException( errMsg ); 414 } 415 416 return svf; 417 } 418 419 /** 420 * DisplayCapture.java は、画面イメージをキャプチャする、メインメソッドです。 421 * 422 * Javaアプリケーションとして実行すると、無限処理に入ります。 423 * 内部的には、flavorsChanged イベント によるクリップボードの監視を行います。 424 * クリップボードに、"GUI:画面ID xxxxx.jsp" 形式の文字が書き込まれると、画面キャプチャを、 425 * ファイルに書き出す処理が実行されます。 426 * 書き出すファイル名の初期形式は、firstID の設定により異なります。 427 * firstID を seq に設定した場合 428 * ベースフォルダでフォルダを作成し、その中に、連番_画面ID_JSPファイル名.画像形式 ファイルを作成します。 429 * ファイルは、キャプチャされた順番に、画面IDも混在して作成されます。つまり、ファイル名の順番に 430 * 再生すれば、リンクや他のシステムとの連携などで、画面が行き来しても、作業の順番にキャプチャできて 431 * いる事になります。 432 * firstID を gui に設定した場合 433 * ベースフォルダでフォルダを作成し、その中に、画面ID_JSPファイル名_連番.画像形式 ファイルを作成します。 434 * 画面ID 単位に、画面のキャプチャを整理、使用したい場合に便利です。 435 * ファイルのタイムスタンプ(作成時刻)で並び替えを行えば、キャプチャ順に並び替えできます。 436 * 437 * Usage: java org.opengion.fukurou.util.DisplayCapture 438 * [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)] 439 * 440 * args[0] BASE_DIR : キャプチャファイルをセーブするベースとなるディレクトリ(初期値:起動フォルダ) 441 * args[1] firstID : キャプチャ画像をセーブするファイル方式を指定します(初期値:seq) 442 * seq (連番_画面ID_JSPファイル名.画像形式) 443 * gui (画面ID_JSPファイル名_連番.画像形式) 444 * args[2] imageFormat : 作成するイメージの形式。png|gif|jpg のどれか(初期値:png) 445 * args[3] startCnt : セーブファイル名をユニークにするためのカウント(初期値:100) 446 * 447 * @param args 引数 [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)] 448 */ 449 public static void main( final String[] args ) { 450 System.out.println( "DisplayCapture を起動しました。" ); 451 452 final DisplayCapture dispCap = new DisplayCapture(); 453 454 if( args.length > 0 ) { dispCap.setBaseDir( args[0] ); } 455 if( args.length > 1 ) { dispCap.setFirstID( args[1] ); } 456 if( args.length > 2 ) { dispCap.setImageType( args[2] ); } 457 if( args.length > 3 ) { dispCap.setStartCnt( args[3] ); } 458 459 // クリップボードの値をクリア(FlavorEvent を起こさせるため) 460// DisplayCapture.setClipboard( null ); 461 setClipboard( null ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 UnnecessaryFullyQualifiedName 462 463 // FlavorListener を登録します。(自分自身のオブジェクト) 464 CLIP_BOARD.addFlavorListener( dispCap ); 465 466 // FlavorEvent で処理させるので、ずっとスレッドをSleepさせておけばよい。 467 while( true ) { 468 // 8.5.4.2 (2024/01/12) PMD 7.0.0 EmptyCatchBlock 469// try { 470// Thread.sleep( 100000 ); 471// } 472// catch( final InterruptedException ex ) {} 473 try { Thread.sleep( 100_000 ); } catch( final InterruptedException ignored ) {} // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseUnderscoresInNumericLiterals 474 } 475 } 476}