さて、前回である程度環境の構築ができたと思いますが、今回は引き続き環境整備(まだあるの・・・)と実際のプログラムの作成を行っていきます。
SWTのインストール
SWTというのは、Standard Widget Toolkitの略でEclipseなんかで使われているJavaのGUIライブラリです。AWTやSwingなどと違って、よりネイティブに近い外観・操作性を持っていると言われています。
実は、最初はXULRunnerとAWTとかでガリガリ書いてやるぞ~と思っていたのですが、よくよくSWTのJavaDocを読むと、Browserクラスというそのものズバリなクラスがあったので、そちらを使わせてもらうことにしました。
SWTのインストールはEclipse環境であれば特になにもする必要はないですが、今回サーバに設置することもあり、まず別途Eclipse Projectのサイトからダウンロードしてきます。リンクがunstableのバージョンになっているのは理由があって、The SWT FAQ によるとGeckoのバージョンとSWTのバージョンの組み合わせによっては正常に動作せず、前編でインストールしたXULRunner 1.8.1を利用するので、今回は3.3をダウンロードします。先ほどのリンク先の一番下の方の「SWT バイナリーおよびソース」というところにある、Linux (x86/GTK 2) 版を選びました。
解凍後、swt.jar を好きな場所へおきCLASSPATHに追加します。
- mv swt.jar /usr/java/jdk1.5.0_12/lib/
- export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/swt.jar
ウィンドウを表示してみる
ここまでで、環境がきちんと整備されているかどうかチェックするために簡単なプログラムを書いてみます。単純にSWTでウィンドウが表示できるかどうか確認するというだけのものです。
import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; public class TestWindow { public static void main(String args[]) { Display display = new Display(); Shell shell = new Shell(display); shell.setSize(800, 600); shell.open(); while(!shell.isDisposed()) { if(!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }
上記のコードをTestWindow.javaというファイル名で保存しコンパイル&実行してみます。
- javac TestWindow.java
- java TestWindow
空の大きなウィンドウが表示されればOKです。
いよいよBrowserクラスの登場
先ほどのソースを若干変更して、Browserを埋め込んでみます。
import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.browser.*; public class TestWindow { public static void main(String args[]) { Display display = new Display(); Shell shell = new Shell(display); shell.setSize(800, 600); shell.open(); Browser browser = new Browser(shell, SWT.NONE); browser.setBounds(shell.getClientArea()); browser.setUrl("http://www.google.co.jp/"); while(!shell.isDisposed()) { if(!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }
あっけなく、ブラウザの完成です。簡単すぎて拍子抜けしましたか?
しかしこれだけでは、何の意味もないので次の段階としてはこのブラウザ画面を画像に保存することを考えます。
レンダリング結果のキャプチャ
先ほどのサンプルではGoogleを開いてそのまま何もせず終わっていますが、キャプチャを行う場合の手順としては、
- 読み込み完了まで待つ
- 完了後、表示結果をImageにコピー
- Imageを画像ファイルに書き出し
のような感じになると思います。
1.の読み込み完了まで待つというのは実際には待つのではなく読み込みが完了したというイベント発生時に処理を行うようプログラムを書くことになります。
2.のImageへのコピーは少々強引な感が否めませんが、BrowserウィジェットのGCを使ってImageへ矩形コピーを行います。
3.の画像ファイルへの保存は、ImageIOを用いて縮小保存を行います。
以上を行うにあたって、画像の保存部分を別のクラスに分け、別スレッドとして起動するようにしました。その理由はまた次回説明します。最後にキャプチャ可能なソースを書いて今日は終わりとします。
/** * @brief pretty simple browser only for capturing * @author flipper * @date 2007.07.04 */ import java.io.*; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.browser.*; import org.eclipse.swt.widgets.*; public class CaptureBrowser { public static ImageSaver saver = new ImageSaver("image saver thread"); public static boolean isFinishCapture = false; public static void main(String args[]) { Display display = new Display(); Shell shell = new Shell(display); saver.display = display; shell.setSize(830, 630); shell.open(); Browser browser = new Browser(shell, SWT.NONE); final GC gc = new GC(browser); saver.gc = gc; Rectangle rect = shell.getClientArea(); rect.height = 630; rect.width = 830; saver.width = 816; saver.height = 600; browser.setBounds(rect); browser.setUrl("http://www.yahoo.co.jp"); // Listens for page loading status. browser.addProgressListener(new ProgressListener() { public void changed(ProgressEvent event) { } public void completed(ProgressEvent event) { System.out.println("completed"); saver.start(); } }); while (!shell.isDisposed()) { if(isFinishCapture) { shell.close(); } if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }
/** * @brief make thumbnail and save image for CaptureBrowser * @author Daijiro Abe * @date 2007.07.04 */ import java.io.*; import java.io.File; import java.awt.image.*; import java.awt.Graphics2D; import java.awt.Graphics; import java.awt.RenderingHints; import javax.imageio.ImageIO; import org.eclipse.swt.graphics.*; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; public class ImageSaver extends Thread { public Display display; public GC gc; public int width; public int height; public ImageSaver(String name) { super(name); } public void run() { try { sleep(1000); Image img = new Image(display, width, height); gc.copyArea(img, 0, 0); BufferedImage bimg = swt2awt(img.getImageData()); BufferedImage simg = new BufferedImage(320, 200, bimg.getType()); Graphics2D g2d = simg.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g2d.drawImage(bimg.getScaledInstance(320, 240 , java.awt.Image.SCALE_AREA_AVERAGING) , 0, 0, 320, 200, null); ImageIO.write(simg, "jpg", new File("test.jpg")); CaptureBrowser.isFinishCapture = true; // System.out.println("jpg file is out"); // for debugging } catch (Exception e) { e.printStackTrace(); } } private BufferedImage swt2awt(ImageData data) { ColorModel colorModel = null; PaletteData palette = data.palette; if (palette.isDirect) { colorModel = new DirectColorModel(data.depth, palette.redMask, palette.greenMask, palette.blueMask); BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height), false, null); WritableRaster raster = bufferedImage.getRaster(); int[] pixelArray = new int[3]; for (int y = 0; y < data.height; y++) { for (int x = 0; x < data.width; x++) { int pixel = data.getPixel(x, y); RGB rgb = palette.getRGB(pixel); pixelArray[0] = rgb.red; pixelArray[1] = rgb.green; pixelArray[2] = rgb.blue; raster.setPixels(x, y, 1, 1, pixelArray); } } return bufferedImage; } else { RGB[] rgbs = palette.getRGBs(); byte[] red = new byte[rgbs.length]; byte[] green = new byte[rgbs.length]; byte[] blue = new byte[rgbs.length]; for (int i = 0; i < rgbs.length; i++) { RGB rgb = rgbs[i]; red[i] = (byte) rgb.red; green[i] = (byte) rgb.green; blue[i] = (byte) rgb.blue; } if (data.transparentPixel != -1) { colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue, data.transparentPixel); } else { colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue); } BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height), false, null); WritableRaster raster = bufferedImage.getRaster(); int[] pixelArray = new int[1]; for (int y = 0; y < data.height; y++) { for (int x = 0; x < data.width; x++) { int pixel = data.getPixel(x, y); pixelArray[0] = pixel; raster.setPixel(x, y, pixelArray); } } return bufferedImage; } } }
Good post.
ありがとうございます!!
とてもとても参考になりました!!
お役に立てたようで良かったです!
Xwindowが入っていないコンソールオンリーでも動作しますでしょうか?
試してみたところDisplayの作成に失敗して、困っています。
Exception in thread “http://hogehoge.com” org.eclipse.swt.SWTError: No more handles [gtk_init_check() failed]
at org.eclipse.swt.SWT.error(Unknown Source)
at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
at org.eclipse.swt.widgets.Display.create(Unknown Source)
at org.eclipse.swt.graphics.Device.(Unknown Source)
at org.eclipse.swt.widgets.Display.(Unknown Source)
at org.eclipse.swt.widgets.Display.(Unknown Source)
at safeurl.site.capture.CaptureBrowser.run(CaptureBrowser.java:30)