ベジェ曲線 (3次 cubic-bezier) の座標を計算する

ベジェ曲線 (3次 cubic-bezier) の座標を計算するコードを紹介します。

概要

こちらの記事ではベジェ曲線を描画するコードを紹介しました。 キャンバスにベジェ曲線を描画するだけであれば、紹介したコードで実装できますが、ベジエ曲線の座標値を取得したい場合は、 ベジェ曲線の座標値を計算により求める必要があります。この記事では、ベジェ曲線の座標を計算するコードを紹介します。

計算式

3次のベジェ曲線の計算式は次の通りです。


x,yのそれぞれの座標値は次の計算式になります。


  • 始点 (p1x,p1y)
  • コントロールポイント1 (p2x,p2y)
  • コントロールポイント2 (p3x,p3y)
  • 終点 (p4x,p4y)
このとき
x = (1-t)*(1-t)*(1-t)*p1x + 3*(1-t)*(1-t)*t*p2x + 3*(1-t)*t*t*p3x + t*t*t*p4x

y = (1-t)*(1-t)*(1-t) * p1x + 3*(1-t)*(1-t)*t*p2y + 3*(1-t)*t*t*p3y + t*t*t*p4y
となり、tの値を0から1まで変化させたときのx,yの値がベジェ曲線の座標値になります。

プログラム

UI

下図のフォームを作成します。

コード

下記コードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleDrawCubicBezier
{
  public partial class FormManualDrawCubicBezier : Form
  {
    Point point1;
    Point point2;
    Point point3;
    Point point4;
    Pen bPen;
    Brush backBrush;

    public FormManualDrawCubicBezier()
    {
      InitializeComponent();
      bPen = new Pen(Color.Blue, 2);
      backBrush = new SolidBrush(Color.White);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      int cp1x = Convert.ToInt32(textBox1.Text);
      int cp1y = Convert.ToInt32(textBox2.Text);
      int cp2x = Convert.ToInt32(textBox3.Text);
      int cp2y = Convert.ToInt32(textBox4.Text);

      point1 = new Point(0, 320);
      point2 = new Point(cp1x, cp1y);
      point3 = new Point(cp2x, cp2y);
      point4 = new Point(320, 0);

      panel1.Refresh();
    }

    private void panel1_Paint(object sender, PaintEventArgs e)
    {
      e.Graphics.FillRectangle(backBrush, new Rectangle(0, 0, 320, 320));

      double px = point1.X;
      double py = point1.Y;

      for (double t = 0; t <= 1.0; t = t + 0.01)
      {
        double x = Math.Pow(1 - t, 3) * point1.X + 3 * Math.Pow(1 - t, 2) * t * point2.X + 3 * (1 - t) * Math.Pow(t, 2) * point3.X + Math.Pow(t, 3) * point4.X;
        double y = Math.Pow(1 - t, 3) * point1.Y + 3 * Math.Pow(1 - t, 2) * t * point2.Y + 3 * (1 - t) * Math.Pow(t, 2) * point3.Y + Math.Pow(t, 3) * point4.Y;

        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        e.Graphics.DrawLine(bPen, (float)px, (float)py, (float)x, (float)y);

        px = x;
        py = y;
      }
    }
  }
}

解説

基本的な処理はこちらの記事と同様です。
Panelコントロールの描画部分でベジェ曲線の座標を計算により求めています。
tの値を0から1まで変化させ、x,y座標の値を求めています。画面への描画は1つ手前のx,y座標の値と、求めたx,y座標とを結ぶ直線を描画しています。
  double px = point1.X;
  double py = point1.Y;

  for (double t = 0; t <= 1.0; t = t + 0.01)
  {
    double x = Math.Pow(1 - t, 3) * point1.X + 3 * Math.Pow(1 - t, 2) * t * point2.X + 3 * (1 - t) * Math.Pow(t, 2) * point3.X + Math.Pow(t, 3) * point4.X;
    double y = Math.Pow(1 - t, 3) * point1.Y + 3 * Math.Pow(1 - t, 2) * t * point2.Y + 3 * (1 - t) * Math.Pow(t, 2) * point3.Y + Math.Pow(t, 3) * point4.Y;

    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    e.Graphics.DrawLine(bPen, (float)px, (float)py, (float)x, (float)y);

    px = x;
    py = y;
  }

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[Draw]ボタンをクリックします。ベジェ曲線がパネルに描画されます。


