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 では数秒かかるわけで。自分の実装がヒドイだけかもしれませんが。
でも、描画アルゴリズムの勉強・動作確認としてはアリかなと思いつつ。
でも、描画アルゴリズムの勉強・動作確認としてはアリかなと思いつつ。
[ ツッコむ ]
#2 [nitijyou] 自転車で買い物に
リオンドールで夜食等を購入。
ホームセンター サンデーで犬を繋ぐためのワイヤーを購入。1.3mもあればなんとかなるかなと思ったけど、帰宅後封を切ってみたら意外と短かった。
ホームセンター サンデーで犬を繋ぐためのワイヤーを購入。1.3mもあればなんとかなるかなと思ったけど、帰宅後封を切ってみたら意外と短かった。
[ ツッコむ ]
以上、1 日分です。





