Rustでピクセルフォント175種類を作った開発者
(chevyray.dev)- 2023年12月に公開された 175 Pixel Font Megapack は、独自のRustツールチェーンでフォント生成、品質テスト、配布まで処理したピクセルフォントプロジェクト
- 以前のパックは2016年に12種類、2018年に40種類のフォントだったが、新パックは 品質・スタイル・言語対応・配布自動化 を一気に引き上げる目標で始まった
- 新フォントはASCIIのみ対応という限界を超え、EFIGS 向けの拡張ラテン文字176字をサポートするが、アジア圏・非ラテン文字は品質と制作時間の問題で除外された
- Rustツール pifo は、PNGタイルシートとTOML設定からグリフ輪郭、自動カーニング、TTF、タイルシート、パック済みテクスチャアトラスを生成し、175種類のフォントも数秒で処理する
- 大型サンプル画像、自動カーニング、itch.io向けの
butler配布スクリプトにより、手作業での確認・修正・アップロードの負担が減り、200種類近いフォントの品質を素早く改善できた
以前のピクセルフォントパックからMegapackへ
- 2016年の最初のフォントパックは、Ikenfell の制作を始めていた時期に作った12種類のフォントのセットだった
- ゲーム用に作っていたフォントを整え、いくつか追加してitch.ioで販売した
- 複数のインディーゲームで使われ、反応も良かった
- 2018年の2つ目のパックでは 40種類のフォント へ増え、品質も大きく向上した
- 文章レンダリングを良くするためにカーニングテーブルを作成した
- 複数のゲームエンジンで使いやすい事前ビルド形式も提供した
- インディー開発者のゲームスクリーンショットに自分のフォントを適用して見せる形で宣伝した
- 以前のパックは数百本のインディーゲームで使われ、Nintendoの Cadence of Hyrule にも採用された
- Love Conquers All Gamesの Get in the Car, Loser でも複数のフォントが使われた
- Ikenfell発売後の長い燃え尽き期間にもRustの腕を磨き、いつか新しいピクセルフォントパックを作るためにフォント制作を続けていた
Megapackの目標
- 新パックの当初の目標は100種類のフォントだったが、最終的には 175種類のピクセルフォント になった
- 以前のパックはそれぞれ12種類と40種類だった
- 新パックはSF、ファンタジー、ホラー、農場、コージーゲームなど、さまざまなジャンルで使えるフォントを目指した
- 以前のパックで最も多かった要望と不満は、ASCII文字しか対応していない 点だった
- 新パックは English, French, Italian, German, Spanish を意味する EFIGS に対応するため、拡張ラテン文字セットを採用した
- 最終的な文字セットは176字
- アジア圏または非ラテン系言語は、文字セットに不慣れで求める品質を出しにくく、制作時間も大幅に増えるため除外した
- ゲームUIには視覚的な階層が必要なため、各フォントを単一スタイルではなく フォントファミリー として作ることを目指した
- 例として Virtue フォントファミリーには20種類のスタイルが含まれる
- 開発者は異なる雰囲気のフォントを混ぜずに、サイズや太さの変化を付けられる
- カーニングは以前のパック制作で大きな時間消費要因だった
- 対応文字が176字あると、可能なカーニングペアは最大
176²個まで増えうる - すべてのペアを手動入力しないために、大半は自動計算し、アルゴリズムで補えない部分だけを手動入力する方式を採った
- 対応文字が176字あると、可能なカーニングペアは最大
- 品質管理と配布も自動化の対象になった
- 以前はピクセルの不具合やカーニングバグを直した後、再エクスポートしてテストし、itch.ioに手動でアップロードする必要があった
- 新しいツールチェーンは、サンプルテキストとカーニングの可視化を生成して問題を素早く見つけ、修正結果を即座に確認できることを目標にした
フォントデザイン品質を高めるための準備
- ピクセルアートとピクセルフォントの経験は豊富だったが、伝統的な 書体デザイン を本格的に学んだことはなかった
- 作業中はRichard Poulinの Design School: Type を参考にした
- 書体デザインの用語や慣習を身につけるのに役立つ入門書として使った
- 身の回りで見かけた文字の写真も参考資料として集めた
- 小さな店の看板、博物館、ギャラリー、祭り、地域イベントのポスター、本の表紙などが参考になった
- フォントをコピーするのが目的ではなく、文字の形、ベースラインより下に伸びる文字、細くて広い文字の違いを研究するためだった
- 屋外で見た装飾的なフォントを観察し、多くの「派手な」フォントはすべての文字を過剰に飾るのではなく、大文字に装飾を集中させ、小文字はより控えめに処理していることを学んだ
Rustツール pifo の役割
- 目標を達成するため、独自のRustプログラム
pifoを作成した - フォントデザイン自体は一般的なピクセルアートツールで行った
- 入力はPNGタイルシートと設定用TOMLファイル
- 例のコマンドは
pifo --all --output "Faraway" --input "Faraway*"の形
pifoは入力画像を個別のグリフタイルに切り分け、グリフ輪郭を作成し、カーニングペアを自動計算したあとTTFファイルとして出力する- 複数のエンジンですぐ使える別形式もあわせて生成する
- 個別グリフの処理は並列化されており、単一フォントならほぼ即時に処理される
- 175種類のフォントを1つずつ処理しても数秒しかかからない
- 使用した主なRustクレートは次のとおり
グリフ輪郭生成の過程
- 各フォントシートは タイルシート とTOML設定ファイルで構成される
- 設定には
version,baseline,line_gap,spacing,metrics,auto_kerning,auto_kerning_min,manual_kerning,skip_kerning_left,skip_kerning_rightなどが含まれる - グリッドサイズは変えられるが、常に均一なグリッドである必要があり、文字は決められた位置に置かなければならない
- ツールは100%白のピクセルだけを処理し、チェック柄の背景とベースラインはガイドとしてのみ使う
- 設定には
- TrueTypeグリフは1つ以上の 輪郭(contour) で構成される
- ピクセルフォントでは、連結したピクセル群、つまりクラスターごとに輪郭を作る必要がある
- たとえば小文字の
tに分離した2つのピクセル塊があれば、2つの輪郭が必要になる
- 輪郭生成は複数段階で進む
RgbaImageから白いピクセルだけを取り出し、Point構造体のHashSetに集める- 任意のピクセルから始めて隣接ピクセルへ フラッドフィル を行い、未訪問ピクセルがなくなるまで繰り返してクラスターを見つける
- 各クラスターで隣接ピクセルが存在しない面を 露出した edge として集める
- edge の head と tail をつないで閉じた経路を作り、輪郭へ変換する
- 穴のあるクラスターでは複数の輪郭ができることがある
- 外側の輪郭は時計回り、内側の穴は反時計回りに巻かれる
- この向きの違いが、TTFで穴を表現する方式と一致する
- 連続する直線上の中間点は削除する
- ファイルサイズを減らし、ラスタライズ速度を上げるための最適化
カーニングテーブルの自動化
- TTFを作るうえで、輪郭の次に必要なのが カーニングテーブル
- カーニングテーブルは特定の文字ペアを左へ詰め、単語がより自然に見えるようにする
- たとえば
Va,ltの組み合わせを1ピクセル左に詰めると見栄えが良くなる
- 手動カーニングはTOMLの
manual_kerningに指定する{ left = "V", right = "a", kern = -1 }のような形式- アクセント付き文字をすべて個別指定しなくてよいように
alts = trueオプションをサポートする aに対してàáâãäåのような代替文字をマッピングし、同じカーニングを適用する
- 自動カーニングは右側グリフを左へ1ピクセルずつ移動しながら計算する
- 左側グリフに触れない最も近い位置を探す
- 角同士の接触も接触として扱う
- 例の
LVではVを1ピクセル詰めることは可能だが、2ピクセルではピクセルが接触するため-1がカーニング値になる
- 自動カーニング計算は手動指定値と除外設定を尊重する
skip_kerning_left,skip_kerning_rightで特定方向のグリフを除外できるrayonを使ってすべてのカーニングペアを並列計算する
エクスポート形式
- PIFOは TTF だけでなく、タイルシートとパック済みテクスチャアトラスも生成する
- TTFファイルは複数テーブルから成るバイナリファイル
- エクスポートツールは
head,hhea,maxp,OS/2,hmtx,cmap,loca,glyf,kern,name,postテーブルを埋める - 各テーブルは位置、長さ、チェックサムを記録する必要があり、
begin_table()とend_table()で処理する
- エクスポートツールは
- ピクセルフォントの基本サイズは 16 に設定した
- サイズ16でレンダリングすると、ピクセルが正確に
1 pxになる - ブレなく拡大するには
32,48,64のように16の倍数で描画すればよい
- サイズ16でレンダリングすると、ピクセルが正確に
- タイルシートのエクスポートは、入力シートとは異なりUnicodeコードポイント順に並べられる
- 同時生成されるデータファイルには、行・列、タイルサイズ、ベースライン、行間、スペース幅、グリフメトリクス、カーニングテーブルが入る
serdeでJSON、XML、TOMLなど複数形式にシリアライズする
- パック済みアトラスも生成する
- 独自の矩形パッカークレート crunch を使用する
- 密にパックすると位置情報が失われるため、レンダリング用に
x,y,w,h,off_x,off_y,advといった追加データを含める
品質テストとサンプル画像
- 望む品質を得るため、PIFOに 巨大なサンプル画像生成 機能を追加した
- サンプル画像は複数のテストブロックで構成される
- すべての文字といくつかのテスト文
- Terry Bissonの短編 They're Made out of Meat の一部
- Text for Proofing Fonts: A farewell to The Quick Brown Fox の戦略を活用したテスト単語
- すべての数字の組み合わせと通貨記号
- 大文字、小文字、大小混在のカーニングペア
- さまざまな句読点
- 作業中にサンプル画像を即座に生成できたため、問題を素早く見つけて修正できた
- この過程によって、200種類近いフォント全体の品質と仕上がりを高めた
itch.io 配布の自動化
- 最終成果物は 175種類のピクセルフォント で、ダウンロード販売先のオンラインストアとして従来使っていた itch.io を選んだ
- 最も面倒だった作業は、各フォントごとに個別のitchプロジェクトを作ることだった
- この作業はフォントごとに1回だけ必要だった
- その後のアップロードと保守はコマンドラインスクリプトで処理できた
- 配布スクリプトは3段階で構成される
- PIFOをリリースモードでコンパイルする
- 必要なフォントアセットをビルドする
- 各フォントのitchプロジェクトを新しいアセットで更新する
--input "Faraway*"のような入力パターンは、Faraway - Regular,Faraway - Boldなど同じ接頭辞を持つスタイルを見つけ、1つのフォントファミリーパッケージにまとめる- 最終アップロードにはitchが提供するコマンドライン配布ツール
butlerを使ったbutler push ../distro/faraway chevyray/pixel-font-faraway:assetsの形で配布する- Butlerが変更されたファイルを追跡して必要な部分だけ更新するため、別途ファイルのハッシュ化や特別なバージョン管理は不要だった
1件のコメント
Hacker News のコメント
素晴らしいフォント群で、記事も興味深い。
右上の架空ゲーム https://chevyray.dev/blog/creating-175-fonts/old_previews.pn... が実際のゲームだったらいいのにと思った。
「GBA 向けの Wonder Boy in Monster World」のような雰囲気があって気に入った。
ただしライセンスを見ると、TTF をオープンソースソフトウェアプロジェクトで使うことは許可されていないようなので注意が必要。
弁護士ではないが、最後の条項は明確に禁止しているように読める: https://github.com/ChevyRay/pixel_font_megapack_license/blob...
あのモックアップの場面は Celeste のオープニングシーンを参照したもの。
意図としては明らかに、ゲームエンジンやテンプレートよりも、オープンソースが比較的少ない最終製品段階のゲームで使ってほしいという方向に見える。
この記事を読みながら、別の著作権上の境界事例も思い出した。米国では書体(typeface)は著作権保護の対象ではないという点だ。
フォントはコンピュータプログラムとして保護されるが、書体や単純なビットマップフォントはそうではない。
つまりこの作業フローは、著作権のない元のタイルシートから著作権のある TTF を作り、さらに著作権のない出力タイルシート/アトラスにも戻る、ということになる。
本当に印象的。
完全なフォントセットを作るには、簡単に 1 年かかることもある。
'A' から 'Z' まで、大文字・小文字、括弧・アンパサンド・感嘆符のようなすべてのグリフを作り、可能なら太字・斜体のようなバリエーションも作らなければならない。
こうしたバリエーションがないと、フォントの用途は限られる。
特に腕前を分けるのはカーニング、つまり文字間の間隔で、こだわろうと思えば際限なくこだわれる。
記事本文でも、176 文字をサポートすると最大 176² = 37,976 個のカーニングペアを入力する必要があるため、自作ツールで大半を半自動化し、アルゴリズムで足りない部分だけ手入力することにしたとある。
他の言語をサポートするには、ラテン文字を使うフランス語、スペイン語、イタリア語、ポルトガル語のような言語であっても追加文字が必要になる。
その次にはギリシャ文字、キリル文字、日本語、中国語までサポートしたくなるかもしれない。
例えば Wikipedia によると、「Noto fonts は 77,000 文字以上をサポートしており、これは Unicode 15.0 で定義された 149,186 文字のおよそ半分」だという。
何にでも際限なくこだわることはできる。
言語、文章、音楽、書道のような分野は、こだわりの強い人たちを強く引きつけるようにも思う。
要するに、こういう言い方は参入障壁を作ることのように聞こえるということ。
ある分野に新しく入ろうとする人を discouraging する話は、いつも少し敏感に聞こえる。
とても良い。
ひとつ気になるのは、なぜスカンジナビア系の特殊文字のうちこの部分集合を選んだのかという点。
スウェーデン語には äöå、ノルウェー語/デンマーク語には æøå があるが、このフォントには æ はあって ø はない。
それなら æ を外してもスウェーデン語対応は維持でき、ø を追加すればノルウェー語とデンマーク語にも対応できる。
単純な抜けなのか、それとも æ は使うが ø は使わないロケールがあるのか気になる。
なお、æ をフランス語で使われる別グリフである œ 合字と混同したわけではない。
ただし Æ は「encyclopædia」のような古い英語テキストや、ラテン語由来の単語の複数形にも使われる: https://en.wiktionary.org/wiki/Category:English_plurals_in_-...
実はプロジェクトがかなり進んでから、自分の文字セットに ø が抜けていることに気づいた。
すべてのフォントに再追加してパッチすることはもちろん可能だが、それだけの作業をするほど需要があるか見ようと思って待っていた。
それでも、さらに 2 つの言語をサポートできるならかなり良さそうだ。
サイトで使われている一部のフォントとシンプルなスタイリングも気に入った。
幸い、別記事で公開されている: https://chevyray.dev/blog/how-this-site-is-made/#catppuccin-...
興味深い。
ピクセルフォントを TTF にエンコードするときにベクターに変換するとは以前は知らなかった。
ちゃんとしたテキストレンダラーに読み込む人にとっては、ある程度なめらかな拡大/縮小も可能になりそう。
もちろん、ピクセルフォントをほぼ 1:1 でしか使わないゲームエンジン側は例外かもしれない。
こうしたツールにはピクセルフォントという概念がないので、ヒンティングとアンチエイリアスを切った TTF を使えば、それらのアプリで活用できる。
かっこいい
この取り組みが作った人の金銭的な助けにもなったというのも良い
Chevy Ray という名前はインディーゲーム方面で覚えているけれど、具体的に何を作ったのかは思い出せない
たしか 2010〜2012 年ごろ、VVVVVV や Nidhogg、Canabalt みたいな流れの何かだった気がする
Beacon も素晴らしかった — 昔を思い出すし、インディーゲームが恋しい
素晴らしい
この記事の作者のような ダ・ヴィンチ型の人物を見ると、アーティストとエンジニアが明確に分かれておらず一体だった時代を思い出す
「ダ・ヴィンチの発明トップ10」みたいな記事ではなく
実際には両者の間にはかなり大きな隔たりがあった
私だって Maya で、検証されていない怪しげな力学を使って Alpha Centauri まで行く宇宙船をモデリングすることはできる
でもそれを実際に作って星間空間へ送り出すのとはまったく別物だ
絵はきれいで想像力豊かではあるけれど
Rust では、こういう処理を 複数コアで実行できるようにするのが面白いほど簡単
関数チェーンに呼び出しを 1 つ挟むだけで十分なこともある
言語が定めたルールに従うという前提では、マルチスレッディングはほとんど「タダ」に近い
Amdahl の法則の影響は依然としてあるが、少なくともアプリが目の前で爆発することはない
Scala と Java も基本的にこうした機能をサポートしている
本当に素晴らしい取り組みで、記事も良い
いくつかコツを使えば、多くのアルゴリズムをもっと速くできそうだ
読んだ限りでは、文字の描画と ASCII 変形のマッピングで、ハッシュマップを ビットマップとバイトインデックス配列に置き換えるのが手がかりになりそう
もちろん、すでに十分速いので、このコードでは大きな問題ではない
サイトには全コードは載っていないが、実際には文字列やデータ構造をかなり多く割り当てたり複製したりしている
明らかなところでは最適なコードを書いたし、ビットマップのコピーや簡単に並列化できる処理のような部分は一瞬で終わるほど速かったので、他の部分の最適化は試しもしなかった
それでも、やろうと思えばできる
Rust、C++、Go のような 低レベル寄りの言語がどれほど速いかは忘れがちで、そこにマルチスレッディングの基本ツールやワークキューなどを使うとなおさらだ
フォントに関心がある人は https://tomorrow.type.today/ も見てみるといい
優れた独創的・実験的フォントを作っている タイポグラフィスタジオだ
私たちのチームの一人がタイポグラフィに深くはまっていて、彼らの作品コレクションを https://play.soot.com/tomorrowtypetoday に作っている