自分の筆跡をコーディングする技術
(amygoodchild.com)ブロックスクリプト
- 前回の記事は、ブロック体のアルファベット版についてのものだった。
- 要約すると、次のような工程で作成した:
- 各文字のパスの主要な点を定義するコードを書く(文字あたり約10点)。
- Chaikin の曲線アルゴリズムを使ってパスを滑らかにする。
- パスを可変太さの形状に変換する。
- p5js を使って形状のパスを描画する。
- 見た目はこのようなものだった:
- このシステムで文章を生成する方法についての記事もまもなく公開予定。ニュースレターに登録して知らせを受け取ってほしい。
- もともと文字のパスを定義する作業は、コード内に位置を書き込み、文字が正しく見えるよう点を調整するという非常に手作業なものだった。
- 筆記体をコーディングするときは、この工程を簡略化した。
文字デザイン
- p5js エディタから簡単にアクセスできるよう、パスの主要な点を定義して出力するツールを作った。
- サンプル文字を表示し、新しい文字をデザインする領域を用意した。
- 手順は次のとおり:
- パスの主要な点をクリックして配置する — 生成された Chaikin 曲線パスが表示される。
pを押して編集モードに切り替える。- 点を選択し、目的の位置へドラッグする。
enterを押してパスをコンソールに出力する。
- 各文字について 2〜3 個のオプションを作った。
- 結果のパスは次のようになる:
[{x:0.7,y:22.5},{x:8.2,y:18.1},{x:8.9,y:11.2},{x:3.7,y:11.4},{x:1.7,y:18.9},{x:8.4,y:22.4},{x:17.7,y:22.0}] - 自分の筆跡をガイドとして使いたかったので、小文字と大文字の見本を書き、画像をツールに直接読み込んでなぞった。
w/a/s/dキーで画像を正しい位置に置き、r/eキーで画像を拡大・縮小した。- 数値は、文字生成ウィンドウ内にその領域を配置するための x y 座標である。
- すべてのパスを作成し、曲線を描き、可変幅の形状へ変換したあと、各文字は個別にはこのように見えた。
筆記体化
- 文字同士をつなげるのが簡単な場合もある。主要な点のパスから次のパスへそのまま移動し、Chaikin 曲線をまとめて一度適用すればよい。
- しかし、うまく合わない文字ペアもある。
- たとえば na の組み合わせでは、n の最後の点が低く、a の最初の点が高いため、a を斜めに横切るパスができて e のように見えてしまう。
- ti の組み合わせでは、t がベースラインの上で終わり、i がベースラインから始まるため、不自然なうねりが生じる。
- こうした問題を解決するため、a の始まりに追加の点を足したり、t の最後の 2 点を削除したりできる。
- しかし、すべてのケースでこのように文字を変更できるわけではない。
- たとえば a が単語の先頭にある場合、その追加点は誤った位置にあり、w のような文字の後に来ると、今度は別の形で a を横切る線が生まれてしまう。
- t が k と組み合わさる場合も変形してしまう。
- 文字パスの始点と終点は、隣接する文字との位置関係に応じて変える必要がある。
- 当初は特定の「問題」ペアを列挙して、そのためのルールを書くつもりだったが、最終的には各パスの先頭と末尾に数字を追加して、次を表すようにした:
- 他の文字とつながらない (0)
- ベースライン付近で他の文字とつながる (1)
- ベースラインのすぐ上で他の文字とつながる (2)
- x-height 付近で他の文字とつながる (3)
- 例:
- 各文字パスは今では次のようになる。先頭と末尾の 1 桁の数字に注目:
[0,{x:12.2,y:13.2},{x:13.5,y:11.0},{x:6.2,y:8.4},{x:1.1,y:13.0},{x:1.8,y:19.0},{x:7.0,y:23.4},{x:15.2,y:23.6},{x:18.4,y:22.1},1] - すべての文字ペアをテストした:
- ここでは、各文字に複数のパスオプションがあり、隣接する文字に応じて文字が編集されることで生じる変化を見ることができる。
- 理想的には各文字について最低でも 5〜6 個のパスオプションを持ちたいが、ファイルサイズとのバランスを取る必要がある。
単語生成
- 単語が生成されるとき:
- 各文字について 2〜3 種類の候補の中から基本パスが選ばれる。
- パスの終端に関する情報が隣接する文字に渡される(同じ文字でも別のパス候補は異なる終点を持つことがあるため、まずすべての文字パスを選んでおく必要がある)。
- 基本パスは隣の文字に応じて調整される。たとえば、前の文字の終端の高さが 2 ならこのパスの先頭から 1 点を削除し、次の文字の開始高さが 1 なら特定の位置に追加の点を入れる、といった具合だ。
- 調整関数は少し複雑になることがある。たとえば文字 q の関数は次のようになる:
// ip = パス // pc = 前の文字の終端情報 // nc = 次の文字の始端情報 // n = この文字に対して選択されたパスのインデックス adjust: (ip, pc, nc, n) => { // この文字の終端に 70% の確率で中断を追加 if (rand() < 0.7 ) ip.splice(-1, 1, 0); // 4 つの候補のうち [2] がこのパスに対して選ばれた場合 if (n < 2) { // 前の文字が 3 で終わるなら最初の 2 点を別の点で置き換える if (pc == 3) ip.splice(1, 2, {x:10,y:12}); // そうでなく 0 以外なら先頭に点を追加する else if (pc > 0) ip.splice(1, 0, {x:10,y:20}); } // この文字と次の文字の間に中断がない場合 (0) if (nc > 0 && ip[ip.length-1] != 0){ // 最後の 2 点を別の点で置き換える ip.splice(-3, 2, {x:16,y:34}); } } - ただし、もっと短いことも多い。たとえば文字 n の関数は次のようになる:
adjust: (ip, pc, nc) => { // 次の文字が 3 で始まるならランダムに中断を作るか最後の点を移動する if (nc == 3) rand() < 0.3 ? ip.splice(-1, 1, 0) : ip.splice(-2, 1, {x:17,y:23.8}); } - 次に、すべての文字の基本パスがひとつにつながれる。このときパス中の 1、2、3 は無視されるが、0 が現れるたびに新しいパスを開始して中断を作る。
- その後、パスを曲線化し、可変幅の形状へ変換し、Perlin ノイズを使って少し揺らぎを加えると、筆記体の文字はこのように見える。
- この文章を生成する方法についての記事もまもなく公開予定。ニュースレターに登録して知らせを受け取ってほしい。
- おまけとして、プロッターで出力したコーディングされた筆跡と実際の筆跡を並べて比較した。
サイズはどれくらいか?
- ブロック体用の文字クラスは 9.7kb だった。
- 筆記体用の文字クラスは現在 26.1kb である(圧縮後)。
- このクラスには、各文字に対する複数のパスと、点を調整する関数が含まれているため、より大きい。ただし、ほかにもいくつか節約できる方法はある。
- さらに削減できる余地はありそうだ。自分はコードゴルフの達人ではないが、いくつかアイデアはある。
- たとえば現在、文字は基本フォントサイズ 20 を基準に設計されてから拡大縮小される。そのため多くの点が
x: 14.5のように定義されているが、基本サイズを 200 に変えれば145のように定義して小数点をなくせる。この変更は慎重に行う必要があるので、今後の ToDo にしている。
使い道
- この筆跡の主な目的は、自分が作業中の図のタイトル、ラベル、走り書きメモのためである。
- しかし、テキストそのものをいじって遊ぶのもとても楽しい。
- パスをエンコードしているおかげで、フォントを使う代わりにパス自体で遊べる。文字の位置を変えたり、個々の文字の太さを変えたりできる。
- 次はこの筆跡を図に組み込む予定だが、テキストそのものに焦点を当てた何かを作るつもりでもある。とても美しく、可能性がたくさんある。
GN⁺の意見
- この記事は、JavaScript と p5.js を使って筆跡をデジタル化するプロセスの興味深い例を示している。ソフトウェアエンジニアにとって、創造的なプロジェクトを通じてコーディングスキルを磨く良い機会になりうる。
- Chaikin の曲線アルゴリズムのような数学的アルゴリズムを、実際のプロジェクトにどう適用するか学べる。これはグラフィックスプログラミングへの理解を深める助けになる。
- パス調整関数のような複雑なロジックをどう扱うか学べる。これはコードの柔軟性と拡張性を高めるうえで重要な技術である。
- このプロジェクトは、ファイルサイズ最適化のような実用的な課題も扱っている。これは実際のソフトウェア開発で重要な考慮事項だ。
- この技術を導入する際には、パス定義や調整関数の作成に多くの時間がかかる可能性がある。しかし、得られる成果物は非常にパーソナルでユニークなテキスト表現を提供してくれる。
まだコメントはありません。