ベクタグラフィックスその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なのでそこの中を変更するだけになる。
結果
このように線形グラデーションができるようになる。あと円状のグラデーションと箱状のグラデーションがあるが、クラス追加するだけなのでたいして難しくないはず。
今回のコードはこちら。