「ベジエによるグラフィックの描画」
とりあえずコントロールの基本的な使い方がわかったところで、次にグラフィックの描画の基本について説明しましょう。コントロールは、使い方さえわかれば後はリファレンスを見て調べながら使えそうですが、グラフィックは基本の考え方がわからないとなんともやりようがないのです。なぜなら、Cocoaではグラフィックは単純に「このメソッドを実行」というだけでは済まないからです。
Cocoaでは、グラフィックというのは「ベジエ曲線」として用意するのが基本です。Mac OS Xでは「Quartz」というグラフィックエンジンが使われている、というのは聞いたことがありますね? これは、AdobeのPostScriptをベースとしています。このPostScriptというのは、要するにさまざまな曲線データを元に描画するやつで、その基本となっているのがベジエ曲線である、というわけです。まるで「風が吹けば桶屋が儲かる」みたいですが、そういうことで「Cocoaのグラフィックはベジエ曲線が基本である」と考えて下さい。
グラフィックを描画するというのは、「描画のメソッドを呼び出す」という発想でした。JavaのAWTも、Graphicsインスタンスに用意されている「円を描く」とか「四角形を描く」といったメソッドを呼び出せば、それが表示されました。が、Cocoaではこの考えは根本的に改める必要があります。Cocoaでは、グラフィックを描くというのは「図形のパスを作成する」「それを元に画面に描く」という手順をとります。要するに「図形の作成」と「描画」が分かれているのです。
また、JavaのAWTでは「描くコンポーネント内の描画メソッドを呼び出す」という発想でした。あるコンポーネントに描画したければ、そのコンポーネントのGraphicsインスタンスを取得し、その中にあるメソッドを呼び出せばそこに描かれたわけですね。——ところがCocoaでは違います。コントロールごとに描画用のオブジェクトは「ない」のです。それぞれのコントロールには、描画対象として設定する機能があり、ベジエ曲線のオブジェクトに描くメソッドがあります。そして「お前が描画対象になれ」「お前の図形を描け」というようにして描画をします。
——まぁ、これだけじゃわからんでしょう。そこで、JavaとCocoaの描画の基本的な流れを簡単に整理してみましょう。
※Java(AWT)の場合
- 描画するコンポーネントの描画用オブジェクト(Graphics)を取得する。
- そのオブジェクト内にある描画用メソッドを呼び出す。(ここで描画される)
- 描画が終ったら、必要に応じてオブジェクトの破棄などを行なって、おしまい。
※Cocoaの場合
- 描画するコントロールに「お前が描画対象になれ!」と命令する。
- 描画する図形のベジエ曲線を作成する。
- その他、必要なオブジェクト(色など)を用意する。
- 描画用オブジェクトやベジエ曲線で、描画のメソッドを呼び出す。(ここで描画される)
- 描画が終ったら、描画するコントロールを描画対象から解放して、おしまい。
ざっとですが、違いがわかってきたでしょうか。ちょっとCocoaの方が面倒ですね。なにしろ「描く図形」と「描画」がわかれているので、複雑な図形を描こうとするとけっこう大変そうです。——が、逆に「1つの図形を使い回せる」という利点もあります。つまり、図形を作成したら、それを元にして移動したり拡大縮小したりして描画すれば、かなり高度なグラフィックも簡単に描ける(場合もある)のです。
1.新規プロジェクト(Cocoa-Java Application)を作成したら、MainMenu.nibをInterface Builderで開きます。
2.デザインウィンドウに「NSImageView」と「NSButton」を1つずつ配置します。NSImageViewというのは、ツールパレットの左から3番目のアイコンをクリックすると現れます。左上の、中に絵が描かれている部品です。これらを配置して、位置や大きさを整えて下さい。
3.インターフェイスが出来上がったら、Javaのクラスを作成します。以下の手順に従ってMyObjectクラスとインスタンスを作成して下さい。
- Nibファイルウィンドウで「Classes」タブを選択し、「java.lang.Object」を選択する。そして「Subclass java.lang.Object」メニューで「MyObject」クラスを作成する。
- Infoウィンドウで、言語が「Java」になっていることを確認する。
- アウトレットを作成する。名前:「img1」、種類:「NSImageView」という項目を1つ作成する。
- アクションを作成する。名前:「btn1Click()」という項目を1つ作成する。
- 以上が終ったら、MyObjectをファイルに保存する。
- 作成したMyObjectを選択して「Instantiate MyObject」メニューでインスタンスを1つ作成する。
4.接続を行ないます。まずMyObjectインスタンスからNSImageViewまでCtrlキー+ドラッグ&ドロップし、「img1」に接続します。次にNSButtonからMyObjectインスタンスまでCtrlキー+ドラッグ&ドロップし、「btn1Click()」に接続します。
public void btn1Click(Object sender) { /* IBAction */ |
この「lockFocus」というメソッドが、描画先を設定する(フォーカスをロックする)メソッドです。これはCocoaのコントロールにはすべて用意されているもので、これでロックすると、以後の描画命令はすべてロックされたコントロール上で実行されるようになるのです。img1.lockFocus();
最初に「NSRect」というオブジェクトを作っています。これは、Cocoaで領域を示すのに用いるものです。AWTの「Rectangle」と同じものと考えればよいでしょう。JavaとCocoaでは、ほぼ同じ働きをするものでも、オブジェクトの構造が違うため、別々にクラスが用意されていることが多いのです。このRectangleとNSRectもそうですし、位置を示すPointとNSPointというのもあります。この後に登場するNSColorも、AWTのColorと同様のものです。NSRect r1 = new NSRect(10,10,70,70);
次の行が、ベジエ曲線(のインスタンス)を作成している部分です。より正確にいえば「ベジエ曲線の『パス』」でしょうか。要するに、図形の線分情報を示すオブジェクトなわけですね。このベジエ曲線(のパス)は、「NSBezierPath」というクラスとして用意されています。これはnewで新たに作成する方法もあるんですが、ここでは「bezierPathWithRect」というメソッドを使ってインスタンスを作成しています。NSBezierPath p1 = NSBezierPath.bezierPathWithRect(r1);
次に、色を示すオブジェクト「NSColor」を作成しています。これは、先も触れましたがAWTのColorのCocoa版といったものです。RGBを指定してインスタンスを作ったりすることもできますが、ここでは「redColor」という赤い色を示すインスタンスを返すメソッドでインスタンスを取得しています。このようにNSColorには、主な色のインスタンスを返すメソッドが用意されており、それらを使って簡単に色のインスタンスが得られます。主な色生成のメソッドとしては、以下のようなものがあります。NSColor c1 = NSColor.redColor();
blackColor() , dargGrayColor() , lightGrayColor , whiteColor() , grayColor() , redColor() , greenColor() ,cyanColor() , yellowColor() , magentaColor() , orangeColor() , purpleColor() , brownColor() , clearColor()とりあえず、これらのメソッドがわかれば、ちょっとした色変更はできるでしょう。どれも「《色名》Color」という名前ですから、覚えるのにそう苦労はしませんね。
まず、描画する色を設定します。色を設定する場合、JavaのAWTでは「色設定のメソッドを呼び出す」という感じでしたが、Cocoaでは「色のインスタンスに『お前を使う』と指示する」のです。この「set」メソッドがそのためのもので、これを実行すると、そのNSColorインスタンスが描画色として設定されます。c1.set();
次に、ベジエ曲線を描画します。図形の描画も、AWTのように「描画のメソッドを呼び出す」という感じではなく、描く図形のベジエ曲線インスタンスに「お前を描画しろ」と指示するわけです。この「stroke」メソッドは、そのベジエ曲線インスタンスを線描画(内部を塗りつぶさない)するメソッドです。p1.stroke();
ここでは先ほどとちょっと違うメソッドを呼び出していますね。「bezierPathWithOvalInRect」は、引数に指定したNSRectの領域内にぴったりおさまる円のベジエ曲線インスタンスを返すメソッドです。先ほどの四角形作成メソッドとペアで覚えておくとよいでしょう。NSBezierPath p2 = NSBezierPath.bezierPathWithOvalInRect(r2);
この「fill」が、塗りつぶして図形を描画するメソッドです。これで青い円も描画できました。——最後にunlockFocusでフォーカスロックを解除し、作業終了というわけです。p2.fill();
public void btn1Click(Object sender) { /* IBAction */ |
このように、newでNSBezierPathを作成すると、何の図形情報も持たないインスタンスが作成されます。ここに、自分で図形の情報を追加していくのです。NSBezierPath p1 = new NSBezierPath();
この「moveToPoint」というメソッドが、描画地点を移動するメソッドです。引数には「NSPoint」というインスタンスを指定します。これは、AWTの「Point」に相当するもので、位置を示すオブジェクトです。これはnewで横・縦の位置のfloat値を引数に指定してインスタンスを作成します。p1.moveToPoint(new NSPoint(20,20));
次に、その地点から直線を作成します。「lineToPoint」というのがそれで、現在の描画地点から引数に指定した地点までの直線をベジエ曲線インスタンスに追加します。描画地点はlineToPointした場所に移動するので、連続して呼び出すことで、次々に直線を描き足していけるわけです。p1.lineToPoint(new NSPoint(150,150));
p1.lineToPoint(new NSPoint(20,150));
次に実行しているのが曲線を描くメソッドです。「curveToPoint」は、描画地点から引数に指定した位置情報を元に作成した曲線をベジエ曲線インスタンスに追加します。これは3つの引数がありますが、これはそれぞれ以下のような役割をします。p1.curveToPoint(new NSPoint(20,80), new NSPoint(150,100),
new NSPoint(150,20));
ベジエ曲線の曲線(ああややこしい)というのは、基本的に4つの点によって作成されます。開始地点・終了地点・そして両地点からそれぞれ延びる2つのコントロールポイントです。まぁ、これは実際に各点をいろいろと指定して描画してみると、どういうものかわかってくることと思います。
- 1つ目のNSPoint:曲線の終りの位置
- 2つ目のNSPoint:曲線の始まり位置から延びるコントロールポイントの位置
- 3つ目のNSPoint:曲線の終り位置から延びるコントロールポイントの位置