Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

BezierPathLab

以下は、Mac OS X 10.5.5 上の Xcode 3.1 で説明しています。(10.4.11 の Xcode 2.5 からの変更も注記しています。)

目次:
1 README.rtf の日本語訳
2 アプリケーション
3 ファイル構成
4 MainMenu.nib
5 BezierView クラス
5.1 BezierView の変数と型
5.2 BezierView のメソッド
5.3 drawRect: メソッド
6 機能追加のアイデア

1 README.rtf の日本語訳

BezierPathLab は、NSBezierPath の機能のいくつかを実演する単純なサンプルです。NSBezierPath のさまざまな機能は、異なる線パターンや線端スタイルを作成するために使われます。NSGraphicsContextNSAffineTransform がパスを回転するために使われます。

パスの属性を設定するために、ボタン、スライダー、カラーウェルが使われます。これらのアクションは、NSView のサブクラスである BezierView に送られ、これがベジエパスの属性のすべてを管理します。このサブクラスは、さまざまな設定をパスに適用し、ビューにパスを描画するために、-drawRect: メソッドをオーバーライドします。

2 アプリケーション

まず、Xcode 上でビルドして実行してみます。アプリケーションを起動すると、次のようなウインドウが表示されます。

起動直後

ビューの下側や右側のコントロールを動かすと、その設定に従ってビューのなかのベジエパスが再描画されます。下のカラーウェルは、線色、塗りつぶしの色、背景を設定します。その下のスライダーで回転・ズーム・線の太さを変えられます。右側は上からパスの形を選ぶラジオボタン、塗りつぶしをするかどうかのチェックボックス、線端の形を設定するラジオボタン、破線のタイプを選ぶラジオボタンです

3 ファイル構成

プロジェクトを構成しているファイルのうち、ここで注目するのは、MainMenu.nib、BezierView.h と .m です。

4 MainMenu.nib

MainMenu.nib は次のようになっています。

MaiinMenu.nib 内の Window

左上のビューはカスタムビューで、NSBezierView クラスのインスタンスです。各コントロールのアクションは、このビューへ接続されています。このクラスのアウトレットは、lineColorWell(線色ウェル)、fillColorWell(塗りつぶし色ウェル)、backgroundColorWell(背景色ウェル)がビューの下の3つのカラーウェルに、angleSlider(角度スライダー)、zoomSlider(倍率スライダー)、lineWidthSlider(線幅スライダー)がその下の3つのスライダーに、pathTypeMatrix(パスタイプのマトリックス)、filledBox(塗りつぶしボックス)、capStyleMatrix(線端スタイルのマトリックス)、lineTypeMatrix(線幅タイプのマトリックス)が右側のラジオボタンとチェックボックスに接続されています。

NSBezierView のアクションには、ビュー下のカラーウェルから changeLineColor:(線色変更)、changeFillColor:(塗りつぶし色変更)、changeBackgroundColor:(背景色変更)、その下のスライダーから changeAngleSlider:(角度スライダー変更)、changeZoomSlider:(倍率スライダー変更)、changeLineWidth:(線幅スライダー変更)、右側のラジオボタンやチェックボックスから setPathType:(パスタイプ設定)、setFilled:(塗りつぶし設定)、setCapStyle:(線端スタイル設定)、changeLineType:(線種設定)にそれぞれ接続されています。

5 BezierView クラス

5.1 BezierView の変数と型

唯一の新しいクラスである BezierView を見ていきましょう。このアプリケーションの機能は、実質的にこのクラスで実装されます。まずヘッダファイルには、アウトレット以外に次のようなインスタンス変数が定義されています。

BezierView.h > @interface インスタンス変数宣言
NSColor *lineColor, *fillColor, *backgroundColor; // 線色、塗りつぶし色、背景色 NSBezierPath *path; // パスを格納 CGFloat lineWidth, angle, dashCount; // 線幅、角度、破線の設定行列の要素数 //Xcode 2.5 では float BezierPathType pathType; // パスのタイプ(独自に宣言した定数) CapStyleType capStyle; // 線端のスタイル(独自に宣言した定数) BOOL filled; // 塗りつぶすかどうか CGFloat dashArray[3]; // 破線の設定配列 Xcode 2.5 では float

