Ruby と rcairo でベジェ曲線を描いてみる

ベジェ曲線Wikipedia)は滑らかな曲線を描くために使われるものです。いくつかの「制御点」を指定して描きます。計算はそんなにむずかしくなくて、上の Wikipedia の記事で充分わかりますし、ネット上にわかりやすい記事がたくさんあるので検索してみて下さい。

Ruby と 'rcairo' で描いてみた例です。
20190320195050
青い線が制御点を結んだ折れ線で、赤い曲線がベジェ曲線です。

Ruby コード。
bezier_curve.rb

require 'cairo'
require 'matrix'

class BezierCurve
  def initialize(points, step = 0.01)
    @points = points.map {|a| a.class == Array ? Vector[*a] : a}
    @step = step
  end
  
  def calc
    n = @points.size - 1
    Enumerator.new do |y|
      c = [1] + (1..n).map {|k| (n - k + 1..n).inject(&:*) / (1..k).inject(&:*)}
      0.0.step(1.0, @step) do |t|
        j = ->(i) { c[i] * t ** i * (1 - t) ** (n - i) }
        y << @points.map.with_index {|b, i| b * j.(i)}.inject(&:+)
      end
      y << @points.last
    end
  end
end


if __FILE__ == $0
  #画像の大きさ
  W = 300
  
  #cairoの初期設定
  surface = Cairo::ImageSurface.new(W, W)
  context = Cairo::Context.new(surface)
  
  #背景
  context.set_source_color(Cairo::Color.parse("#F1F389"))
  context.rectangle(0, 0, W, W)
  context.fill
  
  #制御点を与える
  points = [[20.0, 280.0], [60.0, 100.0], [200.0, 120.0], [290.0, 230.0]]
  
  #制御点を結ぶ青い線
  context.set_source_color(Cairo::Color::BLUE)
  context.set_line_width(2)
  context.move_to(*points.first)
  points.drop(1).each {|r| context.line_to(*r)}
  context.stroke
  
  #ベジェ曲線(赤色)の描画
  context.set_source_color(Cairo::Color::RED)
  context.move_to(*points.first)
  
  BezierCurve.new(points).calc.each do |r|
    context.line_to(*r.to_a)
  end
  context.stroke
  
  #png画像として出力
  surface.write_to_png("bezier_curve.png")
end

使い方としては、配列 points に制御点を入れて(制御点は配列あるいは Vector クラスで表現します)、BezierCurve.new(points, step).calc で折れ線(step を細かくすればベジェ曲線に見えるわけです)の頂点(Vector クラスで表現されています)を順に与える Enumerator を返します。step は省略されれば 0.01 がデフォルトになります。曲線は t = 0 が開始で t = 1 が終了なので、step は一回に進む t の値を指定します。step = 0.01 ならば曲線が 100分割されるということです。


なお、なめらかな曲線としては「スプライン曲線」というのもあります。下の記事で扱っています。
obelisk.hatenablog.com