SkiaSharp のパスとテキスト
パスとテキストの共通部分を調べる
最新のグラフィックス システムでは、テキスト フォントは文字のアウトラインの集合体であり、通常は 2 次ベジエ曲線で定義されます。 したがって、最新のグラフィックス システムの多くには、テキスト文字をグラフィックス パスに変換する機能が含まれています。
テキスト文字の輪郭にストロークを付けたり、塗りつぶしたりできることは既にご存知でしょう。 これにより、「パスの効果」の記事で説明されているように、特定のストローク幅とパス効果を使用して、これらの文字のアウトラインを表示できます。 ただし、文字列を SKPath
オブジェクトに変換することもできます。 つまり、テキストのアウトラインは、「パスおよび領域でクリッピング」の記事で説明されている手法を用いたクリッピングに使用できます。
パス効果を使用して文字のアウトラインにストロークを付けるだけでなく、文字列から派生したパスに基づいてパス効果を作成したり、2 つの効果を組み合わせたりすることもできます。
パス効果に関する前の記事では、SKPaint
の GetFillPath
メソッドでストロークを付けたパスのアウトラインを取得する方法について説明しました。 このメソッドは、文字のアウトラインから派生したパスと共に使用することもできます。
最後に、この記事では、パスとテキストの別の共通部分を示します: SKCanvas
の DrawTextOnPath
メソッドを使用すると、テキストのベースラインが曲線パスに従う用意テキスト文字列を表示できます。
テキストからパスへの変換
SKPaint
の GetTextPath
メソッドでは、文字列を SKPath
オブジェクトに変換します:
public SKPath GetTextPath (String text, Single x, Single y)
x
引数と y
引数は、テキストの左側のベースラインの開始点を示します。 これらの引数は、SKCanvas
の DrawText
メソッドと同じ役割を果たしています。 パス内では、テキストの左側のベースラインには座標 (x、y) が含まれます。
GetTextPath
メソッドは、結果のパスを塗りつぶしたりストロークを付けたりするだけの場合は過剰です。 通常の DrawText
メソッドでは、これを行うことができます。 GetTextPath
メソッドは、パスを含む他のタスクに役立ちます。
これらのタスクの 1 つがクリッピングです。 [テキストのクリッピング] ページでは、"CODE" という単語の文字のアウトラインに基づいてクリッピング パスが作成されます。このパスをページのサイズに伸縮して、[テキストのクリッピング] ソース コードのイメージを含むビットマップをクリッピングします。
ClippingTextPage
クラス コンストラクターは、ソリューションの [メディア] フォルダーに埋め込みリソースとして格納されているビットマップを読み込みます。
public class ClippingTextPage : ContentPage
{
SKBitmap bitmap;
public ClippingTextPage()
{
Title = "Clipping Text";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
string resourceID = "SkiaSharpFormsDemos.Media.PageOfCode.png";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
}
...
}
PaintSurface
ハンドラーは、まずテキストに適した SKPaint
オブジェクトを作成します。 Typeface
プロパティは TextSize
と同様に設定されますが、この特定のアプリケーションでは TextSize
プロパティは純粋に任意です。 また、Style
設定がないことにも注意してください。
この SKPaint
オブジェクトは、テキスト文字列 "CODE" を使用した GetTextPath
呼び出しのみに使用されるため、TextSize
および Style
プロパティ設定は必要ありません。 次に、ハンドラーは結果の SKPath
オブジェクトを測定し、3 つの変換を適用して中央揃えし、ページのサイズに合わせてスケーリングします。 その後、パスをクリッピング パスとして設定できます。
public class ClippingTextPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Blue);
using (SKPaint paint = new SKPaint())
{
paint.Typeface = SKTypeface.FromFamilyName(null, SKTypefaceStyle.Bold);
paint.TextSize = 10;
using (SKPath textPath = paint.GetTextPath("CODE", 0, 0))
{
// Set transform to center and enlarge clip path to window height
SKRect bounds;
textPath.GetTightBounds(out bounds);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(info.Width / bounds.Width, info.Height / bounds.Height);
canvas.Translate(-bounds.MidX, -bounds.MidY);
// Set the clip path
canvas.ClipPath(textPath);
}
}
// Reset transforms
canvas.ResetMatrix();
// Display bitmap to fill window but maintain aspect ratio
SKRect rect = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawBitmap(bitmap,
rect.AspectFill(new SKSize(bitmap.Width, bitmap.Height)));
}
}
クリッピング パスが設定されると、ビットマップを表示でき、文字のアウトラインにクリッピングされます。 縦横比を維持しながらページいっぱいに四角形を計算する SKRect
の AspectFill
メソッドの使用に注意してください。
[テキスト パス効果] ページでは、1 つのアンパサンド文字をパスに変換して、1D パス効果を作成します。 次に、このパス効果を持つペイント オブジェクトを使用して、同じ文字のより大きなバージョンのアウトラインにストロークを付けます。
TextPathEffectPath
クラスでの作業の多くは、フィールドとコンストラクターで発生します。 フィールドとして定義されている 2 つの SKPaint
オブジェクトは、2 つの異なる目的で使用されます。最初のオブジェクト (名前付きの textPathPaint
) は、50 のうち TextSize
を持つアンパサンドを 1D パス効果のパスに変換するために使用されます。 2 番目のオブジェクト (textPaint
) は、そのパス効果を持つアンパサンドの大きなバージョンを表示するために使用されます。 そのため、この 2 番目のペイント オブジェクト Style
は Stroke
に設定されていますが、1D パス効果を使用する場合、StrokeWidth
プロパティは必要ないため設定されません。
public class TextPathEffectPage : ContentPage
{
const string character = "@";
const float littleSize = 50;
SKPathEffect pathEffect;
SKPaint textPathPaint = new SKPaint
{
TextSize = littleSize
};
SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Black
};
public TextPathEffectPage()
{
Title = "Text Path Effect";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Get the bounds of textPathPaint
SKRect textPathPaintBounds = new SKRect();
textPathPaint.MeasureText(character, ref textPathPaintBounds);
// Create textPath centered around (0, 0)
SKPath textPath = textPathPaint.GetTextPath(character,
-textPathPaintBounds.MidX,
-textPathPaintBounds.MidY);
// Create the path effect
pathEffect = SKPathEffect.Create1DPath(textPath, littleSize, 0,
SKPath1DPathEffectStyle.Translate);
}
...
}
コンストラクターは、まず textPathPaint
オブジェクトを使用して、50 のうち TextSize
を持つアンパサンドを測定します。 その四角形の中心座標の負の値が GetTextPath
メソッドに渡され、テキストがパスに変換されます。 結果のパスは、文字の中心に点 (0, 0) を持ち、1D パス効果に最適です。
コンストラクターの最後に作成された SKPathEffect
オブジェクトは、フィールドとして保存されるのではなく、textPaint
の PathEffect
プロパティに設定できると考えるかもしれません。 しかし、これは PaintSurface
ハンドラーの MeasureText
呼び出しの結果をゆがめてしまうため、あまりうまく機能しないことが判明しました。
public class TextPathEffectPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Set textPaint TextSize based on screen size
textPaint.TextSize = Math.Min(info.Width, info.Height);
// Do not measure the text with PathEffect set!
SKRect textBounds = new SKRect();
textPaint.MeasureText(character, ref textBounds);
// Coordinates to center text on screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
// Set the PathEffect property and display text
textPaint.PathEffect = pathEffect;
canvas.DrawText(character, xText, yText, textPaint);
}
}
この MeasureText
呼び出しは、ページ上の文字を中央揃えするために使用されます。 問題を回避するために、テキストが測定された後、表示される前に PathEffect
プロパティが paint オブジェクトに設定されます。
文字のアウトラインの輪郭
通常、SKPaint
の GetFillPath
メソッドは、ペイント プロパティ (特にストロークの幅とパス効果) を適用することによって、1 つのパスを別のパスに変換します。 パス効果なしで使用すると、GetFillPath
は効果的に別のパスの輪郭を描くパスを作成します。 これは、パス効果の [タップしてパスの輪郭を描く] ページに示されています。
また、GetTextPath
から返されたパスで GetFillPath
を呼び出すこともできますが、最初は、それがどのようなものか完全にはわからない場合があります。
[文字のアウトラインの輪郭] ページでは、この手法が示されています。 関連するすべてのコードは、CharacterOutlineOutlinesPage
クラスの PaintSurface
ハンドラーにあります。
コンストラクターは、まず、ページのサイズに基づく TextSize
プロパティを持つ textPaint
という名前の SKPaint
オブジェクトを作成します。 これは、GetTextPath
メソッドを使用してパスに変換されます。 GetTextPath
への座標引数は、パスを効果的に画面上で中央揃えします。
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint textPaint = new SKPaint())
{
// Set Style for the character outlines
textPaint.Style = SKPaintStyle.Stroke;
// Set TextSize based on screen size
textPaint.TextSize = Math.Min(info.Width, info.Height);
// Measure the text
SKRect textBounds = new SKRect();
textPaint.MeasureText("@", ref textBounds);
// Coordinates to center text on screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
// Get the path for the character outlines
using (SKPath textPath = textPaint.GetTextPath("@", xText, yText))
{
// Create a new path for the outlines of the path
using (SKPath outlinePath = new SKPath())
{
// Convert the path to the outlines of the stroked path
textPaint.StrokeWidth = 25;
textPaint.GetFillPath(textPath, outlinePath);
// Stroke that new path
using (SKPaint outlinePaint = new SKPaint())
{
outlinePaint.Style = SKPaintStyle.Stroke;
outlinePaint.StrokeWidth = 5;
outlinePaint.Color = SKColors.Red;
canvas.DrawPath(outlinePath, outlinePaint);
}
}
}
}
}
次に、PaintSurface
ハンドラーは、outlinePath
という名前の新しいパスを作成します。 これが GetFillPath
への呼び出しの宛先パスになります。 25 の StrokeWidth
プロパティは、テキスト文字にストロークを付ける 25 ピクセル幅のパスの輪郭を outlinePath
に記述します。 その後、このパスのストロークの幅は 5 になり、赤で表示されます。
よく見ると、パスの輪郭が鋭角になる場所で重複しているのを確認できます。 これらは、このプロセスの通常の成果物です。
パスに沿ったテキスト
テキストは通常、水平なベースライン上に表示されます。 テキストは垂直方向または斜めに回転させることができますが、ベースラインは直線のままです。
ただし、テキストを曲線に沿って実行する必要がある場合があります。 SKCanvas
の DrawTextOnPath
メソッドの目的を次に示します。
public Void DrawTextOnPath (String text, SKPath path, Single hOffset, Single vOffset, SKPaint paint)
最初の引数で指定されたテキストは、2 番目の引数として指定されたパスに沿って実行されます。 hOffset
引数を使用して、パスの先頭からオフセットした位置でテキストを開始できます。 通常、パスはテキストのベースラインを形成します。テキスト アセンダーはパスの片側にあり、テキスト ディセンダーはもう一方の側にあります。 ただし、vOffset
引数を使用して、パスからテキストベースラインをオフセットすることができます。
このメソッドには、SKPaint
の TextSize
プロパティを設定することで、パスの先頭から末尾までテキストのサイズが完全に合うようにするためのガイダンスを提供する機能はありません。 場合によっては、そのテキスト サイズを自分で確認できます。 「パス情報と列挙」に関する次の記事で説明するパス測定関数を使用する必要がある場合もあります。
囲い文字プログラムは、テキストを円で囲います。 円周は簡単に決められるため、テキストのサイズを正確に一致させるのは簡単です。 CircularTextPage
クラスの PaintSurface
ハンドラーは、ページのサイズに基づいて円の半径を計算します。 その円は次の circularPath
のようになります。
public class CircularTextPage : ContentPage
{
const string text = "xt in a circle that shapes the te";
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath circularPath = new SKPath())
{
float radius = 0.35f * Math.Min(info.Width, info.Height);
circularPath.AddCircle(info.Width / 2, info.Height / 2, radius);
using (SKPaint textPaint = new SKPaint())
{
textPaint.TextSize = 100;
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 2 * 3.14f * radius / textWidth;
canvas.DrawTextOnPath(text, circularPath, 0, 0, textPaint);
}
}
}
}
その後、textPaint
の TextSize
プロパティは、テキストの幅が円周と一致するように調整されます。
テキスト自体もやや円形になるように選択されました。"circle" という単語は、文の主語であると同時に前置詞句の目的語でもあります。