mieki256's diary



2014/05/29(木) [n年前の日記]

#1 [dxruby][cg_tools] DXRubyでswfのシェイプを描画する実験

とりあえず、今現在動いてるところまでメモしておこうかなと。

二次ベジェ曲線。 :

まずは、二次ベジェ曲線の描画を実験。
二次ベジェ曲線の描画テスト

_bezierdraw.rb
require 'dxruby'

# 二次ベジェ曲線を描画するクラス
class BezierDraw
  DBG = true

  # 二次ベジェ曲線を描画するための頂点リストを生成
  #
  # @param [Number] x1 始点 x 座標
  # @param [Number] y1 始点 y 座標
  # @param [Number] x2 制御点 x 座標
  # @param [Number] y2 制御点 y 座標
  # @param [Number] x3 終点 x 座標
  # @param [Number] y3 終点 y 座標
  # @return [Array] ベジェ曲線を描画するための頂点座標配列
  #
  def BezierDraw.calc(x1, y1, x2, y2, x3, y3)
    p = []
    p.push([x1, y1])
    t = 0.05
    while t < 1.0
      v = 1.0 - t
      a = v * v
      b = 2 * v * t
      c = t * t
      x = a * x1 + b * x2 + c * x3
      y = a * y1 + b * y2 + c * y3
      p.push([x, y])
      t += 0.05
    end
    p.push([x3, y3])
    return p
  end

  # ベジェ曲線を描画
  #
  # @param [Object] img Imageオブジェクト
  # @param [Array] p 頂点座標配列
  # @param [Array] lcol 色配列。A,R,G,B が入ってる。
  #
  def BezierDraw.draw(img, p, lcol)
    (p.size - 1).times do |i|
      j = i + 1
      img.line(p[i][0], p[i][1], p[j][0], p[j][1], lcol)
    end
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  img = Image.new(640, 480)
  p = [[30, 240], [300, 10], [600, 400]]
  lcol = [255, 255, 0, 0]

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    p[1] = Input.mousePosX, Input.mousePosY

    x0, y0 = p[0]
    for i in 1..2 do
      x1, y1 = p[i]
      Window.drawLine(x0, y0, x1, y1, [128, 128, 128])
      x0, y0 = x1, y1
    end

    img.clear
    x1, y1 = p[0]
    x2, y2 = p[1]
    x3, y3 = p[2]
    np = BezierDraw.calc(x1, y1, x2, y2, x3, y3)
    BezierDraw.draw(img, np, lcol)
    Window.draw(0, 0, img)
  end
end
マウスカーソルを動かすと制御点を移動できます。

凹多角形の塗りつぶし。 :

凹多角形の塗りつぶし

_polyfill.rb
require 'dxruby'

# 与えられた多角形で塗りつぶしをするクラス
#
# @note 以下のページを参考にしました。
#       多角形塗りつぶし(1)
#       http://azskyetc.client.jp/paintprog/031_polygon1.html
#
class PolyFill

  # 塗り潰しをする
  #
  # @param [Object] img Imageオブジェクト
  # @param [Array] np 頂点座標配列 ((x,y), (x,y), ...)の形で入っている。
  # @param [Array] f0col 塗り潰しに使う色配列
  # @param [Array] f1col 塗り潰しに使う色配列
  # @param [Boolean] clip_enable trueなら画面サイズを超えた部分の描画をしない
  #
  def PolyFill.fill(img, np, f0col, f1col, clip_enable = true)

    return if np.size < 3

    # y座標の最小値と最大値を求める
    ymin = ymax = np[0][1]
    np.each do |p|
      ymin = p[1] if ymin > p[1]
      ymax = p[1] if ymax < p[1]
    end

    if clip_enable
      ymin = 0 if ymin < 0
      ymax = Window.height if ymax > Window.height
    end

    if clip_enable
      # 1ラインずつスキャンしていく
      ymin.step(ymax, 1.0) do |y|
        xlst = PolyFill.get_intersection_point(y.to_f, np) # 交点のx座標を求める
        next if xlst.empty? # 交点が無い
        next if xlst.size % 2 != 0 # 交点数が奇数
        i = 0
        while i < xlst.size
          x0, x1 = xlst[i], xlst[i+1]
          unless x1 < 0 or x0 > Window.width
            img.line(x0, y, x1, y, f0col)
          end
          i += 2
        end
      end
    else
      ymin.step(ymax, 1.0) do |y|
        xlst = PolyFill.get_intersection_point(y.to_f, np)
        next if xlst.empty? or xlst.size % 2 != 0
        i = 0
        while i < xlst.size
          img.line(xlst[i], y, xlst[i+1], y, f0col)
          i += 2
        end
      end
    end
  end

  # 与えられた頂点座標配列を元にして交点リストを求める
  #
  # @param [Number] y スキャンするy座標
  # @param [Array] lst 頂点座標配列
  # @return [Array] 交点のx座標を配列で返す
  #
  def PolyFill.get_intersection_point(y, lst)
    xlst = []
    len = lst.size
    len.times do |i|
      x1, y1 = lst[i]
      x2, y2 = lst[(i + 1) % len]

      next if y1 == y2 # 水平線なら処理しない

      # yが、現在の辺の終点と、次辺の始点であり、
      # 両辺が上方向、または下方向の場合は交点から除外
      if y == y2
        y3 = lst[(i + 2) % len][1]
        next if (y2 - y1 < 0 and y3 - y2 < 0)
        next if (y2 - y1 > 0 and y3 - y2 > 0)
      end

      # yが下方向になるように入れ替え
      if y1 > y2
        x1, x2 = x2, x1
        y1, y2 = y2, y1
      end

      if y1 <= y and y <= y2
        # 交点を追加
        x = ((y - y1) * (x2 - x1)).to_f / (y2 - y1) + x1
        xlst.push(x)
      end
    end

    # 交点を小さい順にソートして返す
    return xlst.sort
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  img = Image.new(640, 480)
  lst = [
    [200, 30],
    [300, 130],
    [350, 230],
    [300, 330],
    [200, 230],
    [50, 330],
    [100, 130],
    [200, 30]
  ]
  f0col = [255, 0, 255, 0]
  f1col = [255, 255, 0, 0]

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)
    lst[4] = Input.mousePosX, Input.mousePosY
    img.clear
    PolyFill.fill(img, lst, f0col, f1col, false)
    Window.draw(0, 0, img)
  end
