Bullet 第7回

GUIコンポーネントの作成 (番外編)
  1. 今回の例題
  2. まずは回転
  3. 次は傾ける
  4. イメージに関する処理
  5. いろいろな形のバーメータ
  6. おまけ
  7. ソースコードのダウンロード

 
  今回の例題  
 

今回は番外編ということで、バーメータをいろいろとアレンジして行きましょう。アレンジする道具は Java 2 です。

みなさんは Netscape 6 のPR 版をダウンロードされたでしょうか。Netscape 6 ははじめから Java 2 に対応しています。今までは Java 2 をアプレットで使用する場合は Java Plug-In を使用しなければなりませんでした。しかし、Java Plug-In は文字通りプラグインなので、ユーザはダウンロードしてインストールする必要があります (Internet Explorer では自動インストールができますが)。これでは、なかなか Java 2 を使用することができませんでした。

しかし、ブラウザーが Java 2 に対応すれば、より機能の豊富な Java 2 をアプレットでも使用することができます。また、Java 2 SDK 1.3 からはバーチャルマシンがデフォルトでクライアント用に最適化した HotSpot VM になりました。HotSpot は高速で、しかもメモリの使用量が JIT などに比較すると少ない VM です。今は Java はサーバ側で使用されることが多いですが、これからは再びクライアント側で Java が注目されていくのではないでしょうか。

もっとも、Netscape 6 は Java 2 に標準に対応といっていますが、パソコンにインストールしてある Java 2 SDK もしくは Java 2 RE を必要とするので、動作としては Java Plug-In と同じになります。製品版では複数の VM を切り替えて使用することも可能になるそうです。

今回はクライアントでの Java を考える上で重要なグラフィックのライブラリ Java 2D を使用してバーメータをアレンジして行くことにしましょう。Java 2 でのグラフィックでは Swing の方ばかり注目されていますが、それ以外にも Java 2D や印刷に関する API、ドラッグアンドドロップなどいろいろな点で機能拡張されています。その中でも Java 2D はドローソフトの Illustrator やレタッチソフトの Photoshop などを開発している Adobe Systems と共同開発を行っていため、Illustrator や Photoshop の機能を Java でも使えるようになります。Java 2D にエクステンションの JAI (Java Advanced Imaging) を加えれば、本当に Java で Photoshop が作れるのではないかと思えるぐらいです。

Java 2D は標準で Java 2 SDK に含まれていますが、ドキュメントやチュートリアルはJava 2D の Web サイトで提供されています。

Java 2D Web Site http://java.sun.com/products/java-media/2D/index.html

Java 2D API Guide http://java.sun.com/products/jdk/1.3/docs/guide/2d/spec/j2d-title.fm.html

Java 2D API Guide (日本語版) http://java.sun.com/products/jdk/1.2/ja/docs/ja/guide/2d/spec/j2d-title.fm.html

今回のサンプルは、すべて Java 2 を使用しているので、Netscape 6 PR 1 以降のバージョンと Java 2 SDK 1.3 (Java 2 RE 1.3)、もしくは Java Plug-In をインストールしておく必要があります。

 
  ▲このページのトップへ戻る  
  まずは回転  
 

今まで作成したバーメータは縦型でしたが、横型のバーメータもよく使われるのではないでしょうか。そこで、Java 2D で導入されたアフィン変換を利用して縦型のバーメータを横型にしてみましょう。もっとも、こんなことをやらずにはじめから横型で作ったほうが早いのですが、それは横においておいて......。

アフィン変換は回転、並行移動、拡大・縮小、シャーリングを行う変換で、数式で表せば次の行列式になります。

式 1

原点を中心にした回転であれば変換のための行列が回転行列になります。

式 2

θが回転の角度です。なんか難しそうですが、プログラミングでは直接こんな行列を扱わないので、安心してください。

この変換を用いて横型にしたバーメータが HorizontalBarMeter.java です。

