2014/05/29(木) [n年前の日記]
#1 [dxruby][cg_tools] DXRubyでswfのシェイプを描画する実験
とりあえず、今現在動いてるところまでメモしておこうかなと。
_polyfill.rb
_多角形塗りつぶし(1) をそのまま DXRuby で動くように書き直しただけですが…。
◎ 二次ベジェ曲線。 :
まずは、二次ベジェ曲線の描画を実験。
_bezierdraw.rb
_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
_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
ソース中で、以下の描画方法を試してます。
オーバーサンプリングを使った描画は、 _アンチエイリアス付きの自由線描画(1) をそのまま Ruby で書いてみました。さすがに Ruby でやると遅い…。
_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
一応、データ群も一緒にまとめて zip で置いときます。
_swf_shape_parse_20140529.zip (432KB)
(※ 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キー : 押してる間、ベクターデータ(シェイプ)を、大きさを変えて再描画。数秒無反応になるぐらいに遅い。
一応、データ群も一緒にまとめて zip で置いときます。
_swf_shape_parse_20140529.zip (432KB)
◎ ここまで書いといてアレだけど。 :
swfのベクターデータを描画する処理に関しては、Ruby で書いてる限り、速度の問題で、実用性は無いなと痛感していたり。Flash Player なら一瞬で表示されるものが、Ruby + DXRuby では数秒かかるわけで。自分の実装がヒドイだけかもしれませんが。
でも、描画アルゴリズムの勉強・動作確認としてはアリかなと思いつつ。
でも、描画アルゴリズムの勉強・動作確認としてはアリかなと思いつつ。
[ ツッコむ ]
以上です。