次の方法で共有


SkiaSharp のパスとテキスト

パスとテキストの共通部分を調べる

最新のグラフィックス システムでは、テキスト フォントは文字のアウトラインの集合体であり、通常は 2 次ベジエ曲線で定義されます。 したがって、最新のグラフィックス システムの多くには、テキスト文字をグラフィックス パスに変換する機能が含まれています。

テキスト文字の輪郭にストロークを付けたり、塗りつぶしたりできることは既にご存知でしょう。 これにより、「パスの効果」の記事で説明されているように、特定のストローク幅とパス効果を使用して、これらの文字のアウトラインを表示できます。 ただし、文字列を SKPath オブジェクトに変換することもできます。 つまり、テキストのアウトラインは、「パスおよび領域でクリッピング」の記事で説明されている手法を用いたクリッピングに使用できます。

パス効果を使用して文字のアウトラインにストロークを付けるだけでなく、文字列から派生したパスに基づいてパス効果を作成したり、2 つの効果を組み合わせたりすることもできます。

テキスト パス効果

パス効果に関する前の記事では、SKPaintGetFillPath メソッドでストロークを付けたパスのアウトラインを取得する方法について説明しました。 このメソッドは、文字のアウトラインから派生したパスと共に使用することもできます。

最後に、この記事では、パスとテキストの別の共通部分を示します: SKCanvasDrawTextOnPath メソッドを使用すると、テキストのベースラインが曲線パスに従う用意テキスト文字列を表示できます。

テキストからパスへの変換

SKPaintGetTextPath メソッドでは、文字列を SKPath オブジェクトに変換します:

public SKPath GetTextPath (String text, Single x, Single y)

x 引数と y 引数は、テキストの左側のベースラインの開始点を示します。 これらの引数は、SKCanvasDrawText メソッドと同じ役割を果たしています。 パス内では、テキストの左側のベースラインには座標 (x、y) が含まれます。

GetTextPath メソッドは、結果のパスを塗りつぶしたりストロークを付けたりするだけの場合は過剰です。 通常の DrawText メソッドでは、これを行うことができます。 GetTextPath メソッドは、パスを含む他のタスクに役立ちます。

これらのタスクの 1 つがクリッピングです。 [テキストのクリッピング] ページでは、"CODE" という単語の文字のアウトラインに基づいてクリッピング パスが作成されます。このパスをページのサイズに伸縮して、[テキストのクリッピング] ソース コードのイメージを含むビットマップをクリッピングします。

[Clipping Text] ページのトリプル スクリーンショット

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)));
    }
}

クリッピング パスが設定されると、ビットマップを表示でき、文字のアウトラインにクリッピングされます。 縦横比を維持しながらページいっぱいに四角形を計算する SKRectAspectFill メソッドの使用に注意してください。

[テキスト パス効果] ページでは、1 つのアンパサンド文字をパスに変換して、1D パス効果を作成します。 次に、このパス効果を持つペイント オブジェクトを使用して、同じ文字のより大きなバージョンのアウトラインにストロークを付けます。

[Text Path Effect] ページのトリプル スクリーンショット

TextPathEffectPath クラスでの作業の多くは、フィールドとコンストラクターで発生します。 フィールドとして定義されている 2 つの SKPaint オブジェクトは、2 つの異なる目的で使用されます。最初のオブジェクト (名前付きの textPathPaint) は、50 のうち TextSize を持つアンパサンドを 1D パス効果のパスに変換するために使用されます。 2 番目のオブジェクト (textPaint) は、そのパス効果を持つアンパサンドの大きなバージョンを表示するために使用されます。 そのため、この 2 番目のペイント オブジェクト StyleStroke に設定されていますが、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 オブジェクトは、フィールドとして保存されるのではなく、textPaintPathEffect プロパティに設定できると考えるかもしれません。 しかし、これは 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 オブジェクトに設定されます。

文字のアウトラインの輪郭

通常、SKPaintGetFillPath メソッドは、ペイント プロパティ (特にストロークの幅とパス効果) を適用することによって、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 になり、赤で表示されます。

[Character Outline Outlines] ページのトリプル スクリーンショット

よく見ると、パスの輪郭が鋭角になる場所で重複しているのを確認できます。 これらは、このプロセスの通常の成果物です。

パスに沿ったテキスト

テキストは通常、水平なベースライン上に表示されます。 テキストは垂直方向または斜めに回転させることができますが、ベースラインは直線のままです。

ただし、テキストを曲線に沿って実行する必要がある場合があります。 SKCanvasDrawTextOnPath メソッドの目的を次に示します。

public Void DrawTextOnPath (String text, SKPath path, Single hOffset, Single vOffset, SKPaint paint)

最初の引数で指定されたテキストは、2 番目の引数として指定されたパスに沿って実行されます。 hOffset 引数を使用して、パスの先頭からオフセットした位置でテキストを開始できます。 通常、パスはテキストのベースラインを形成します。テキスト アセンダーはパスの片側にあり、テキスト ディセンダーはもう一方の側にあります。 ただし、vOffset 引数を使用して、パスからテキストベースラインをオフセットすることができます。

このメソッドには、SKPaintTextSize プロパティを設定することで、パスの先頭から末尾までテキストのサイズが完全に合うようにするためのガイダンスを提供する機能はありません。 場合によっては、そのテキスト サイズを自分で確認できます。 「パス情報と列挙」に関する次の記事で説明するパス測定関数を使用する必要がある場合もあります。

囲い文字プログラムは、テキストを円で囲います。 円周は簡単に決められるため、テキストのサイズを正確に一致させるのは簡単です。 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);
            }
        }
    }
}

その後、textPaintTextSize プロパティは、テキストの幅が円周と一致するように調整されます。

[Circular Text] ページのトリプル スクリーンショット

テキスト自体もやや円形になるように選択されました。"circle" という単語は、文の主語であると同時に前置詞句の目的語でもあります。