end
マウスカーソルを動かすと1点だけ移動できます。

_多角形塗りつぶし(1) をそのまま DXRuby で動くように書き直しただけですが…。

アンチエイリアス付きの直線描画。 :

_Xiaolin Wu's line algorithm - Wikipedia, the free encyclopedia を手元でも実験。

_Wu Anti-aliased Lines
_Xiaolin Wu's line algorithm - Rosetta Code
_Antialiasing: Wu Algorithm - CodeProject

アンチエイリアス付きの直線描画

ちなみに、アンチエイリアス無し(DXRuby の Image#line)で描画すると以下のような感じに。
アンチエイリアス無しの直線描画

_xiaolinwu.rb
# Xiaolin Wu's line algorithm - Rosetta Code
# http://rosettacode.org/wiki/Xiaolin%5FWu%27s%5Fline%5Falgorithm

require 'dxruby'

class XiaolinWuDraw

  def XiaolinWuDraw.draw_line(img, x1, y1, x2, y2, colour)
    if x1 == x2 and y1 == y2
      # not line. this is a dot.
      img[x1, y1] = colour
      return
    end

    dx, dy = x2 - x1, y2 - y1
    steep = (dy.abs > dx.abs)? true : false
    x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx if steep
    x1, y1, x2, y2 = x2, y2, x1, y1 if x1 > x2
    gradient = dy.to_f / dx.to_f

    # handle the first endpoint
    xend = x1.round
    yend = y1 + gradient * (xend - x1)
    fp = (x1 + 0.5) - (x1 + 0.5).truncate
    rfp = 1.0 - fp
    xgap = rfp
    xpxl1 = xend
    ypxl1 = yend.truncate
    fp = yend - yend.truncate
    rfp = 1.0 - fp
    XiaolinWuDraw.put_colour(img, xpxl1, ypxl1,     colour, steep, rfp * xgap)
    XiaolinWuDraw.put_colour(img, xpxl1, ypxl1 + 1, colour, steep,  fp * xgap)
    itery = yend + gradient

    # handle the second endpoint
    xend = x2.round
    yend = y2 + gradient * (xend - x2)
    fp = (x2 + 0.5) - (x2 + 0.5).truncate
    rfp = 1.0 - fp
    xgap = rfp
    xpxl2 = xend
    ypxl2 = yend.truncate
    fp = yend - yend.truncate
    rfp = 1.0 - fp
    XiaolinWuDraw.put_colour(img, xpxl2, ypxl2,     colour, steep, rfp * xgap)
    XiaolinWuDraw.put_colour(img, xpxl2, ypxl2 + 1, colour, steep,  fp * xgap)

    # in between
    (xpxl1 + 1).upto(xpxl2 - 1).each do |x|
      itt = itery.truncate
      fp = itery - itery.truncate
      rfp = 1.0 - fp
      XiaolinWuDraw.put_colour(img, x, itt,     colour, steep, rfp)
      XiaolinWuDraw.put_colour(img, x, itt + 1, colour, steep,  fp)
      itery = itery + gradient
    end
  end

  def XiaolinWuDraw.put_colour(img, x, y, col, steep, c)
    return if c == 0.0 or col[0] == 0
    x, y = y, x if steep
    o = img[x, y]
    n = Array.new(4)
    if o[0] >= 255
      # Bg Alpha = 255
      n[0] = 255
      for i in 1..3 do
        n[i] = (col[i] * c + o[i] * (1.0 - c)).round
      end
    elsif o[0] == 0
      # Bg Alpha = 0
      n[0] = (255 * c).round
      for i in 1..3 do
        n[i] = col[i]
      end
    else
      na = (255 * c).round
      n[0] = (na > o[0])? na : o[0]
      for i in 1..3 do
        n[i] = (col[i] * c + o[i] * (1.0 - c)).round
      end
    end
    img[x, y] = n
  end
end

# ----------------------------------------
# test

if $0 == __FILE__
  font = Font.new(14)

  w, h = 500, 500
  bgcol = [0, 0, 255] # blue (R,G,B)
  fgcol = [255, 255, 255, 0] # Yellow (A,R,G,B)
  fgcol2 = [255, 0, 255, 0] # Green (A,R,G,B)

  p = []
  10.step(460, 30) do |a|
    p.push([10, 10, 490, a, fgcol])
    p.push([10, 10, a, 490, fgcol])
  end
  p.push([10, 10, 490, 490, fgcol])

  img0 = Image.new(w, h)
  # img0.fill(bgcol)
  p.each do |d|
    x1, y1, x2, y2, col = d
    XiaolinWuDraw.draw_line(img0, x1, y1, x2, y2, col)
  end
  # img0.save("test.png")

  img1 = Image.new(w, h)
  p.each do |d|
    x1, y1, x2, y2, col = d
    img1.line(x1, y1, x2, y2, col)
  end

  img2 = Image.new(640, 480)

  Window.bgcolor = bgcol
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # Push A key
    aa_enable = (Input.keyDown?(K_A))? false : true

    img2.clear
    x1, y1 = 320, 240
    x2, y2 = Input.mousePosX, Input.mousePosY

    if aa_enable
      XiaolinWuDraw.draw_line(img2, x1, y1, x2, y2, fgcol2)
      Window.draw(0, 0, img0)
      Window.draw(0, 0, img2)
    else
      img2.line(x1, y1, x2, y2, fgcol2)
      Window.draw(0, 0, img1)
      Window.draw(0, 0, img2)
    end

    s = "#{Window.real_fps} FPS  CPU: #{Window.getLoad.to_i} %"
    Window.drawFont(640 - 140, 2, s, font)
    s = "Push A key"
    Window.drawFont(640 - 140, 20, s, font)
  end
end
一見それらしく描画できたものの、アルファチャンネル値の扱いに関してバグが残ってるような気がします…。

直線描画。 :

色々なやり方で直線を描画してみたり。

直線描画

_linedraw.rb
require 'dxruby'
require_relative 'xiaolinwu'

#
# 直線を描画するクラス
#
class LineDraw

  # 直線を描画
  #
  # @param [Object] img 描画対象のImageオブジェクト
  # @param [Number] x1 直線始点 x
  # @param [Number] y1 直線始点 y
  # @param [Number] x2 直線終点 x
  # @param [Number] y2 直線終点 y
  # @param [Array] col 描画色配列 A,R,G,B
  # @param [Number] linew 線幅
  # @param [Number] aa アンチエイリアス処理の分割数
  # @param [Number] last_t 前回描画時の余った値
  # @param [Boolean] aa2 trueなら線幅が太い場合もアンチエイリアスをかけるが激しく遅い
  # @param [Boolean] edge_only trueならエッジのみ描画。動作確認用
  # @return [Number] 描画時の余った値
  #
  def LineDraw.draw(img, x1, y1, x2, y2, col, linew = 1,
                    aa = 1, last_t = 0, aa2 = false, edge_only = false)
    ret = 0
    if linew <= 1
      # 線幅 = 1 の時

      if aa <= 1
        # アンチエイリアス無し
        LineDraw.draw_dda(img, x1, y1, x2, y2, col)
        # img.line(x1, y1, x2, y2, col)
      else
        # アンチエイリアス有り
        XiaolinWuDraw.draw_line(img, x1, y1,x2, y2, col)
      end
    else
      # 線幅 > 1 の時

      if aa2
        # アンチエイリアス有りで描画。激しく遅い
        ret = LineDraw.draw_aa(img, x1, y1, x2, y2, col, linew, aa, last_t)
      else
        # アンチエイリアス無しで描画

        # 三角形ポリゴン+円塗りつぶしで描画
        ret = LineDraw.draw_poly(img, x1, y1, x2, y2, col, linew, edge_only)

        # ブラシで描画
        # ret = LineDraw.draw_brush(img, x1, y1, x2, y2, col, linew, last_t)
      end
    end
    return ret
  end

  # ----------------------------------------
  # 直線をDDAで描画。アンチエイリアス無し
  #
  # @note 以下参考ページ
  #       DDAによる線・円の描画3
  #       http://www7.plala.or.jp/nekogrammer/dda/DDA3.htm
  #
  # @param [Object] img 描画対象のImageオブジェクト
  # @param [Number] x1 直線始点 x
  # @param [Number] y1 直線始点 y
  # @param [Number] x2 直線終点 x
  # @param [Number] y2 直線終点 y
  # @param [Array] col 描画色配列。A,R,G,B
  #
  def LineDraw.draw_dda(img, x1, y1, x2, y2, col)
    x1 = x1.to_i
    y1 = y1.to_i
    x2 = x2.to_i
    y2 = y2.to_i
    dx = x2 - x1
    dy = y2 - y1
    a = dx.abs
    b = dy.abs
    xadd = LineDraw.get_sign(dx)
    yadd = LineDraw.get_sign(dy)
    x, y = x1, y1
    if a >= b
      # x方向のほうが大きい
      e = dy.abs * -1
      fg = true
      while fg
        img[x, y] = col
        break if x == x2
        x += xadd
        e += 2 * b
        if e >= 0
          y += yadd
          e -= 2 * a
        end
      end
    else
      # y方向のほうが大きい
      e = dx.abs * -1
      fg = true
      while fg
        img[x, y] = col
        break if y == y2
        y += yadd
        e += 2 * a
        if e >= 0
          x += xadd
          e -= 2 * b
        end
      end
    end
    return 0
  end

  def LineDraw.get_sign(x)
    return 0 if x == 0
    return -1 if x < 0
    return 1
  end

  # ----------------------------------------
  # 直線をブラシ画像で描画
  #
  # @param [Object] img 描画対象のImageオブジェクト
  # @param [Number] x1 直線始点 x
  # @param [Number] y1 直線始点 y
  # @param [Number] x2 直線終点 x
  # @param [Number] y2 直線終点 y
  # @param [Array] col 描画色配列 A,R,G,B
  # @param [Number] linew 線幅
  # @param [Number] last_t 前回描画時の余った値
  # @param [Number] line_interval 描画間隔
  # @return [Number] 描画時の余った値
  #
  def LineDraw.draw_brush(img, x1, y1, x2, y2, col, linew,
                          last_t = 0, line_interval = 0.2)

    brush = LineDraw.get_brush_image(col, linew, aa = 4)
    dx = (x2 - x1).to_f
    dy = (y2 - y1).to_f
    len = Math.sqrt(dx * dx + dy * dy)
    return if len == 0
    ti = line_interval / len
    t = last_t / len
    r = brush.width / 2
    while t < 1.0
      x = dx * t + x1 - r
      y = dy * t + y1 - r
      img.draw(x, y, brush)
      dt = r * ti
      dt = 0.0001 if dt < 0.0001
      t += dt
    end
    last_t = len * (t - 1.0)
    brush.dispose
    return last_t
  end

  # 描画用のブラシImageを取得
  #
  # @param [Array] col ブラシ色
  # @param [Number] linew 線幅
  # @param [Number] aa アンチエイリアス値
  # @return [Object] ブラシ用Image
  #
  def LineDraw.get_brush_image(col, linew, aa = 9)
    w, h = linew, linew
    r = linew / 2.0
    if w <= 0 or h <= 0
      w, h = 1, 1
      r = 0.5
    end
    img = Image.new(w, h)
    rr = r * r
    aad = aa * aa
    0.step(h, 1.0) do |y|
      0.step(w, 1.0) do |x|
        v = 0
        aa.times do |jy|
          yy = jy * (1.0 / aa) + y - r
          aa.times do |jx|
            xx = jx * (1.0 / aa) + x - r
            d = (xx * xx + yy * yy) / rr
            v += (1.0 - d) if d < 1.0
          end
        end
        a = col[0] * v.to_f / aad
        img[x, y] = [a, col[1], col[2], col[3]]
      end
    end
    return img
  end

  # ----------------------------------------
  # 直線をアンチエイリアス有りで描画
  #
  # @note 以下参考ページ
  #       アンチエイリアス付きの自由線描画(1)
  #       http://azskyetc.client.jp/paintprog/025_aaline1.html
  #
  # @param [Object] img 描画対象のImageオブジェクト
  # @param [Number] x1 直線始点 x
  # @param [Number] y1 直線始点 y
  # @param [Number] x2 直線終点 x
  # @param [Number] y2 直線終点 y
  # @param [Array] col 描画色配列 A,R,G,B
  # @param [Number] linew 線幅
  # @param [Number] aa アンチエイリアス処理の分割数
  # @param [Number] last_t 前回描画時の余った値
  # @param [Number] line_interval 描画間隔
  # @return [Number] 描画時の余った値
  #
  def LineDraw.draw_aa(img, x1, y1, x2, y2, col, linew,
                       aa = 4, last_t = 0, line_interval = 0.25)
    dx = (x2 - x1).to_f
    dy = (y2 - y1).to_f
    len = Math.sqrt(dx * dx + dy * dy)
    return if len == 0
    ti = line_interval / len
    t = last_t / len
    while t < 1.0
      x = dx * t + x1
      y = dy * t + y1
      LineDraw.draw_point(img, x, y, col, linew, aa)
      dt = linew * ti
      dt = 0.0001 if dt < 0.0001
      t += dt
    end
    last_t = len * (t - 1.0)
    return last_t
  end

  # アンチエイリアス付きで円を描画
  #
  # @param [Object] img Imageオブジェクト
  # @param [Number] px x座標
  # @param [Number] py y座標
  # @param [Array] col 描画色配列。A,R,G,B
  # @param [Number] linew 線幅
  # @param [Number] aa アンチエイリアス処理の分割数
  #
  def LineDraw.draw_point(img, px, py, col, linew, aa = 4)
    r = linew / 2.0
    x1, y1 = (px - r).floor, (py - r).floor
    x2, y2 = (px + r).ceil, (py + r).ceil
    rr = r * r
    aad = aa * aa
    cc = [255, col[1].to_i, col[2].to_i, col[3].to_i]
    for y in y1..y2
      for x in x1..x2
        next if img.compare(x,y,cc)

        v = 0.0
        xa = x - px
        ya = y - py
        for jy in 0..aa
          yy = ya + (jy.to_f / aa)
          for jx in 0..aa
            xx = xa + (jx.to_f / aa)
            d = (xx * xx + yy * yy) / rr
            v += (1.0 - d) if d < 1.0
          end
        end
        ao = col[0] * v / aad
        ao = 0 if ao < 0
        ao = 255.0 if ao > 255.0

        s = img[x,y]
        s[0] = (s[0] > ao)? s[0] : ao
        a = ao / 255.0
        ma = 1.0 - a
        s[1] = s[1] * ma + cc[1] * a
        s[2] = s[2] * ma + cc[2] * a
        s[3] = s[3] * ma + cc[3] * a
        img[x,y] = s
      end
    end
  end

  # ----------------------------------------
  # 三角形塗りつぶしと円塗りつぶしを使って直線を描画
  #
  # @note 線本体を三角形塗りつぶしを2回使って描く。始点と終点は円塗りつぶしで描く。
  #
  # @param [Object] img 描画対象のImageオブジェクト
  # @param [Number] x1 直線始点 x
  # @param [Number] y1 直線始点 y
  # @param [Number] x2 直線終点 x
  # @param [Number] y2 直線終点 y
  # @param [Array] col 描画色配列 A,R,G,B
  # @param [Number] linew 線幅
  # @param [Boolean] edge_only trueならエッジのみ描画。動作確認用
  # @return [Number] 描画で余った値
  #
  def LineDraw.draw_poly(img, x1, y1, x2, y2, col, linew, edge_only = false)
    r = linew / 2.0
    dx = x2 - x1
    dy = y2 - y1
    rad = Math.atan2(dy, dx) + (Math::PI / 2.0)
    xd = r * Math.cos(rad)
    yd = r * Math.sin(rad)

    # 線幅を持った線、つまり、矩形の4点を求める
    np = []
    np.push([x1 + xd, y1 + yd])
    np.push([x2 + xd, y2 + yd])
    np.push([x2 - xd, y2 - yd])
    np.push([x1 - xd, y1 - yd])

    if edge_only
      # エッジだけ描画して動作確認
      np.size.times do |i|
        x3, y3 = np[i]
        x4, y4 = np[(i+1) % np.size]
        img.line(x3, y3, x4, y4, col)
      end
      img.circle(x1, y1, r, col)
      img.circle(x2, y2, r, col)
    else
      # 塗り潰し

      # 三角形塗りつぶしを2回
      img.triangleFill(np[0][0], np[0][1], np[1][0], np[1][1],
                       np[2][0], np[2][1], col)
      img.triangleFill(np[2][0], np[2][1], np[3][0], np[3][1],
                       np[0][0], np[0][1], col)
      # 始点と終点を円で塗りつぶし
      img.circleFill(x1, y1, r, col)
      img.circleFill(x2, y2, r, col)
    end

    return 0
  end

end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  font = Font.new(14)
  img = Image.new(640, 480)
  col = [255, 0, 255, 0]
  p = [
    [
      [58.15, 39.0],
      [58.15, 230.1],
      [171.1, 230.1],
      [171.1, 39.0],
      [58.15, 39.0]
    ],
    [
      [20.0, 240.0],
      [100.0, 300.0],
      [600.0, 400.0]
    ]
  ]

  linew = 1
  aa = 1
  aa2 = false
  list_kind = 0
  point_mv = false
  edge_only = false

  # Window.scale = 2
  Window.bgcolor = [128, 128, 128]

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # Eキーで塗りつぶし/エッジのみ描画を切替
    edge_only = !edge_only if Input.keyPush?(K_E)

    # 上下キーで線幅を変更
    linew += 0.5 if Input.keyPush?(K_UP)
    linew -= 0.5 if Input.keyPush?(K_DOWN)
    linew = 1 if linew < 1

    # Aキーで、ブラシ描画/アンチエイリアス描画の切り替え
    aa = ((aa == 1)? 3 : 1) if Input.keyPush?(K_A)

    # Sキーで、線幅が太い時もアンチエイリアス描画
    aa2 = !aa2 if Input.keyPush?(K_S)

    # Kキーで、座標配列を切替
    list_kind = (list_kind + 1) % p.size if Input.keyPush?(K_K)

    # Mキーで、座標のマウス移動を切替
    point_mv = !point_mv if Input.keyPush?(K_M)
    if point_mv
      p[list_kind][1][0] = Input.mousePosX
      p[list_kind][1][1] = Input.mousePosY
    end

    img.clear
    (p[list_kind].size - 1).times do |i|
      x0, y0 = p[list_kind][i]
      x1, y1 = p[list_kind][(i+1) % p[list_kind].size]
      LineDraw.draw(img, x0, y0, x1, y1, col, linew,
                    aa, 0, aa2, edge_only)
    end

    Window.draw(0, 0, img)

    s = "LineWidth=#{linew}  #{Window.real_fps} FPS  CPU: #{Window.getLoad.to_i}%"
    Window.drawFont(8, 2, s, font)
    s = "A , S , K , E , M , up , down key"
    Window.drawFont(8, 16, s, font)
  end
end
  • 上下キー : 線幅の増減
  • Kキー : 描画する形状を変更
  • Mキー : 1点だけマウスカーソルで移動
  • Aキー : (線幅が1の時) アンチエイリアスの有効無効
  • Eキー : (線幅が1より大きい時) エッジだけ描画するか、塗り潰すか
  • Sキー : (線幅が1より大きい時) アンチエイリアスの有効無効 (※ 滅茶苦茶重い)
線幅有りでアンチエイリアスを有効にすると、フリーズしたかと思うぐらいに遅くなります。ここをどうにかしたいけど…。

ソース中で、以下の描画方法を試してます。
  • DDA : 1ドット幅、アンチエイリアス無。
  • Xiaolin Wu's line algorithm : 1ドット幅、アンチエイリアス有。(xiaolinwu.rb を利用)
  • 三角形塗りつぶし+円塗りつぶし : 線幅有、アンチエイリアス無。
  • ブラシ画像 : 線幅有、なんちゃってアンチエイリアス有。
  • オーバーサンプリング : 線幅有、アンチエイリアス有。

オーバーサンプリングを使った描画は、 _アンチエイリアス付きの自由線描画(1) をそのまま Ruby で書いてみました。さすがに Ruby でやると遅い…。

swfmillでswf→xml変換したxmlを読み込んでシェイプを描画。 :

xmlの解析に、 _Nokogiri が必要です。

(※ 2014/05/30追記。Nokogiri をインストールする前に、最新版の mini_portile をインストールしないと、Nokogiri のインストールに失敗する模様 )

シェイプを描画

_shapeparse.rb
require 'nokogiri'
require 'dxruby'
require_relative 'polyfill'
require_relative 'bezierdraw'
require_relative 'linedraw'

# swfのシェイプを解析してImageに描画するクラス
class ShapeParse
  DBG = false
  DBG2 = true
  TWIP = 20.0
  attr_accessor :id
  attr_accessor :width
  attr_accessor :left
  attr_accessor :top
  attr_accessor :height
  attr_accessor :image
  attr_accessor :draw_data
  attr_accessor :clip_enable

  def initialize(node, clip_enable = true)
    self.clip_enable = clip_enable
    self.id = node["objectID"].to_i
    puts "id[#{self.id}] #{node.name}" if DBG

    n = node.at(".//bounds/Rectangle")
    self.left = n["left"].to_i / ShapeParse::TWIP
    self.top = n["top"].to_i / ShapeParse::TWIP
    self.width = n["right"].to_i / ShapeParse::TWIP
    self.height = n["bottom"].to_i / ShapeParse::TWIP
    puts "size : (x,y)=#{self.left},#{self.top} (w,h)=#{self.width},#{self.height}" if DBG

    self.width = Window.width if self.width < Window.width
    self.height = Window.height if self.height < Window.height

    self.image = Image.new(self.width, self.height)

    x0, y0 = 0, 0
    x1, y1 = 0, 0
    x2, y2 = 0, 0
    f0_enable = true
    f1_enable = true
    l_enable = true
    fc = [[0, 0, 0, 0], [0, 0, 0, 0]]
    lc = [0, 0, 0, 0]
    lwidth = ShapeParse::TWIP
    lines = []
    self.draw_data = []

    n = node.at(".//shapes/Shape/edges").child

    while n != nil
      case n.name
      when "ShapeSetup"
        unless lines.empty?
          save_draw_data(lines, f0_enable, f1_enable, l_enable,
                         lwidth, fc[0], fc[1], lc)
          lines = []
        end

        s = "#{n.name} "
        x0 = n["x"].to_f if n.key?("x")
        y0 = n["y"].to_f if n.key?("y")
        f0_enable = (n["fillStyle0"] == "1")? true : false
        f1_enable = (n["fillStyle1"] == "1")? true : false
        l_enable =  (n["lineStyle"] == "1")? true : false

        if false
          s += "(x,y)=#{x0/TWIP}, #{y0/TWIP} ("
          s += (f0_enable)? "f0 " : "   "
          s += (f1_enable)? "f1 " : "   "
          s += (l_enable)? "l" : " "
          s += ")"
          puts s
        end

        n.xpath(".//fillStyles").each do |nn|
          nn.xpath(".//Solid/color/Color").each_with_index do |m, i|
            fc[i] = get_color(m)
          end
        end

        n.xpath(".//lineStyles").each do |nn|
          nn.xpath(".//LineStyle").each do |m|
            lwidth = m["width"].to_i if m.key?("width")
            m.xpath(".//color//Color").each do |c|
              lc = get_color(c)
            end
          end
        end

        if n.key?("x") and n.key?("y")
          lines.push([x0, y0])
        end

      when "CurveTo"
        # 二次ベジェ曲線
        x1 = x0 + n["x1"].to_f
        y1 = y0 + n["y1"].to_f
        x2 = x1 + n["x2"].to_f
        y2 = y1 + n["y2"].to_f
        lst = BezierDraw.calc(x0, y0, x1, y1, x2, y2)
        lines.concat(lst.slice(1..-1))
        x0 = x2
        y0 = y2

      when "LineTo"
        # 直線
        x1 = x0 + n["x"].to_f
        y1 = y0 + n["y"].to_f
        lines.push([x1, y1])
        x0 = x1
        y0 = y1

      else
        puts "!! Unknown Tag : #{n.name}"
      end
      n = n.next
    end

    draw(false)
  end

  # 色配列を取得
  #
  # @param [Object] node 「Color」ノード
  # @return [Array] A,R,G,Bが入った配列
  #
  def get_color(node)
    col = [255, 0, 0, 0] # ARGB
    ["alpha", "red", "green", "blue"].each_with_index do |s,i|
      col[i] = node[s].to_f if node.key?(s)
    end
    return col
  end

  # 描画用の直線データを記録
  #
  # @param [Array] lines 直線データ配列
  # @param [Boolean] fe0 塗り潰し0をするか否か
  # @param [Boolean] fe1 塗り潰し1をするか否か
  # @param [Boolean] le 線を描くか否か
  # @param [Number] lw 線幅
  # @param [Array] fc0 塗り潰し0の色配列
  # @param [Array] fc1 塗り潰し1の色配列
  # @param [Array] lc1 線描画の色配列
  #
  def save_draw_data(lines, fe0, fe1, le, lw, fc0, fc1, lc)
    np = []
    ox, oy = nil, nil
    lines.each do |p|
      x, y = p
      x /= TWIP
      y /= TWIP
      np.push([x, y]) if x != ox or y != oy
      ox, oy = x, y
    end

    # 記録
    self.draw_data.push([np, fe0, fe1, le, lw, fc0.dup, fc1.dup, lc.dup])
  end

  # Imageオブジェクトに描画
  #
  # @param [Boolean] clear_enable trueならImageをクリアしてから描画
  #
  def draw(clear_enable = true, scale_x = 1.0, scale_y = 1.0)
    self.image.clear if clear_enable

    self.draw_data.each do |d|
      p, fe0, fe1, le, lw, fc0, fc1, lc = d
      np = []
      p.each { |q| np.push([q[0] * scale_x, q[1] * scale_y]) }
      draw_line(self.image, np, fe0, fe1, le, lw, fc0, fc1, lc)
    end
  end

  # 多角形を描画
  #
  # @param [Object] img Imageオブジェクト
  # @param [Array] p 頂点座標が入った配列。
  # @param [Boolean] f0_enable trueなら塗り潰し有、falseなら塗り潰し無
  # @param [Boolean] f1_enable trueなら塗り潰し有、falseなら塗り潰し無
  # @param [Boolean] l_enable trueなら線を描く、falseなら線を描かない
  # @param [Number] lwidth 線幅
  # @param [Array] fc0 色配列。A,R,G,B
  # @param [Array] fc1 色配列。A,R,G,B
  # @param [Array] lc 色配列。A,R,G,B
  #
  def draw_line(img, p, f0_enable, f1_enable, l_enable,
                lwidth, fc0, fc1, lc)
    if f0_enable and p.size >= 3
      # 塗り潰しを行う
      PolyFill.fill(img, p, fc0, fc1, self.clip_enable)
    end

    if l_enable
      # 線描画を行う
      (p.size - 1).times do |i|
        x0, y0 = p[i]
        x1, y1 = p[(i+1) % p.size]
        LineDraw.draw(img, x0, y0, x1, y1, lc, lwidth / TWIP,
                      3, 0, false, false)
      end
    end
    lines = []
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  font = Font.new(14)

  # swfmillで swf → xml変換したxmlを解析

  # infile = "swfdata/test_pdr1.xml"
  infile = "swfdata/test_pdr2.xml"

  doc = Nokogiri::XML(File.open(infile)) {|cfg| cfg.noblanks}

  objs = Hash.new
  doc.xpath("//DefineShape3").each do |node|
    pdr = ShapeParse.new(node, false) # ベクターデータを解析して画像化
    objs[pdr.id] = pdr
  end

  redraw_fg = false
  ang = 0
  scale = 1.0

  Window.bgcolor = [0, 190, 118]
  Window.fps = 60

  # メインループ
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # Rキー押しでスケールを変えて再描画
    redraw_fg = false
    redraw_fg = true if Input.keyDown?(K_R)

    objs.each_value do |o|
      # ベクターデータを再描画できなくもないが、遅くて実用にならない
      o.draw(true, scale, scale) if redraw_fg

      Window.draw(0, 0, o.image)
    end

    s = "#{Window.real_fps} FPS / CPU: #{Window.getLoad.to_i}%"
    Window.drawFont(4, 4, s, font)

    ang += 10
    scale = Math.cos(ang * Math::PI / 180.0) + 1.5
  end
end
  • Rキー : 押してる間、ベクターデータ(シェイプ)を、大きさを変えて再描画。数秒無反応になるぐらいに遅い。
虎のxml (test_pdr2.xml) を使うと、描画されるまで数秒待たされます…。 test_pdr1.xml なら、すぐに描画されますが、それでも、60FPSでリアルタイム描画するのは全然無理でした。

一応、データ群も一緒にまとめて zip で置いときます。

_swf_shape_parse_20140529.zip (432KB)

ここまで書いといてアレだけど。 :

swfのベクターデータを描画する処理に関しては、Ruby で書いてる限り、速度の問題で、実用性は無いなと痛感していたり。Flash Player なら一瞬で表示されるものが、Ruby + DXRuby では数秒かかるわけで。自分の実装がヒドイだけかもしれませんが。

でも、描画アルゴリズムの勉強・動作確認としてはアリかなと思いつつ。

#2 [nitijyou] 自転車で買い物に

リオンドールで夜食等を購入。

ホームセンター サンデーで犬を繋ぐためのワイヤーを購入。1.3mもあればなんとかなるかなと思ったけど、帰宅後封を切ってみたら意外と短かった。

以上、1 日分です。

過去ログ表示

Prev - 2014/05 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project