HorizontalBarMeter を使ったアプレットのソースは HorizontalBarMeterApplet.java です。

これはこちらで試すことができます。

Netscape 6, appletviewer 用 Java Plug-In 用
HorizontalBarMeterApplet.html HorizontalBarMeterAppletPLGIN.html

横型にするには回転をしなくてはならないのですが、原点を中心に90度回転させるとバーメータが表示域からはみ出してしまいます。そこで、図1 に示したように表示域にバーメータが入るように回転した後、並行移動を行います。

図 1 バーメータの回転

変換を行うクラスは AffineTransform クラスです。AffineTransform を普通に new を使用して生成する事もできますが、AffineTransform オブジェクトを生成するためのスタティックなメソッドもあります。今回はこのスタティックなメソッドを用いて生成を行っています。 getRotateInstance メソッドが回転の変換を表すオブジェクトの生成、getTranslateInstance メソッドが並行移動を表すオブジェクトの生成を行います。並行移動はバーメータの高さ分だけ行います。

        // 90度回転
        AffineTransform rotate
            = AffineTransform.getRotateInstance(Math.toRadians(90), 0.0, 0.0);


        // 移動
        trans = AffineTransform.getTranslateInstance(height, 0.0);


        // 回転と移動を組み合わせる
        trans.concatenate(rotate);

変換は組み合せることが可能です。数式で表せば並行移動の行列と回転の行列をかけることになりますが、AffineTransform では concatenate メソッドを使用します。組み合わせの順序を間違わないようにしてください。はじめに変換する方 (この場合は回転) が、後の変換 (ここでは並行移動) に組み合わされるようになります。したがって、concatenate メソッドの引数が回転を表す rotate オブジェクトになります。

変換はグラフィックコンテキストに setTransform メソッドを用いて、AffineTransform オブジェクトを指定します。setTransform メソッドを実行した後に描画されるものが変換されて描画されるようになります。イメージの描画には直接 AffineTransform オブジェクトを引数にとる drawImage メソッドがあるので、今回はこちらを使いましょう。

いい忘れましたが、Java 2 では paint メソッドの引数になっているグラフィックコンテキストオブジェクトが java.awt.Graphics から Graphics の派生クラスである java.awt.Graphics2D になっています。ただし、引数の型は互換性を保つために Graphics のままになっているので、Java 2D の機能を使うには Graphics2D にキャストする必要があります。

    public void paint(Graphics g){

        // ダブルバッファリング用のイメージがなければ、
        // 生成し、グラフィックコンテキストをえる。
        if(image == null){
            createBufferImage();
        }


        // 背景色で塗りつぶす
        graphics.setColor(this.getBackground());
        graphics.fillRect(0, 0, width, height);


        // バーの描画
        drawBar(graphics, value);


        // Java2D のグラフィックコンテキストに変換
        Graphics2D g2 = (Graphics2D)g;


        // イメージバッファの描画
        if(image != null){
            g2.drawImage(image, trans, this);
        }
    }

 

 
  ▲このページのトップへ戻る  
  次は傾ける  
 

次にアフィン変換のもうひとつの例として、バーメータを傾けてみましょう。傾けることをシャーリングといい、式で表せば次のようになります。

x 軸と y 軸の両方とも傾けると、よく分からなくなってしまうので、図 2は x 軸だけ傾けた場合 (shy = 0) を示しました。

シャーリング

図 2 シャーリング (shx = -0.5, shy = 0)

これを実装したのが ShearedBarMeter.java です。

アプレットのソースが ShearedBarMeterApplet.java になります。

これはこちらで試すことができます。

Netscape 6, appletviewer 用 Java Plug-In 用
ShearedBarMeterApplet.html ShearedBarMeterAppletPLGIN.html

