テキストラスタライズの2つ目の春
3月 24, 2011 by 朝木卓見 | Comments
この記事は Qt Blog の "A second spring of text rasterization" を翻訳したものです。
執筆: Eskil Abrahamsen Blomfeldt 2010年9月9日
「秋は、すべての葉が花になる2つ目の春だ」と言ったのはアルベール・カミュですが、それに習えば Mac OS X は全ての文字が永遠に花咲く常夏です。
この記事の背景には私の Jira のタスクリストにしばらく居座っていて、そしてまもなく完了する テキストレンダリングに関連したタスク があります。このタスクのゴールは、Mac におけるテキストのレンダリング結果をネイティブのペイントエンジンとラスターペイントエンジンとで同一にする事です。このタスクが重要である理由の一つは、QRasterPaintEngine を Mac/Cocoa でのデフォルトペイントエンジンにすることを考えているからです(訳注: この記事の翻訳時点では、既に master ブランチでの変更は行われました)。ラスターペイントエンジンは Qt の標準となるペイントエンジンであり、Windows ではデフォルトで使用されています。Mac OS X のネイティブのペイントエンジンを利用するよりもより良い表示内容を提供できるだけでなく、多くの鍵となる領域でより良いパフォーマンスを得られます。また、複数の異なるプラットフォームでデフォルトのテクノロジーを統一する事によって、Qt のメンテナンスコストを下げ、プラットフォームごとにデグレードを修正するよりもその他の改善に注力できるようになります(つまり、メリットだらけです)。もちろん統一を行う前に、ラスターペイントエンジンがそのプラットフォームにおけるこれまでの Qt のネイティブのルック&フィールを壊さない事を保証する必要があります。[qtbug QTBUG-5053] は、そのために解決が必要なタスクの一つでした。
付け加えると、このタスクは Qt のテキストレンダリングで正確な印刷を実現するために進めている プロジェクト の一部になります。そのプロジェクトについては後日記事を書きたいと思っています。現時点ではラスターエンジンの変更に注力するつもりです(訳注: 昨年9月のお話しです)。
根本的な問題を説明するために、ペイントエンジンがネイティブのものとラスターペイントエンジンに置き換えた場合のスクリーンショットを比較してください。最初の画像はネイティブペイントエンジンで TextEdit デモプログラムを動かしてテキストを表示した場合のものです。
Mac OS X と Windows は共に、文字を画面に描画する際にサブピクセルアンチエイリアスと呼ばれるアンチエイリアスの手法を使用しています。この仕組みの背景には液晶ディスプレイの画素が実際には三つの要素(赤、緑、青(RGB)の三原色)から構成されているということがあります。これらの三色は物理的にそれぞれ別々の画素として画面内に配置されています。通常のアンチエイリアスでは、描画しようとしている形状が画素にどの程度重なるかを求めて、画素の階調レベルに反映させます。サブピクセルアンチエイリアスでは画素内の RGB の三要素の位置などを考慮して、それぞれに対して個別に階調レベルの反映を行います。
これにより、最初の画像の拡大した領域のように、まるで誰かが小さな小さな紙吹雪を吹きかけたような、文字のエッジに虹色の画素がちりばめられたような結果が得られます。
しかしながら、Mac と Windows のテキストのレンダリングで大きく異なる点が一つあります。Windows では各グリフを描画する前に水平方向のヒントを適用しますが、Mac ではヒントを使用しません。ヒントとはフォントに埋め込まれている小さなプログラム(かある自動的に定まるアルゴリズム)を実行することです。そのプログラムではグリフをピクセルグリッドに合わせて配置したり、指定されたサイズやディスプレイの解像度などに最適化された表示にするために、ベクトル図形の制御点を移動させます。加えて言えば、画面上に文書を表示する際に求めたレイアウトがプリンタで同じ文書を印刷する場合にも正しいとは限りません。ヒントを使用しないアプローチは先に述べたような印刷の場合にも正確です。そのため、UI ではヒントを使用してグリフを描画するプラットフォームにおいても、ヒントをオフに出来ることはとても有用です。
Windows で各グリフでフォントのヒントを使用する副作用として、フォントとポイントサイズが決まればグリフが一意に求まるということがあります。画面のどこにグリフのベクトル図形が置かれたかに関係なく、ピクセルグリッドに沿って配置されるため、たとえば水平方向の座標が 0.0、0.5、0.986574 のどれであっても、各サブピクセルの占める割合は同じとなります。
しかし、Mac では文字はサブピクセル単位の座標で配置されるので、描画の際に画素内に占めるサブピクセルの割合は一定ではありません。
前述のサンプル画像をさらに拡大してみましょう。
例として、このスクリーンショットの文字「i」に注目してみましょう。「ipsum」の中では赤と青で描画されています。しかし、「in」と「elit」内では黒とライトブルーおよび、ベージュで描画されています。これはそれらのグリフでサブピクセル座標が異なっており、サブピクセルがピクセル内に占める領域が変化したために生じています。
それでは、同じテキストを Qt 4.7 のラスターエンジンで描画した結果を見てみましょう。

