KANOU Hiroki
kanou****@khdd*****
2005年 5月 22日 (日) 19:31:19 JST
狩野です。 遅くなりましてもうしわけありません。後半部の肉付けアルゴリズムと、重複 除去処理についての説明を書きました。 こちらを先に改善すれば、頑張って手でスケルトンフォントを作ることも できなくはありません。 3. スケルトンへの肉付けを行い、先端のセリフ処理を行う段階 現在の幾何学的作図に基づく肉付け方式から、サンプルアウトラインの 補間ないし補外に基づく移行を行うべきだと考えています。まず、 移行が必要な理由とその実装の上での検討事項について述べ、続いて、 従来のアルゴリズムでレンダリングを行ったときに発生した問題点 について簡単に述べたいと思います。 3.1. サンプリング方式との優劣比較 どうしてサンプリング方式にしないといけないかというと、作図方式は あまりに難しすぎて、デザイナーとプログラマの作業分離が不可能だから です。また、数式をいじって、可能なパラメータの全範囲で美しい線を 出せるように調整するのには、スケルトンとエディタで同じくらい手間が かかります。 ただし、和田研フォントの肉付け方式を解説した原論文では、見本アウト ラインの変形方式は「エレメントの長さや、制御点間の角度、太さなどの パラメータがある程度以上変化すると対応できないので、エレメントの 種類を増やさなくてはいけない。」とあります。エレメントの種類を 増やしてユーザに指定させるのは非現実的ですので、サンプルの個数を 多数用意させるようにして、最も近いものを自動的に選択するアルゴリズム を考える必要があるでしょう。 多数のサンプルを補間してグリフを作る技術で実用化されているものに、 PostScript フォントの拡張である Multiple Master と、TrueType フォント の拡張である distortable font があります。詳しくは FontForge の マニュアル (fontforge.sf.net/ja/multiplemaster.html) を参照して 頂きたいのですが、パラメータが 4 個までという制限があります。 これはなぜかと言いますと、点を求める方法が n 次元超立方体の補間 (デフォルトではバイリニア法) によるからです。4 個のパラメータがあれば、 それぞれの軸に極大値と極小値を与えた 16 個のサンプルが必要になります。 各軸に中間点を設けて、きめ細かくサンプルを与えるには 3**4 = 27 個の サンプルを与えられなければなりません。 もし仮に、スケルトンの形そのものをパラメータとして使うなら、エレメント は最大 4 個の点を含みますから、(平行移動の自由度を除いて) 6 次元の 自由度をもつことになります。その他に追加のパラメータ (少なくとも線幅 は必要です) が必要なので、最低 7 次元の自由度を扱えなければなりません。 それに、可能なストロークは 7 次元空間の中のごく一部だけですから、 サンプルを 2**n 個用意するというアプローチは使えません。最も近い 1 個 ないし 2 個のサンプルを抽出し、それを変形させるというアプローチの方が 適していると思います。 3.2. 現在考えている方式の概要 最も近いサンプルを検索するときに用いる距離関数は、各エレメントの長さ・ 傾き (2 個目以降は角度変化) をパラメータとして適切なウェイトを掛けて やれば理論的には十分ですが、始点から終点への角度のような、冗長では あるが重要なメトリックも加えてやるほうが効果的だと予想しています。 角度変化については、180°の周りで探索空間を分ける必要があります (通常 使うのはそのうち 1 個だけと思います)。 アウトラインの変形は、論文で引用されている「制御点付近の相対位置指定」 という方式がヒントになると思います (その論文を読んでいないので、単に 想像しただけですが)。 まず太さを変化させる処理について考えましょう。単純に考えれば、ストローク 中心線に垂直な成分を定数倍する変形を行えばいいでしょう。ただし、明朝体 の横画の場合、太さは変わらずに右端のウロコの大きさだけが変わるので、 太さ最大と最小のサンプルを用意しておいて補間させるようにする必要が あります。一般に、肉付けサンプルは同じ形のスケルトンで最大の太さと 最小の太さの両サンプルを対で用意しておくことが推奨されます。 さて、スケルトンの変形に応じたアウトラインの変形ですが、端点近くの点は 端点からの相対位置でいいでしょう。回転させるだけです。折れ曲がり部分 (kokoro の 左下など) は、スケルトンのなす角の二等分線 (より正確には、 アウトラインの曲率の最も強い所から引いた線) とアウトラインとの交点を 原点として、そこに端点を追加したうえで、その前後でそれぞれ、そこからの 相対的な位置 (エレメントの平行成分と垂直成分に分けたもの) が元と一致 するようにすればよいと思います。 中間部の長いベジェ曲線についてですが、その変形には注意が必要です。人間の 目はベジェ曲線を単独で見ているわけではなく、2 本のベジェ曲線によって 囲まれた太さのある画線を見ているからです。 スケルトンへの肉付け方式によって小塚明朝・小塚ゴシック両書体ファミリーを 作成した小塚昌彦さんは、「ゴシックの特徴でもある平行ラインは、曲率と 太さを動かすことでひずみを起こさないように作るため、明朝体よりも時間を 要した。」と図録「タイポグラフィ・タイプフェイスのいま。デジタル時代の 印刷文字」(http://www.joshibi.net/typography/publication/) で書いています。 おそらく、hidari や migi の場合、中間の点からベジェ曲線の制御点への 相対位置を直接求めるのでは線が歪んでしまうでしょう。最も細い所の位置を 動かさないとか、折れ曲がって見える (曲率が最大になる) 位置を保つとかの 工夫が必要となるでしょう。ストローク中心線で、それらに該当する位置を、 ピックアップし、そこにおける太さなどを保存することになるでしょう (折れ曲がり部分と同じようなテクニックを使うことになります)。 「折れ曲がり部分」として述べて来た箇所には、現在 defkazari で定義して いるようなエレメントの接合部も含まれます。部品定義は別のものとなり ますが、補間そのものは同じ手法となります。 解決の必要がある問題としては、エレメントの中間近くの点をどう扱うかと いうことです。近い方の端点からの相対距離を保つようにするより、両端 からの長さの比を保ったほうが適切でしょうが、どうやって「中間近く」 を判別すればよいのか。 3.3. 現在の処理の問題点 さて、サンプリング方式を取ったとしても、エレメントの肉付けを両端と その中間部分に分けて行うという発想そのものは有効です。そして、今までに 今までに出会った問題点は、断片的な形ではありますが、「作業メモとか 考えた事とか」に記載してあります。和田研フォントキットに関連する記述を http://khdd.net/cgi-bin/b.cgi/moji/type/tool/clwfk/index.html に まとめてあります。基本的にこれは、去年までに私が和田研フォントキット で遭遇し、解決するために修正を行うか、回避策を講じたものです。 3.1 (defelement による) 肉付けアルゴリズムの例外 エレメントに加えられる変換が、あまりに縦か横に扁平になりすぎると、 スケルトンのゆがみが大きくなりすぎて数値的な例外が発生します。 例えば、kokoro が「乙」の下半分のように、鋭角の折れ曲がりを持っている 場合 (とくに「乙」が縦方向に極度に圧縮されている場合) 肉付けが 太すぎると端点から立てた垂線同士が交差したり、内側のベジェ曲線の 開始点から制御点に延びるハンドル同士が交差したりということが起こり ます。直接にこれがエラーを生じないまでも、後の段階でエラーとなる遠因 である場合があります。 他にも、詳しく覚えてはいないのですが、明朝の hidari だか migi だかの 払いを求める処理で、点の位置を求めるための補助線 2 本がほとんど平行に なってしまう場合があったと思います。 3.2 交点を求めるアルゴリズムの例外 これも、図形が極度に潰れた場合に起こりやすくなります。例えば、 hidari と ten からなる「くのじ」という部品があります。3 つ並んで 「巛」となる部品です。これを使う部品のうち、最も複雑なのは「鬣」と いう字で、「巛」が縦に潰れて、接合部が鋭角になります。 これにより、4 個の交点が極度に潰れた菱形になったり、左側の交点だけが 非常に遠い場所になったりします。とくに後者が問題となるので、apply.l に adjust-to-diamond という関数を加えて、強制的に補正をかけるように しました。 3.3 セリフと中間部の接続のために中間部を補正するアルゴリズムの例外 defelement で引いた 2 本の画線の対になる線端の相対座標と、defkazari で 引いた 1 本の線の両端の相対座標は、必ずしも一致するわけではありません。 ずれがある時、画線を微調整して両者を重ね合わせることになっています。 この処理 (change-bezier) が、数値的な異常を引き起こすことがあります。 線を縮めたり延ばしたりするときに、ベジェ曲線を表す 3 次式 z0 * (t**3) + 3 * z1 * (t**2)(1-t) + 3 * z2 * t((1-t)**2) + (1-t)**3 (z0, z3 は端点、z1, z2 は制御点の座標) の t の値を調節して内外挿して います。t < 1 なら問題はないのですが、t > 1 の場合 (延長する場合)、 とくに 3.1 や 3.2 の処理によって得られたような極端に歪んだ線 (直線 からのずれが大きい線) だと、線端が遠方に移動してしまうということに なります。 もっと汎用的な方法として、ベジェ曲線の回転と拡大縮小だけで重ね合わせ を行う方法 (rotscale) を導入しました。ただ、この方法は線の進行方向に 垂直な方向に線を移動させることが多く、ストロークの太さを変化させて しまう原因となるので、t の値が 2 より大きいときのみ切替えるように しました。さらに、従来方式で作った曲線が端点の外にはみ出す制御点 (http://fontforge.sf.net/ja/problems.html に図示されています) を 持っているときも、新しい方法に切替えるようにしてあります。 4. 重複除去処理 重複除去処理が無限ループするのは、2 個のループの重なり合いが非常に 小さく、数値誤差のために片方の交点しか「交差している」と判定されない 場合です。また、重複除去処理の対象となる 1 本 1 本のループの中に、 自己交差を持った曲線が存在する時には、作成されたアウトラインが 壊れます。3.3 で、「端点の外にはみ出す制御点」を避けるようにしたのは、 本体とウロコ部分の接合部での自己交差を避けるためです。 内部処理のどこが問題なのかについてはあまり詳しく調べていません。 その前の 3.1 〜 3.3 の時点で異常な曲線が発生するのを避けたり、時には 文字のバランスをいじったりして、回避することが可能であることと、 重複除去処理を FontForge 側で行うこともできることが理由です。 ただ、無限ループは検出することができるので、それは修正するべきだと 思います。 図を書いたりプログラムを動かしたりしながら口頭でで説明するならずっと簡単 なのですが、テキストで説明するのはなかなか大変でした (説明を書き残すための いい機会でしたが)。CodeFest のような場をうまく活用して問題を解決して いければと思います。 狩野 宏樹 <kanou****@khdd*****>