Image I/Oで画像を書き込む
GraphicsExplorerは、Image I/Oの機能を使って、表示中の画像をファイルに書き込むことができます。GraphicsExplorer
クラスのコンストラクタでは、Image I/Oで書き込める画像形式の名前すべてをImageIO.getWriterFormatNames
メソッドで取得し、プルダウンリストの形で選択できるようにしています。
public GraphicsExplorer() { initComponents(); // canvasの描画ルーチンを設定 drawView = new DrawView(); canvas.setDrawCommand(drawView); // 書き込める画像形式の一覧をchoiceSaveTypeに設定する String[] saveTypes = ImageIO.getWriterFormatNames(); Arrays.sort(saveTypes); for(String saveType : saveTypes) choiceSaveType.add(saveType); if(choiceSaveType.getItemCount() > 0) getImageWriters(choiceSaveType.getSelectedItem()); // 読み込める画像形式の一覧から、読み込める画像の一覧を生成し、 // choiceFilenameに設定する createFileList(); }
保存形式を選択すると、選ばれた画像形式を書き込めるplugin(を表すImageWriter
型オブジェクト)のリストをImageIO.getImageWritersByFormatName
メソッドで取得し、その中の1つを選ぶことになります。pluginの一覧は、プルダウンリストの形で選択できますが、読み込みの時と同様に、標準ライブラリに組み込まれているpluginは、1つの画像形式につき1つなので、外部のpluginを導入しない限り、選択の余地はありません。
HashMap<String, ImageWriter> imageWriters = null; /** 指定された型を保存できるImageWriterの一覧を取得する */ private void getImageWriters(String type) { imageWriters = new HashMap<String, ImageWriter>(); Iterator<ImageWriter> tmpWriters = ImageIO.getImageWritersByFormatName(type); choiceIW.removeAll(); while(tmpWriters.hasNext()) { ImageWriter imageWriter = tmpWriters.next(); imageWriters.put( imageWriter.getClass().getName(), imageWriter); choiceIW.add(imageWriter.getClass().getName()); } }
どのpluginを使って書き込むかを決定し、[保存]ボタンを押すと、画像ファイルの書き込みが行われます。まず、pluginであるImageWriter
型オブジェクトに、ImageWriter#setOutput
メソッドで書き込むファイルを指定します。次に、ImageWriter#write
メソッドで画像の書き込みを行い、ファイルを閉じれば、保存が完了します。
/** 保存ボタンが押されたら */ private void buttonSaveActionPerformed( java.awt.event.ActionEvent evt) { ImageWriter imageWriter = imageWriters.get(choiceIW.getSelectedItem()); if(image == null || imageWriter == null) { JOptionPane.showMessageDialog(this, "保存できませんでした"); return; } String filename = choiceFilename.getSelectedItem(); filename = filename.substring(0, filename.lastIndexOf('.')) + (Integer)spinnerGraphicsNumber.getValue() + "." + choiceSaveType.getSelectedItem(); try { ImageOutputStream ios = ImageIO.createImageOutputStream(new File(filename)); imageWriter.setOutput(ios); imageWriter.write(image); ios.flush(); ios.close(); } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "保存できませんでした"); imageWriter.reset(); return; } imageWriter.reset(); createFileList(); }
もっと手軽なImage I/Oによる書き込み方法として、ImageIO
クラスのwrite
メソッドを使うことができます。保存形式を指定するだけで、適切なpluginを自動的に選択し、画像の保存を行います。ただし、条件を満たすpluginが複数あった場合の選択基準については、ドキュメントに記述がないので、注意が必要かも知れません。
ImageI/OでGIFを書き込む
Image I/Oで画像を書き込めるのは良いのですが、このままではGIFのpluginがないので、GIFで保存できません。どこからか、pluginを持ってくる必要があります。
本稿では、オープンソースのGIF書き込みpluginとして、gif-pluginとFreeHEP Java Library 1.2.2を紹介します。
gif-pluginは、java.netに登録されたプロジェクトで、ライセンスはApache License, Version 2.0となっています。2005年8月末にリリースされたバージョン0.1が最新版です(2006/9/8現在)。gif-plugin-0.1.jarがplugin本体ですので、ダウンロードしてください。GIFの書き込み処理については、Acme.JPM.Encoders.GifEncoderをベースにしているようです。
FreeHEP Java Libraryは、高エネルギー物理学のためのライブラリで、ライセンスはLGPLとなっています。現在、2.x系の開発が進められているようですが、成果物の供給がMavenに依存しているため、手軽に使える1.2.2を使います。freehep-v1.2.2.zipをダウンロードし、その中に含まれるfreehep-base.jar、freehep-graphics2d.jar、freehep-graphicsio.jar、freehep-graphicsio-gif.jarの4ファイルを取り出してください。
pluginを使うのは簡単です。実行時のクラスパスに含めれば、自動的にpluginが認識され、GIFを保存できるようになります。例えば、入手した5つのjarファイルをGraphicsExplorer.jarと同じ場所に置き、
> java -cp "freehep-base.jar;freehep-graphics2d.jar;freehep-graphicsio.jar; freehep-graphicsio-gif.jar;gif-plugin-0.1.jar;GraphicsExplorer.jar" GraphicsExplorer
で実行することができます。保存形式でGIFを選ぶと、読み込んだ2つのImageWriterが選べるのが確認できるでしょう。何れかを選んで、GIFを保存することができます。
めでたし、めでたし。と、言いたいところですが、実は、2つのpluginはともに、小さい画像を保存できないという不具合があります。4x4以下のサイズの画像を保存できないことを確認しています。携帯電話用の画素材として、小さい画像を扱うこともあるので、この点は困りものです。
もし、紹介したpluginが、利用に際して問題を持っている場合は、自分でソースを修正する、問題を作者に報告して対処してもらう、商用pluginを買ってくる、といった対応が必要になるでしょう。
さいごに
PNGの普及が進んでいない現在、GIFはまだまだ現役ですが、人によって認識はマチマチなので、JavaでGIFが保存できないという状況も生まれてしまっています。10年前なら、GIFの仕様書を読みながら、読み書きするコードを自前で書いていたと思いますが、今は他の選択肢があります。
画像はImage
に読み込んでdrawImage
するだけという人も多いと思いますが、本稿をきっかけに、コンピュータで画像を扱うことの根本的な概念なども勉強してもらえると、プログラミングの幅が広がるのではないかと思います。
ちなみに、Image I/OのJPEGサポートでは、JPEG2000で規格化された、透過可能なJPEGが読み書きできるようです。お好みでどうぞ。
部品として
Canvas
クラスがパレットに置いてあるので、貼り付けることはできます。しかし、よく知られているように、Canvas
クラスは、継承したクラスのpaint
メソッドを上書きして使うものなので、Canvas
クラスそのもののオブジェクトを使うことはありません。Canvas
クラスのpaint
メソッドには、塗り潰す処理が書かれているので、Canvas
クラスそのもののオブジェクトからGraphics
型の描画面を取得して描画処理をしても、その上から塗り潰しが実行されてしまいます。そこで、
Canvas
クラスを改造して、外部から与えられた処理で描画を行う仕組みを実装した、部品(beans)を作ることにしました。NetBeansでは、新規ファイル作成で、JavaBeansコンポーネントの雛型が得られるので、Canvasを継承させ、paint
メソッドとupdate
メソッドを上書きした、DrawableCanvas
クラスを作成しました。描画処理本体は、DrawCommand
インターフェイスを実装したオブジェクトから得られるようにしています。GraphicsExplorerでは、内部クラスDrawView
が描画処理本体となっています。このように処理本体を分離したデザインは、Strategyパターンとして知られています。