コントロールポイントのテキストボックスの値を変更して、再度[Draw]ボタンをクリックします。曲線の形状が変化することが確認できます。


ベジェ曲線の座標を計算して、画面に描画できました。

プログラム例2

コントロールポイントの位置や、ベジェ曲線の値が枠をはみ出した場合でも描画できるよう周囲にマージンを追加します。 また、コントロールポイントの値を枠の幅、高さで正規化します。

UI

下図のフォームを作成します。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleDrawCubicBezier
{
  public partial class FormManualDrawCubicBezierEx : Form
  {
    Point point1;
    Point point2;
    Point point3;
    Point point4;
    Pen bPen;
    Pen fPen;
    Pen cPen;
    Brush backBrush;
    Brush controlpointBrush;

    int offsetX, offsetY;
    int areaWidth, areaHeight;
    int controlpointRaduis;

    private void button1_Click(object sender, EventArgs e)
    {
      double cp1x = Convert.ToDouble(textBox1.Text);
      double cp1y = Convert.ToDouble(textBox2.Text);
      double cp2x = Convert.ToDouble(textBox3.Text);
      double cp2y = Convert.ToDouble(textBox4.Text);

      point1 = new Point(offsetX, offsetY + areaHeight);
      point2 = new Point(offsetX + (int)(areaWidth * cp1x), offsetY + (int)(areaHeight * cp1y));
      point3 = new Point(offsetX + (int)(areaWidth * cp2x), offsetY + (int)(areaHeight * cp2y));
      point4 = new Point(offsetX + areaWidth, offsetY);

      panel1.Refresh();
    }

    public FormManualDrawCubicBezierEx()
    {
      InitializeComponent();

      offsetX = 64;
      offsetY = 64;
      areaWidth = 320;
      areaHeight = 320;
      controlpointRaduis = 8;

      bPen = new Pen(Color.Blue, 2);
      fPen = new Pen(Color.Black, 1);
      cPen = new Pen(Color.FromArgb(105, 161, 255), 1);
      backBrush = new SolidBrush(Color.White);
      controlpointBrush = new SolidBrush(Color.FromArgb(105, 161, 255));
    }

    private void panel1_Paint(object sender, PaintEventArgs e)
    {
      e.Graphics.FillRectangle(backBrush, new Rectangle(0, 0, panel1.Width, panel1.Height));

      e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

      e.Graphics.DrawRectangle(fPen, new Rectangle(offsetX, offsetY, areaWidth, areaHeight));

      e.Graphics.DrawLine(cPen, point1, point2);
      e.Graphics.DrawLine(cPen, point4, point3);

      e.Graphics.FillEllipse(controlpointBrush, new Rectangle(point2.X - controlpointRaduis, point2.Y - controlpointRaduis, controlpointRaduis * 2, controlpointRaduis * 2));
      e.Graphics.FillEllipse(controlpointBrush, new Rectangle(point3.X - controlpointRaduis, point3.Y - controlpointRaduis, controlpointRaduis * 2, controlpointRaduis * 2));

      double px = point1.X;
      double py = point1.Y;

      for (double t = 0; t <= 1.0; t = t + 0.01)
      {
        double x = Math.Pow(1 - t, 3) * point1.X + 3 * Math.Pow(1 - t, 2) * t * point2.X + 3 * (1 - t) * Math.Pow(t, 2) * point3.X + Math.Pow(t, 3) * point4.X;
        double y = Math.Pow(1 - t, 3) * point1.Y + 3 * Math.Pow(1 - t, 2) * t * point2.Y + 3 * (1 - t) * Math.Pow(t, 2) * point3.Y + Math.Pow(t, 3) * point4.Y;

        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        e.Graphics.DrawLine(bPen, (float)px, (float)py, (float)x, (float)y);

        px = x;
        py = y;
      }
    }
  }
}

解説

処理はこちらの記事と同様です。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[Draw]ボタンをクリックします。ベジェ曲線とコントロールポイントが表示されます。


コントロールポイントの値を変更します。ベジェ曲線が枠をはみ出る場合でも描画できます。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2022-02-13
iPentec all rights reserverd.