重要:このアプローチは時代遅れで、DirectWriteを使用してすべてのOpenTypeパンを入手できるようになりました。 ここで特定の実装の例を見つけることができます 。
はじめに
ご存知のように、WPFにはかなり強力な組版システムが組み込まれています。 残念ながら、このシステムは主にドキュメントの操作に重点を置いているため、OpenTypeサポートなどの活版印刷のすべてをLabelなどの単純なコントロールに使用することはできません。 しかし、何があっても、テキストの高品質なレンダリングを取得する機会はまだあります。少し苦しむ必要があります。
挑戦する
なぜテキストを描画するのですか? ええと、例えば、私はブログに美しい見出しを付けたい-私が選んだフォントで作った。 もちろん、画像またはFlashに基づくソリューションは既にありますが、リソースを集中的に使用する(SVGのレンダリングなど)か、IEと互換性がありません(たとえば、 Canvas
要素を使用するソリューション)。 さらに、利用可能なシステムはどれもOpenTypeまたはClearTypeのいずれもサポートしていません。つまり、小さなテキストには不便であり、高価なフォントへの投資を完全に償却していません1 。
解決策
見出しを取得するために、活版印刷システムWPFを使用します。 目標はかなり原始的であるため、使用する主なクラスは3つだけです。
-
Run
このクラスには、WPFの最小テキスト長が含まれ、HTMLの<span>
に類似しています。 各Run
は独自の活版印刷スタイルがあり、レイアウト中に1つの文に太字、斜体、その他のスタイルを混在させることができます。
-
Paragraph
は、HTMLの<div>
段落または類似物です。 その本質は、複数のRun
(だけでなく)含み、それらを1つのユニットとして表示することです。 見出しを作成する予定なので、1段落で十分です。
-
FlowDocument
は、段落を含む可能性のあるドキュメントです。 実際、これは異なるテキストブロックを保持し、たとえば異なるページサイズに適応できる一種のコンテナです。 これは実際には必要ありませんが、コンテナとしてのドキュメントは、視覚情報(つまり、テクスチャ)を引き出すので便利です。
上記の3つの構造は非常に使いやすいです。 これはかなり単純な例です:
// <br/>
Run r = new Run( "Hello rich WPF typography" );<br/>
// <br/>
Paragraph p = new Paragraph();<br/>
p.Inlines.Add( r );<br/>
// <br/>
FlowDocument fd = new FlowDocument();<br/>
fd.Blocks.Add(p);<br/>
サブピクセル最適化
この時点で、 FlowDocument
をテクスチャに描画することはできますが、サブピクセルレンダリングではテキストがよりシャープに見えるのに対し、単純な白黒のアンチエイリアスが得られます。
ClearTypeに似た効果を得る方法を見てみましょう。 まず、水平方向に3倍の情報を取得する必要があるため、テキストを3倍に拡大します。
DocumentPaginator dp = ((IDocumentPaginatorSource)fd).DocumentPaginator;<br/>
ContainerVisual cv = new ContainerVisual();<br/>
cv.Transform = new ScaleTransform(3.0, 1.0);<br/>
cv.Children.Add(dp.GetPage(0).Visual);<br/>
そこで、(ドキュメントのビジュアルコンポーネント用の) Visual
要素のコンテナを作成し、ドキュメントから最初のページを引き出し、このContainerVisual
配置して、水平方向に3倍に伸ばしました。 すべては順調ですが、これまでのところ、これは何らかの方法で描画する必要がある単なるVisual
です。 問題ありません-これには、 Visual
をビットマップに直接描画する対応するAPIがあります。 むしろ、 Bitmap
なく、 RenderTargetBitmap
:
// . 3<br/>
RenderTargetBitmap rtb = new RenderTargetBitmap(2400, 100, 72, 72, PixelFormats.Pbgra32);<br/>
rtb.Render(cv);<br/>
おそらくここでは、おなじみのSystem.Drawing.Bitmap
への直接変換がないため、WPFの「気まぐれ」が始まります。 しかし、これは何もありません-データをストリームにシリアル化し、このストリームからそれらを取得するだけで十分です。基本的に同じことを取得します。
PngBitmapEncoder enc = new PngBitmapEncoder();<br/>
enc.Frames.Add(BitmapFrame.Create(rtb));<br/>
Bitmap zeroth;<br/>
using (MemoryStream ms = new MemoryStream())<br/>
{<br/>
// <br/>
enc.Save(ms);<br/>
// <br/>
zeroth = new Bitmap(ms);<br/>
}<br/>
それで、「ゼロ」ビットマップ、つまりダンスをするストーブを得ました。 ビットマップを取得して保存すると、次のようになります。
驚くべきことではありません-これは実際には、WPFタイポグラフィシステムを使用して3倍に拡大されたテキストです。 さて、サブピクセルの最適化のために画像を準備するために、各ピクセルのエネルギーを隣接ピクセルに分配します-左側に2つ、右側に2つ 。 これにより、非常にスムーズで迷惑なユーザー描画ができなくなります。 これを行うには、 argb
という便利な構造を作成します。
public struct argb<br/>
{<br/>
public int a, r, g, b;<br/>
public void AddShift(Color color, int shift)<br/>
{<br/>
a += color.A >> shift;<br/>
r += color.R >> shift;<br/>
g += color.G >> shift;<br/>
b += color.B >> shift;<br/>
}<br/>
}<br/>
この構造の目的は1つだけです。特定のColor
構成要素を取得し、シフトすることでパラメーターを調整し、結果を記録することです。 次に、この構造を使用してみましょう。
public Bitmap Coalesce(Bitmap bmp)<br/>
{<br/>
int width = bmp.Width;<br/>
int height = bmp.Height;<br/>
Bitmap output = new Bitmap(width, height);<br/>
for ( int y = 0; y < height; ++y)<br/>
{<br/>
for ( int x = 2; x < width - 2; ++x)<br/>
{<br/>
argb final = new argb();<br/>
final.AddShift(bmp.GetPixel(x - 2, y), 3);<br/>
final.AddShift(bmp.GetPixel(x - 1, y), 2);<br/>
final.AddShift(bmp.GetPixel(x, y), 1);<br/>
final.AddShift(bmp.GetPixel(x + 1, y), 2);<br/>
final.AddShift(bmp.GetPixel(x + 2, y), 3);<br/>
output.SetPixel(x, y, System.Drawing.Color.FromArgb(<br/>
Clamp(final.a),<br/>
Clamp(final.r),<br/>
Clamp(final.g),<br/>
Clamp(final.b)));<br/>
}<br/>
}<br/>
return output;<br/>
}<br/>
上記では、 Clamp()
関数も使用しました。これにより、色の値が常に255以下になります。
このテキストをもう一度見ると、興味深いものは何も表示されません。テキストは水平方向に「わずかにぼやけている」だけです。
次のステップは、最終画像を取得することです。 サブピクセルのアルファ値をそれぞれ赤、青、緑として使用し、水平方向に3回絞ります。 唯一の修正は、これらのアルファ値を反転する必要があることです。つまり、255から値を減算します。
Bitmap second = new Bitmap(( int )(first.Width / 3), first.Height);<br/>
for ( int y = 0; y < first.Height; ++y)<br/>
{<br/>
for ( int x = 0; x < second.Width; ++x)<br/>
{<br/>
// -, 255<br/>
System.Drawing.Color final = System.Drawing.Color.FromArgb(255,<br/>
255 - first.GetPixel(x * 3, y).A,<br/>
255 - first.GetPixel(x * 3 + 1, y).A,<br/>
255 - first.GetPixel(x * 3 + 2, y).A);<br/>
second.SetPixel(x, y, final);<br/>
}<br/>
}<br/>
最後にできることは、ビットマップをトリミングすることです。 それだけです:
その結果、ClearTypeのようなレンダリングをサポートするテキストを取得しました。 OpenTypeのサポートに関しては、とても簡単です。 たとえば、私のサイトでは次のスクリプトを使用しています。
Run r1 = new Run(text.Substring(0, 1))<br/>
{<br/>
FontFamily = new FontFamily(fontName),<br/>
FontSize = fontSize,<br/>
FontStyle = FontStyles.Italic<br/>
};<br/>
if ( char .IsLetter(text[0]))<br/>
r1.SetValue(Typography.StandardSwashesProperty, 1);<br/>
Run r2 = new Run(text.Substring(1, text.Length - 2))<br/>
{<br/>
FontFamily = new FontFamily(fontName),<br/>
FontSize = fontSize,<br/>
FontStyle = FontStyles.Italic<br/>
};<br/>
r2.SetValue(Typography.NumeralStyleProperty, FontNumeralStyle.OldStyle);<br/>
Run r3 = new Run(text.Substring(text.Length - 1))<br/>
{<br/>
FontFamily = new FontFamily(fontName),<br/>
FontSize = fontSize,<br/>
FontStyle = FontStyles.Italic<br/>
};<br/>
r3.SetValue(Typography.StylisticAlternatesProperty, 1);<br/>
私はすべてが言葉なしで明確だと思います-見出しの最初の文字は「手書き」形式を使用し、最後の代替は使用します。 ヘッダーに適用できます:
ただし、見出しには最終文字sに代わるものがないため、より「指標的」なものを使用できます。
おわりに
上記のすべての使用例が私のブログにあります-私はこのサブシステムを使用してヘッダーを生成します。 そして、尋ねる前に-いいえ、このアプローチはインデックス作成に干渉しません(信じない場合は、「ソースを表示」して実装方法を確認してください)。Googleリーダーのような「リーダー」には問題はありません。 一方、私のブログでは、現在削除しているいくつかのシステムバグを確認できます。
上記で説明したのは、非常に遅いアプローチです。 GetPixel()
およびSetPixel()
関数は本当に悪であり、ビットマップを使用したすべての操作は、OpenMPまたはIntel TBBを使用してC ++で行う必要があります。 しかし、私の場合、画像は一度だけ生成する必要があります(そして、ブログエントリを追加した直後に生成します)ので、気にしません。 また、ビットマップからバイトを抽出し、P / Invokeで処理することは難しくありません。
注釈
- ↑
さらに、一部のシステムでは、サイトの開発者にフォントをアップロードする必要があります。
- ↑
このアルゴリズムは、ここから取得されます 。 唯一の違いは、乗算する代わりにシフトを回避するのに使いやすい係数を使用したことです。