ネイティブペイントエンジンのスクリーンショットと比較してみましょう。この画像の拡大していない領域でまず最初に気が付くのは、各文字の間隔がまちまちであるということでしょう(ネイティブペイントエンジンの画像はクリックしてフルサイズにしてください。「posuere」(訳注: 下から5行目の右から2番目の単語)の「s」と「u」の間隔が良い例でしょう)。拡大した領域をよく見てみると、各グリフの描画結果が全てのサブピクセル座標で同じであることがその理由だと分かります。
ラスターペイントエンジンが開発された時点では Mac には既にネイティブペイントエンジンがあったため、Mac 上でラスターペイントエンジンで UI のテキストを描画することは考慮されていませんでした。その後、ペイントエンジンにグリフキャッシュが実装されました。グリフキャッシュでは一度フォントとサイズを指定されてラスタライズした文字の画像をバッファに保存し、次回同じ文字を再び描画する際にはその画像を再利用します。実際のグリフの描画はコストの高い処理であるため、この機能は描画のパフォーマンスを改善しました。設計段階で Mac での需要は考慮されていなかったため、フォント、グリフ、サイズに対してただ一つのラスタライズ結果しかキャッシュに入れませんでした。しかし、Mac では複数のラスタライズ結果があり得ます。その異なるラスタライズ結果の数はフォントやフォントサイズによって異なるため、不定です。(Qt 4.8 ではいくつかの実験的なリバースエンジニアリングの結果、その数の推測を試みます。同じグリフを12の異なるサブピクセル座標で描画し、その結果から同一であるラスタライズ結果を数えています。 拡大でもしなければ細かな差異はほとんど分からないと思いますが、もしネイティブのテキスト描画で使われている同一のラスタライズ結果の数を求める、より良い方法をご存じであれば教えてください。)

結論としては、Qt 4.8 でこのギャップを埋めるつもりです。先に示したサンプルテキストのラスターエンジンでの描画結果は、ネイティブエンジンのものと同じ結果になるでしょう(実際、双方のスクリーンショットを提示するまでもなく同じものです)。さらに改良を進めていけば、その他のフォントエンジンがサブピクセル座標をサポートするように変更された時にも恩恵が受けられることでしょう。全ての現代的なデスクトッププラットフォームの未来は、味気ないヒントによって固定されたグリフではなく、拡大しても豪華な花園のように美しく、正確なテキストを印刷できる素晴らしいものとなるでしょう。
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.9 Now!
Download the latest release here: www.qt.io/download.
Qt 6.9 is now available, with new features and improvements for application developers and device creators.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.
Great work! You've made use of lots of good modern C++ features to remove limitations we had in the Qt 5 system.
what's the lowest compilers for Qt6? gcc 7.3 or 8.3 ? MSVC2015?
Seems it'll be gcc 8.1 and MSVC 2019.
Well, I tried a few quick benchmarks and it seems that the new conversions of types such as int or QString to and from QVariant are now 2 to 3 times slower than before. This performance regression is not good news to me. :(