シャーリングだけを行うと回転と同様に表示領域からはみ出てしまうので、回転の時と同様に並行移動もいっしょに行います。

        // シャーリング
        AffineTransform shear
            = AffineTransform.getShearInstance(-0.5, 0.0);


        // 移動
        trans
            = AffineTransform.getTranslateInstance(height/2.0, 0.0);


        // シャーリングと移動を組み合わせる
        trans.concatenate(shear);

そして、この AffineTransform オブジェクトを使用して、イメージの描画を行います。

            g2.drawImage(image, trans, this);

AffineTransform の例として回転とシャーリングを示しましたが、これ以外にも行列の要素をすべて指定して変換することもできます。たとえば、反転などもアフィン変換で行うことができます。これらの変換を用いることで GUI の表現力を向上することができます。

例として、2 つのペアになっているモータの回転数をメータを使用して監視することを考えて見ましょう。図 3 a) が普通に 2 つのメータを並べたところです。b) の方はメータを回転させ、片方は x 軸で反転させています。こうすれば 2 つのメータが連動して動いていることが、一見して分かるのではないでしょうか。


a) メータを並べたもの

b) メータを回転、反転させたもの
図 3 モータの回転数の監視例

ちょっとした工夫でシステムのユーザインタフェースをより視認しやすくでき、その工夫を実装するために Java 2D が有効に活用できるはずです。

 
  ▲このページのトップへ戻る  
 

イメージに関する処理

 
 


イメージに関する処理にはぼかし、シャープ化、コントラスト調整、明度調整、色調変更などがあります。レタッチソフトを使ってみたことがある人ならば、どのような処理か想像がつくのではないでしょうか。

今回はフィルタを利用してぼかし処理を行ってみましょう。ここでいうフィルタとはイメージのある画素を、そのまわりの画素を利用して変換することで、たたみ込み (Convolution) ともいわれます。フィルタを用いると、ぼかし、シャープ化、輪郭強調などが行えます。レタッチソフトの中には Photoshop のようにカスタムフィルタをユーザが設定できるものがありますが、ここで行う処理はこれと同じものです。フィルタは適応させる画素を中心にして重みをまわりの画素に重みをかけたものを加え合わせる処理を行います。

ぼかしを行うには、ある画素の値を自分も加えたまわりの画素の値の平均にすることなので、3×3 の 9 画素の平均をとるには、重みをすべて 1/9 にすればいいのです。ただし、イメージのエッジはまわりの画素が欠けてしまいますから、処理をしないか、まわりの画素を 0 とするなどの方法をとる必要があります。

それでは、プログラムを見ていきましょう。

ソースは BluredBarMeter.java です。

アプレットのソースが BluredBarMeterApplet.java になります。

これはこちらで試すことができます。

Netscape 6, appletviewer 用 Java Plug-In 用
BluredBarMeterApplet.html BluredBarMeterAppletPLGIN.html

まず、たたみ込みを行う行列を Kernel クラスを使用して表します。今回は自分を含めた回りの 5×5 = 25 画素を使用してぼかし処理を行ってみました。

        // 行列の要素

        float[] blurElements = new float[25];
        for(int i = 0 ; i < 25 ; i++){
            blurElements[i] = 1.0f/25.0f;
        }

        // 行列を表すオブジェクト
        Kernel kernel = new Kernel(5, 5, blurElements);

まず、行列の要素を float 型の配列に格納します。Kernel のコンストラクタは、行列の行数、列数、行列の要素を引数に取ります。ここでは 5×5 の行列を表しています。

ぼかし変換を表すクラスは ConvolveOp です。この他にもイメージに対する変換は LookupOp や RescaleOp などのようにクラス名の最後に Op がつけられいます。これらのクラスは BufferedImageOp と RasterOp というインタフェースを実装したクラスになります。ちなみに Op は Operation の略です。

ConvolveOp の生成には Kernel オブジェクトと、エッジの処理法、レンダリングの処理法を指定します。レンダリングは null にするとデフォルトのレンダリングが使用されます。

        blur = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);

この例ではエッジは処理を行わないようにしました。

