WPFのXAMLでベジェ曲線とCatmull-Romスプライン曲線

コンピュータ

XAMLはベクター図形を描くことが出来るので、今回は曲線を引いて見たいと思います。

ベジェ曲線のサンプル

MainWindow.xaml(XAMLのみ)

<Window x:Class="BezierDemo1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BezierDemo1"
        mc:Ignorable="d"
        Title="Bezier Demo" Width="800" Height="500">
    <Grid Background="#1E1E1E">
        <Canvas>
            <!-- PathGeometryでCubic Bezier (BezierSegment) -->
            <Path Stroke="DeepSkyBlue" StrokeThickness="4" StrokeLineJoin="Round">
                <Path.Data>
                    <PathGeometry>
                        <PathFigure StartPoint="80,300" IsClosed="False" IsFilled="False">
                            <BezierSegment Point1="200,80"
                                           Point2="420,520"
                                           Point3="700,250" />
                        </PathFigure>
                    </PathGeometry>
                </Path.Data>
            </Path>
        </Canvas>
    </Grid>
</Window>

実行例スクリーンショット

Point1

Point1="200,80"

Point1="0,80"

へ変更します。
予想ではx軸の左側に移動するはず。

Point2

Point2="420,520"

Point2="420,760"

へ変更します。
予想ではy軸の下側に移動するはず。

変更後スクリーンショット

予想通りの結果になりました。

Catmull-Romスプライン曲線

MainWindow.xaml(Canvasだけ)

<Window x:Class="CatmullRomDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CatmullRomDemo"
        mc:Ignorable="d"
        Title="Catmull-Rom → Bezier (Minimal)"
        Width="800" Height="500">
    <Canvas Name="CanvasRoot" Background="#1E1E1E"/>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace CatmullRomDemo;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += (_, __) => Draw();
    }

    private void Draw()
    {
        // === Catmull-Rom 用の固定点列 ===
        var points = new List<Point>
        {
            new Point(100, 300),
            new Point(200, 120),
            new Point(350, 260),
            new Point(520, 100),
        };

        // 端点対策(複製)
        points.Insert(0, points[0]);
        points.Add(points[^1]);

        // Catmull-Rom → Bezier
        PathGeometry geometry = ConvertCatmullRomToBezier(points);

        // 描画
        var path = new Path
        {
            Data = geometry,
            Stroke = Brushes.DeepSkyBlue,
            StrokeThickness = 4,
            StrokeLineJoin = PenLineJoin.Round,
            StrokeStartLineCap = PenLineCap.Round,
            StrokeEndLineCap = PenLineCap.Round
        };

        CanvasRoot.Children.Add(path);
    }

    // ================================
    // Catmull-Rom → Cubic Bezier 変換
    // ================================
    private static PathGeometry ConvertCatmullRomToBezier(IList<Point> pts)
    {
        if (pts.Count < 4)
            throw new ArgumentException("Need at least 4 points");

        var figure = new PathFigure
        {
            StartPoint = pts[1],
            IsClosed = false,
            IsFilled = false
        };

        for (int i = 0; i < pts.Count - 3; i++)
        {
            Point p0 = pts[i];
            Point p1 = pts[i + 1];
            Point p2 = pts[i + 2];
            Point p3 = pts[i + 3];

            // Catmull-Rom (tension = 0.5) → Bezier
            Point c1 = p1 + (p2 - p0) / 6.0;
            Point c2 = p2 - (p3 - p1) / 6.0;

            figure.Segments.Add(
                new BezierSegment(c1, c2, p2, true)
            );
        }

        return new PathGeometry(new[] { figure });
    }
}

実行例、スクリーンショット

var points

にポイントを足してみます。

new Point(650, 300),

変更後、スクリーンショット

曲線が伸びたことが確認出来ます。

感想

ベジェ曲線、Catmull-Romスプライン曲線のいずれも理屈は全く理解していないのですが、

座標をノードとして曲線を描くことが出来ることを確認しましたので、

プログラムに組み込む目処が立ちました。

 

マウスのクリックイベントで、座標を取得し曲線を描くプログラムが作れると思います。

ただ、UIとして作り込む場合、ベジェ曲線は、ハンドル等を実装する必要があるので、
比較的簡単そうなCatmull-Romスプライン曲線で実装したいと思います。

コメント