lineColorfillColorbackgroundColor は、線色、塗りつぶし色、背景色に対応する NSColor クラスです。path は描画されるパスです。lineWithangle は線の太さと回転角度です。pathType はパスの形の種類を表し、次のような列挙型で指定されます。

BezierView.h > BezierPathType
enum { // ベジエパスのタイプ Xcode 2.5 では typedef enum { } SquarePath, // 正方形パス CirclePath, // 円形パス ArcPath, // 円弧パス LinePath // 直線パス }; typedef NSInteger BezierPathType;

capStyle は、線端の形状で、次のような列挙型で指定されます。『Cocoa 描画ガイド』>「パス」>「NSBezierPath クラス」>「パスの属性」>「線の端点スタイル」で説明されています。

BezierView.h > CapStyleType
enum { // 線端スタイルのタイプ Xcode 2.5 では typedef enum { } ButtLine, // 切れ端 SquareLine, // 正方形 RoundLine // 円形パス }; typedef NSInteger CapStyleType;
線端スタイル

filled は塗りつぶしするかどうかを表す BOOL 型変数です。

dashArray[3] は破線を定義する配列で、dashCount はその要素数です。破線の設定については、『Cocoa 描画ガイド』>「パス」>「NSBezierPath クラス」>「パスの属性」>「破線スタイル」で説明されています。配列の各要素が交互に、実線、間隔の長さを表しています。

このサンプルを実行してみるとわかりますが、配列要素自体は実線と間隔を区別せず繰り返されることに注意してください。配列要素の数が奇数の場合、たとえば、8-3-8 だと、実線8、間隔3、実線8 とですが、この次にすぐ実線 8 が続いて実線 16 となるわけではなく、次は間隔8、実線3、間隔8 となります。あくまで、最初が実線設定から始まるというだけです。

8-3-8 の設定の場合の破線

5.2 BezierView のメソッド

基本的にこのクラスのメソッドは、ほとんどがクラスのインスタンス変数を設定するものです。drawRect: メソッドで描画する際に、それらの変数が参照されることになります。ただし、拡大縮小に関しては、フレームのサイズ自体を拡大するため、インスタンス変数だけではなく、フレームのサイズも変更しています。

set... 系メソッドは直接インスタンス変数を設定します。change... 系メソッドは、各種のコントロールから呼ばれ、set... 系メソッドを使って設定を変更し、自身(changeZoomSlider: は上位ビュー)に再表示を行うように指定します。change... 系メソッドのうち、破線スタイルを設定する changeLineType: だけは、set... 系メソッドを介さずに直接設定を行っています。

set... 系メソッドでは、引数としてオブジェクトをとるものがあり、それらは引数が nil でないか調べて、そうでないなら元の変数をまず解放し、それから引数オブジェクトを copyWithZone:[self zone] でコピーしてから代入していることに注意してください。

set... 系メソッドで特別なのは、setZoom: です。これは全体の倍率を操作するため、少し複雑になっています。

BezierView.h > setZoom:
- (void)setZoom:(CGFloat)scaleFactor { // Xcode 2.5 では (float) NSRect frame = [self frame]; NSRect bounds = [self bounds]; frame.size.width = bounds.size.width * scaleFactor; frame.size.height = bounds.size.height * scaleFactor; [self setFrameSize: frame.size]; // ビューのサイズを変更 [self setBoundsSize: bounds.size]; // ビューのバウンズを復元、これはビューが拡大縮小されることを引き起こす }

MainMenu.nib で、倍率用のスライダーを調べると、1.0 〜 5.0 の範囲で動くようになっています。この数が引数として渡されることになります。バウンズの幅と高さに倍率を掛けて、それをフレームに設定しています。その後でバウンズを元の大きさに戻しています。こうすることで、倍率が 1.0 より大きい場合、バウンズは最初のサイズのままで、それがより大きいフレームに収まるように拡大縮小されて表示されることになり、スクロールビューがフレームにしたがって調整されます。この調整がうまく機能するように、changeZoomSlider: では上位ビューに再表示を命じています。次にこのメソッドが呼ばれた時も、バウンズは最初のサイズのままなので、フレームは最初のサイズに倍率を掛けたサイズになります。

awakeFromNib は、nib ファイルが読み込まれインスタンス化される時に呼び出されるメソッドです。ここでは、すべてのインスタンス変数に対して初期値を設定しています。dealloc メソッドは、このインスタンス(BezierView)が割り当て解除される時、インスタンス変数のうち、オブジェクトとして保持されているものを解放しています。これは set... 系メソッドで保持されたものです。

5.3 drawRect: メソッド

さて、サンプルの中心となるのは、drawRect: メソッドです。これは大まかに次のような構造をしています。

BezierView.m > drawRect:
局所変数の宣言と初期化 背景の描画と線色の設定 パスのタイプによって描くパスを設定 パスの線端スタイルを設定 破線と線幅を設定 コンテキストの保存と変換の設定と実行 パス描画の実行 コンテキストの復元

基本的に背景を描画したあと、パスの形状に応じてパスを作成し、回転などのアフィン変換を作成して、それを適用して、最後にパスを描くか塗りつぶすことになります。このとき、グラフィックスコンテクストを保存して最後に復元していることに注意してください。

BezierView.m > drawRect: > 局所変数の宣言と初期化
- (void)drawRect:(NSRect)rect { NSRect bounds = [self bounds]; NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; NSAffineTransform *rotation = [NSAffineTransform transform]; NSAffineTransform *translation = [NSAffineTransform transform]; CGFloat emptySpace = 40; // パスとそれを囲むビュー(BezierView 自身)の間の間隔を定義 // Xcode 2.5 では float

バウンズの取得は、emptySpace とあわせて、パス全体の大きさを計算するために必要です。グラフィックコンキストは保存と復元を行うために取得されます。回転用と平行移動用のアフィン変換を別々に確保しています。transform は、一時的な恒等変換(変換した結果が同じ)を新しく作成します。

BezierView.m > drawRect: > 背景の描画と線色の設定
[backgroundColor set]; // 背景色設定 NSRectFill([self bounds]); // バウンズ全体を塗りつぶす [lineColor set]; // 線色を設定

これは単純です。変数で保持している色を設定し、その色でバウンズ全体を塗りつぶします。そして線色を設定しています。

BezierView.m > drawRect: > パスのタイプによって描くパスを設定
switch (pathType) { // パスタイプで分ける case SquarePath: { // 正方形パスの場合 if (NSMaxX(bounds) > NSMaxY(bounds)) { // 横幅が高さより大きければ CGFloat width = (NSMaxY(bounds) - emptySpace)/sqrt(2); // 小さい方の高さで図形の幅を設定 Xcode 2.5 では float [self setPath:[NSBezierPath bezierPathWithRect: NSMakeRect(width/-2,width/-2,width, width)]]; // 正方形を作成 } else { // 高さが横幅より大きければ CGFloat width = (NSMaxX(bounds) - emptySpace)/sqrt(2); // 小さい方の幅で図形の幅を設定 Xcode 2.5 では float [self setPath:[NSBezierPath bezierPathWithRect: NSMakeRect(width/-2, width/-2, width, width)]]; // 正方形を作成 } } break; ...

保存されている pathType によって、処理を分けています。ここでは、バウンズの縦横の大きさを比べて処理を分けています。emptySpace 分の余白ができるように計算されています。bezierPathWithRect: で長方形パスを作成しています。パスが作成されているだけであることに注意してください。まだ、パスに沿って線を引いたり、塗りつぶしは行われません。このように、パスはまず作っておき、それからそれを使って線を引いたり、塗りつぶしを行います。

他のタイプも似たような事を行っています。違いは、大きさの計算とベジエパスを得るメソッドです。

BezierView.m > drawRect: > パスの線端スタイルを設定
switch (capStyle) { // 線端スタイルで分ける case ButtLine: { // 切れ端なら [path setLineCapStyle: NSButtLineCapStyle]; // Cocoa 定数で線端設定 } break; ...

保存された capStyle の設定で処理を分け、setLineCapStyle: で設定を反映させます。

BezierView.m > drawRect: > 破線と線幅を設定
if (dashCount) { // 破線が設定(配列の要素がある)されていたら [path setLineDash:dashArray count:dashCount phase: 0.0]; // 破線を設定 } [path setLineWidth: lineWidth]; // 線幅を設定

破線の設定があるなら、それを設定します。phase: は、配列で示されたもののどこから開始するかです。これを使えば、間隔から開始することが可能です。実線4、間隔2、実線6、間隔3 があるとすれば、4.0 を設定すれば、2 つめの間隔から始まります。この数は、設定した配列の数値の区切りでなく、任意の数を設定できます。2.0 とすれば、最初の実線の真ん中から始まります。

BezierView.m > drawRect: > コンテキストの保存と変換の設定と実行
[NSGraphicsContext saveGraphicsState]; // グラフィック状態を保存 [rotation rotateByDegrees:angle];// 回転を設定 [translation translateXBy: NSMaxX(bounds)/2 yBy: NSMaxY(bounds)/2];// 平行移動を設定 [translation concat]; // 平行移動を適用 [rotation concat]; // 回転を適用

まずグラフィック状態を保存し、その後で変換を設定しています。その後、concat で現在の変換に追加して適用しています。

BezierView.m > drawRect: > パス描画の実行
[path stroke]; // 線を引く(パスに沿って線幅を塗る) if (filled) { // 塗りつぶしをするなら [fillColor set]; // 塗りつぶし色設定 [path fill]; // 塗りつぶし(パスで囲まれた内部を塗る) }

ここでパスを描画しています。stroke はパスに沿って線を描きます。次に塗りつぶしのチェックボックスがチェックされていれば、塗りつぶし色を設定し、path で塗りつぶしています。この順番に注意してください。これにより、線幅の半分まで塗りつぶされています。これを逆にして、ビルドして実行してください。ただし、線色は最初のほうで設定されているので、線を描く直前に移します。

BezierView.m > drawRect: > パス描画の実行(修正)
if (filled) { // 塗りつぶしをするなら [fillColor set]; // 塗りつぶし色設定 [path fill]; // 塗りつぶし(パスで囲まれた内部を塗る) } [lineColor set]; // 線色を設定 [path stroke]; // 線を引く(パスに沿って線幅を塗る)

この順番だと、背景が塗りつぶされてから、線幅いっぱいまで線が描かれます。このようにパスを描くときは順番に気をつけてください。

線の後に塗り塗りの後に線
BezierView.m > drawRect: > コンテキストの復元
[currentContext restoreGraphicsState]; // 保存したグラフィック状態を戻す

最後にグラフィック状態を復元しています。これは変換を行って現在の変換行列に追加しているから、こうしているのでしょうが、その効果が私にはいまいちわかりません。Cocoa ではビューがグラフィック状態を管理しているため、上位ビューなどにもれる心配がないと思うのですが。複数スレッド使用時などはこうした方がいいのかもしれません。なんとなく、昔はこうしないと問題が起こったような気もしますが…。今はどうなんでしょう。そういうわけで、グラフィック状態の保存コードを除去してみましょう。[NSGraphicsContext saveGraphicsState];[currentContext restoreGraphicsState]; をコメントアウトして、ビルドして実行してみます。いろいろいじってみても、特に問題はなさそうです。

6 機能追加のアイデア

パスの属性は、まだ他にもあります。『Cocoa 描画ガイド』>「パス」>「NSBezierPath クラス」>「パスの属性」を見ると、このサンプルで扱われていないものには、線の結合スタイル (line join style)、線の平坦さ (flatness)、マイター限界 (miter limit)、ワインディングルール (winding rule) があります。コントロールやメソッドを追加して、これらの属性を変更するようにできます。ワインディングルールは複数パスの重なりが必要なので、このサンプルのままではダメです。複数パスを描くようなタイプを追加する必要があります。

また、『Cocoa 描画ガイド』>「より進んだ描画テクニック」>「描画パスへの影の追加」を参考にして、影 (shadow) をつけたり、影の設定を変更できるようにするのもおもしろいでしょう。

また、グラフィック状態の属性のうち、アンチエイリアス設定 (anti-aliasing setting) のオンオフも試せます。もっとも、この程度の描画ではあまり効果が確認できないかもしれません。

7 更新履歴

2007.11.11

v10.4、Xcode 2.5 で解説作成(ずっと以前の自分用のメモを修正)。

2008.09.25

v10.5.5、Xcode 3.1 用に解説更新。


管理人:神吉 秀典 E-mail:puer@ba.wakwak.com