So-net無料ブログ作成

太さの変わるBezier曲線の生成 - その25 [考え中 - 太さの変わるBezier曲線]

AGG方式のSmoothingをNSBezierPathにやらせるためのカテゴリを昨日から書き出した。昨日はカテゴリの入り口と、サブパスの端点を保持する小さなクラスを考えて、自分自身からサブパスを切り出すところまでをやった。今日はそのあと、Smoothingした曲線を作り直す。

サブパスの端点を全部保持し終えたら、その後はそれを全部消してSmoothingしたサブパスを追加しなおす。NSBazierPathのカテゴリのプライベートメソッド、

- (void)rebuildUsing:(NSArray *)subpaths withKValue:(float)kvalue
{
    NSEnumerator    *en = [subpaths objectEnumerator];
    id              subpath;
    while ((subpath = [en nextObject]) != nil)
        [subpath addSmoothedSubPathTo:self withKValue:kvalue];
}
これはサブパスごとにaddSmoothedSubPathTo:withKValue:メソッドを読んで自分自身に描かせている。

サブパスの区切りを調べる

DCElementTypeForSubPathを調べるメソッドは

+ (DCElementTypeForSubPath)typeForSubPath:(NSBezierPathElement)element
{
  switch(element) {
      case NSMoveToBezierPathElement:
          return DCNeedToStartNewSubPath;
      case NSLineToBezierPathElement:
      case NSCurveToBezierPathElement:
          return DCShouldAppendPoint;
      case NSClosePathBezierPathElement:
          return DCEndOfSubPath;
  }
  return DCDontKnowHowToDo;
}
で、まったくエレメントのNSBezierPathElementにそのまま対応している。そのままなのでわざわざDCElementTypeForSubPathなんていうのを作る必要はないのだけど、もしエレメントの追加のされ方がFrameworkのバージョンによって変わった場合はこのメソッドで吸収する。ちゃんとドキュメントされてない動作に依存する場合はこんなふうにワンクッションおいて変更をひとところに集めておいたほうがいい。

端点を拾い出す

端点を追加するDCAnchorsOfSubPathのメソッドは

- (void)appendAnchorOfElement:(NSBezierPathElement)type withPoints:(NSPointArray)points
{
    NSPoint firstpnt;
    switch (type) {
        case NSMoveToBezierPathElement:
        case NSLineToBezierPathElement:
            [anchors addObject:[NSValue valueWithPoint:points[0]]]; // (1)
            break;
        case NSCurveToBezierPathElement:
            [anchors addObject:[NSValue valueWithPoint:points[2]]]; // (2)
            break;
        case NSClosePathBezierPathElement:
            isClosed = YES;
            firstpnt = [[anchors objectAtIndex:0] pointValue];
            [anchors addObject:[NSValue valueWithPoint:firstpnt]];  // (3)
            break;
    }
}
で、NSMoveToBezierPathElement、NSLineToBezierPathElement、NSCurveToBezierPathElementの場合にそれぞれ端点が格納されている位置が違うのでそれに従って取り出す。NSClosePathBezierPathElementのとき(3)は、isClosedフラグをセットし、pointsに格納された情報は捨ててその代わりに描きはじめの座標をもういちど入れておく。これはまえに説明した、閉じたサブパスに中間の制御点を付け加えるための追加の端点である。

Smoothingの準備

つぎはSmoothing。DCAnchorsOfSubPathのメソッドのaddSmoothedSubPathTo:withKValue:で制御点を追加する。

- (void)addSmoothedSubPathTo:(NSBezierPath *)path withKValue:(float)kvalue
{
  if ([anchors count] <= 1)                                       // (1)
      return;
  else if ([anchors count] == 2) {                                // (2)
      [path moveToPoint:[[anchors objectAtIndex:0] pointValue]];
      [path lineToPoint:[[anchors objectAtIndex:1] pointValue]];
  }
  else {                                                          // (3)
      NSPoint *controls = (NSPoint *)malloc(2 * [anchors count] * sizeof(NSPoint));
      [self createControlsTo:controls withKValue:kvalue];
      [self appendSubPathTo:path WithControls:controls];
      free(controls);
  }
}
サブパスに端点が1個以下なら(1)、その端点ごと捨ててなにもしない。これは例えばmoveToPoint:が連続して2回呼ばれたときなどに起こる。これはもともと何も描画を引き起こさないので捨ててしまっていい。 端点が2個なら(2)、Smoothingのしようがないのでその2点を直線で結ぶだけ。端点が3個以上なら(3)、
  1. 中間制御点のためのメモリ領域を確保する
  2. createControlsTo:withKValue:メソッドでAGG方式の制御点位置を計算する
  3. appendSubPathTo:WithControls:メソッドでその制御点ともとの端点を使って曲線を生成する
  4. 中間制御点のためのメモリ領域を解放する
をしている。端点はNSMutableArrayのインスタンスなのに中間制御点は連続したC配列になっていて一貫性がない。これは端点の場合はサブパスが終わるまで個数がわからないけど追加する制御点の個数は式-97で決まるからで、本当は両方ともC配列にしたかった。これは単なる趣味。

createControlsTo:withKValue:メソッドは端点を順に読んで中間制御点の座標をaddSmoothedSubPathTo:withKValue:メソッドが確保したメモリ領域に格納する。

- (void)createControlsTo:(NSPoint *)controls withKValue:(float)kvalue
{
    int num = [anchors count] - 1;                                  // (0)
    int n;
    NSPoint points[3];
    for (n = 1 ; n < num ; n ++) {
        points[0] = [[anchors objectAtIndex:n - 1] pointValue];
        points[1] = [[anchors objectAtIndex:n] pointValue];
        points[2] = [[anchors objectAtIndex:n + 1] pointValue];
        [self getControlPoints:controls + (2 * n - 1)               // (1)
                   WithAnchors:points
                     andKValue:kvalue];
    }
    if (isClosed) {                                                 // (2)
        points[0] = [[anchors objectAtIndex:num - 1] pointValue];
        points[1] = [[anchors objectAtIndex:0] pointValue];
        points[2] = [[anchors objectAtIndex:1] pointValue];
        [self getControlPoints:controls + (2 * num - 1)
                   WithAnchors:points
                     andKValue:kvalue];
        controls[0] = controls[2 * num];
    }
    else {                                                          // (3)
        controls[0] = [[anchors objectAtIndex:0] pointValue];
        controls[2 * num - 1] = [[anchors objectAtIndex:num] pointValue];
    }        
}
まず(0)で端点の個数を取り出す。(1)で順番に端点を3つずつ取り出して、getControlPoints:WithAnchors:andKValue:というメソッドに渡している。このメソッドが中間の制御点をAGG方式で作る。サブパスが閉じている場合、(2)で最後の点と描き始めの点、その次の点の3つで制御点を作って、一番最後にできた制御点を先頭に戻す。これはわかりにくいけど、AGG方式では端点の前後の制御点が一回の計算で得られるが、NSBezierPathのcurveTo~メソッドは端点に挟まれた制御点を使用するためである。点の関係と配列内の位置を図-33に示す。
0526fig33.png
閉じていない場合(3)では、最初の制御点と最後の制御点を始点と終点に一致させている。

なんか結局全部のコードを書いてる。ちょっと丁寧すぎるな。もっと簡単な書き方にしたほうがわかりやすい。とは言うもののもう書いてしまったのでもういいや。次はAGG方式のアルゴリズムのコードへの展開。


nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0