BufferedImageOp もしくは RasterOp はイメージの処理を行うための filter メソッドが定義されていますので、filter メソッドを使用してぼかしを行います。

        blur.filter((BufferedImage)image, filteredImage);

フィルタをかけられるのは filteredImage の方です。もとイメージはそのまま残ります。

バーメータでぼかし処理をしてもぜんぜん嬉しくないのですが、ここでイメージ処理について説明を加えたのはわけがあります。監視システムではバーメータなどを用いて圧力や温度などの物理量を監視する以外にも、監視カメラからの画像などを表示すことが多々あると思います。監視カメラの画像をエッジ強調をして特徴を抽出することや、一部分をズームすることなども必要になるのではないでしょうか。 このようなときにイメージ処理が役に立つわけです。

 

 
  ▲このページのトップへ戻る  
  いろいろな形のバーメータ  
 

ここまでは四角形のバーメータばかり扱ってきましたが、ここでちょっと変な形のバーメータを作ってみましょう。監視システムでは監視対象の形 (たとえばタンクの形とかボイラの形など) をそのまま使用して画面を作ることが多くあると思います。そこで、この形をそのまま活かして、バーメータしてしまおうというわけです。

しかし、複雑な形のままバーを描画するのはなかなか大変です。そこで、マスクを使用することにしましょう。ある画像にマスクを適応させると、マスクに覆われた部分は見えなくなります。これをマスキングとかクリッピングといいます。タンクの形などをしたマスクを利用すれば、今まで作ってきたバーメータがマスキングすることでタンクの形のバーメータに変身するわけです。

マスキングをするには、マスクを作らなくてはならないのですが、Java ではいろいろな種類のマスクを作成することができます。幾何学形状を表すための Shape インタフェースの実装クラスであれば、すべてマスクになります。そこで、4種類のマスクを使ってバーメータを作ってみました。

ソースは ClipBarMeter.java です。

アプレットのソースが ClipBarMeterApplet.java になります。

これはこちらで試すことができます。

Netscape 6, appletviewer 用 Java Plug-In 用
ClipBarMeterApplet.html ClipBarMeterAppletPLGIN.html

まず、一般的なマスキング処理について説明しましょう。マスクを施すのには Graphics2D クラスの clip メソッドあるいはGraphics クラスの setClip メソッドを使用します。setClip メソッドは、それまで設定してあったマスク領域をチャラにして、新たにマスクを設定します。clip メソッドは、それまで設定してあったマスク領域と引数で指定したマスク領域の重なる部分を新たなマスク領域として設定します。

    g2D.clip(shape);

  
        or


    g.setClip(shape);

マスクのクリアには setClip メソッドの引数に null を与えれます。clip メソッドの引数はマスク領域の重ね合わせを行うため、 null にすると NullPointerException が発生しまいます。

さて、マスクの作り方です。ここでは次に示す 4 種類を作ってみました。

  1. 楕円
  2. 角丸長方形
  3. 文字
  4. 幾何図形の組み合わせ
  5. ベジェ曲線

これらのマスクは setClipShape メソッドをコールして変更できるようにしました。setClipShape ではマスクの指定に OVAL, ROUND_RECTANGLE, TEXT, COMP_SHAPE, GENERAL_PATH という変数を用いて指定します。

    public void setClipShape(int shapeNo){
        this.clipShapeNumber = shapeNo;
        clipShape = createClipShape(shapeNo);
    }

setClipShape の内部でコールされる createClipShape メソッドの中でマスクを作成します。createClipShape メソッドでは shapeNo をパラメータにした switch 文で生成するマスクを決定しています。

