ベクタグラフィックスその7

今まで放置していた色をつける機能を追加する。NanoVGではnvgStrokeColorでベタ塗りの色を指定して、nvgStrokePaintでグラデーションを設定できる。どちらも内部的にはNVGstate構造体のstrokeメンバにNVGpaint構造体を格納しているだけである。よってベタ塗りとグラデーションは関数こそ違うが設定は後勝ちとなる。NanoVGではベタ塗りも3種類あるグラデーションもすべて一つのNVGpaint構造体で表現できるようになっていて、この中身のパラメータをシェーダでごにょごにょ計算して色を生成する。
OreVGではラスタライザもRubyで書いているのでこのいかにも重そうな計算をピクセルごとにやるのはちょっと精神衛生上よろしくない。それぞれ分けることにする。

ラスタライザ

とりあえずOreVGクラスにインスタンス変数@paintを追加して、ベタ塗り用もしくはグラデーション用のオブジェクトを格納することにする。このオブジェクトはcalc_colorメソッドを持っていて、座標を渡すと色が返ってくるように作る。なのでラスタライザのtriangleは以下のようになる。

  private def triangle(x1, y1, x2, y2, x3, y3)
    rasterize(x1, y1, x2, y2, x3, y3) do |x, y|
      @image[x,y] = @paint.calc_color(x, y)
    end
    @triangles << [x1, y1, x2, y2, x3, y3]
  end

ベタ塗り

ベタ塗りの場合はstroke_colorメソッドで色を指定する。渡す値はDXRuby用の色配列ということにしておく。

  def stroke_color=(color)
    @paint = FillColor.new(color)
  end

FillColorクラスは渡された色を保持してcalc_colorで無条件に返すだけになる。

  class FillColor
    def initialize(col)
      @col = col
    end

    def calc_color(x, y)
      @col
    end
  end

簡単である。

線形グラデーション

線形グラデーションをするためにNanoVGと同様にlinear_gradientメソッドを作ってオブジェクトを返し、stroke_paintメソッドで設定するようにする。

  def linear_gradient(x1, y1, x2, y2, incol, outcol)
    LinearGradient.new(x1, y1, x2, y2, incol, outcol)
  end

  def stroke_paint(paint)
    @paint = paint
  end

このincolは(x1,y1)地点での色、outcolは(x2,y2)地点での色で、この間が中間色になる。この範囲外はそれぞれincol、outcol固定。これが水平、垂直だけなら簡単なのだが斜めにもグラデーションできてしまう仕様なのでそこだけちょっと考える必要がある。

  # 線形グラデーション
  class LinearGradient
    def initialize(x1, y1, x2, y2, incol, outcol)
      v = Vector.new(x2, y2) - Vector.new(x1, y1)
      @x1 = x1
      @y1 = y1
      @len = v.len
      @incol = incol
      @outcol = outcol
      @dx, @dy = v.normalize
    end

    def calc_color(x, y)
      # 渡された座標を0〜1にマッピング
      t = ((x - @x1) * @dx + (y - @y1) * @dy) / @len

      if t <= 0
        @incol # 0以下の色
      elsif t >= 1
        @outcol # 1以上の色
      else # 中間色算出
        @incol.zip(@outcol).map do |ary|
          ary[0] * (1-t) + ary[1] * t
        end
      end
    end
  end

ちなみにまだ作っていない機能に座標のアフィン変換があって、これで座標を変形するとグラデーションの座標も同様に変形しないといけないのだが、それは当然まだ入っていない。変形するタイミングはstroke_paintなのでそこの中を変更するだけになる。

結果

このように線形グラデーションができるようになる。あと円状のグラデーションと箱状のグラデーションがあるが、クラス追加するだけなのでたいして難しくないはず。

今回のコードはこちら