楕円と角丸長方形は幾何図形です。幾何図形は java.awt.geom パッケージにまとめられてあり、楕円、長方形、円弧、線分、点などがあります。また、精度によって単精度 (float) と倍精度 (double) のものが用意されています。楕円を表すインタフェースが Ellipse で、それを実装した倍精度のクラスが Ellipse2D.Double になります。クラス名にドットが入っているので変な感じですが、こういうものだと思ってください。

        Shape shape;


        switch(shapeNo){
          case OVAL:
            shape = new Ellipse2D.Double(0.0, 0.0,
                                 (double)width, (double)height);
            break;

角丸長方形も java.awt.geom パッケージに用意されています。こちらも倍精度のものを使用しました。

          case ROUND_RECTANGLE:
            shape = new RoundRectangle2D.Double(0.0, 0.0,
                                (double)width, (double)height,
                                (double)width/5.0, (double)height/5.0);
            break;

最後の 2 つの引数が角の部分を構成する楕円の幅と高さになります。

文字を Shape に変換するには java.awt.font.TextLayout クラスを使用します。まず、表示する文字列とフォントを指定して、TextLayout オブジェクトを生成します。最後の引数の FontRenderContext クラスはアンチエイリアスを行うかどうかなどを指定するためのクラスです。特に必要なければ、下の例のようにします。

次に AffineTransform オブジェクトを用意します。このオブジェクトは文字列から輪郭を抽出するときに使用します。今回は並行移動を行って、ベースライン上の基準点を左上にするために、y 軸方向に並行移動させています。

最後に getOutline メソッドを使用して、文字列の輪郭を抽出します。

文字列「あj」には特に意味はありませんが、フォントの上方線から下方線の位置まで使うような文字にしました。そうしないと、バーメータが表示している最小や最大値が表示されなくなることがあるからです。まぁ、文字列をバーメータに使用したい人はほとんどいないと思うので、問題にはならないでしょうが......。

          case TEXT:
            // テキストレイアウトの生成
            TextLayout text = new TextLayout("あj",
                             new Font("Serif", Font.BOLD, height),
                             new FontRenderContext(null, false, false));

            // Shape に変換するときに、アフィン変換を行う
            AffineTransform trans = new AffineTransform();

            // テキストの基準点はベースラインにあるので、それを左上にする
            trans.translate(0, text.getAscent()-text.getDescent());

            // アウトラインの抽出
            shape = text.getOutline(trans);
            break;

次は幾何図形の組み合わせを行います。単純に四角や丸だけで表しきれないときに、これらの図形の組み合わせを行うことができます。組み合わせを行うときには java.awt.geom.Area クラスを使用します。Area クラスのコンストラクタは Shape インタフェースを実装したクラスのオブジェクトを引数にとります。ここでは 2 つの角丸長方形と 2 つの長方形の 4 種類の図形を Area オブジェクトとして生成しました。

          case COMP_SHAPE:
            // 角丸長方形
            Area area1 = new Area(new RoundRectangle2D.Double(
                               0.0, (double)height*2.0/3.0,
                               (double)width, (double)height/3.0,
                               (double)width/10.0, (double)height/10.0));


            // 角丸長方形
            Area area2 = new Area(new RoundRectangle2D.Double(
                               (double)width/6.0, 0.0,
                               (double)width*2.0/3.0, (double)height/3.0,
                               (double)width/20.0, (double)height/20.0));


            // 長方形

            Area area3 = new Area(new Rectangle2D.Double(
                               (double)width/4.0, (double)height/3.0,
                               (double)width/2.0, (double)height/3.0));


            // 長方形
            Area area4 = new Area(new Rectangle2D.Double(
                               (double)width/3.0, (double)height/3.0,
                               (double)width/3.0, (double)height/3.0));

            area1.add(area2);
            area1.add(area3);
            area1.subtract(area4);
            shape = area1;
            break;

図形の論理和をとるには add メソッドを使用します。論理差をとるには substract メソッドです。他にも論理積は intersect メソッド、排他的論理和をとるには exclusiveOr メソッドが使用できます。ここでは area1 と area2、area3 の論理和から area4 の論理差をとっています。

最後はベジェ曲線です。いままで、Java で曲線を使うには楕円弧を利用したりして苦労していたのですが、Java 2 でやっと曲線をサポートするようになりました。ベジェ曲線は 3 次関数なのですが、始点、終点と 2 つのコントロールポイントで表します。コントロールポイントは始点もしくは終点の接線ベクトルを示しています。ドローソフトで曲線を描画するときに使われるので、知っていらっしゃる方も多いと思います。

また、複数の直線や曲線をまとめるクラス GeneralPath も Java 2 で導入されました。今回はこれを利用して、S 字型のバーメータを作ってみましょう。GeneralPath クラスは moveTo メソッドで点を指定し、そこから lineTo メソッドでは直線を、curveTo メソッドではベジェ曲線をつなげて行きます。closePath メソッドは現在の点と始点を結ぶ関数です。

プログラムだけだとどのような図形か分かりにくいので実際にアプレットを実行してみてください。

          case GENERAL_PATH:
            GeneralPath path = new GeneralPath();


            path.moveTo(0.0f, (float)height);
            path.curveTo(0.0f, (float)height*0.2f,
                         (float)width*0.9f, (float)height*0.7f,
                         (float)width*0.9f, 0.0f);
            path.lineTo((float)width, 0.0f);
            path.curveTo((float)width, (float)height*0.8f,
                         (float)width*0.1f, (float)height*0.3f,
                         (float)width*0.1f, (float)height);


            path.closePath();
            shape = path;
            break;

ClipBarMeterApplet では、4 つのボタンを上部に配置してマスクの形状を変えられるようにしました。ぜひ実行して、マスクが変更される様子を見ていただきたいと思います。


 
  ▲このページのトップへ戻る  
  おまけ  
 

Java 2D ではその他にもいろいろな機能があります。その中のひとつに透明度の操作があります。透明度を持つ色は赤、青、緑以外にアルファと呼ばれる数値を持ちます。このアルファが大きいほど透明になり、小さいほど不透明になります。これを利用することで監視カメラからの静止画像にバーメータなどをスーパーインポーズすることができます。画像をむやみに隠すことなくバーメータなどを表示できるのです。

イメージ的にはこんな感じになると思います。なかなかいい感じではないでしょうか。ただし、透明度を扱うととたんに処理が重くなるため、かなりちらつくようになってしまいましたが。

Netscape 6, appletviewer 用 Java Plug-In 用
TranslucentMetersApplet.html TranslucentMetersAppletPLGIN.html

ソースの解説は特にしないでおきます。キーになるのは Java 2D で導入されたバッファ用イメージの BuferedImage クラスです。BufferedImage オブジェクトを生成するときに、透明を扱うかどうか指定することができます。また、透明な色は普通に Color クラスで表すことができます。

ソースはここで見ることができます 

バーメータを Java 2D を使用していろいろアレンジしてみましたが、いかがだったでしょうか。今後、Java 2 もクライアント側に徐々に導入されて行くと思うので、監視システムなどのアプリケーションにも Java 2 が使えるようになるのではないでしょうか。今回の解説はそのための Java 2D の機能紹介という面も併せ持っていますが、紹介できなかった機能がまだまだたくさんあります。

JDK 1.1 までの Java のグラフィックスはあれもできないこれもできないと不便な部分ばかり目立っていましたが、Java 2D を使用すればこれもできるあれもできるというふうになりました。後は、これをどうやって活用するかです。いままで、機能的にあきらめていたことも、実現できるはずです。

次回からは本題に戻って、工業用のシステムなどでよく使用される RS-232C などを Java で使用するための Communication API について解説したいと思います。


 
  ▲このページのトップへ戻る  
  ソースコードのダウンロード  
 

今回用いた全てのアプレットのソースファイル、クラスファイル、HTMLファイルはここからダウンロードできます appletsrc.zip


Bullet JavaおよびJavaに関する商標は、米国Sun Microsystems社の登録商標または商標です。

 
  ▲このページのトップへ戻る