#!ruby -Ks # -*- mode: ruby; encoding: sjis -*- # Last updated: <2014/05/29 06:35:00 +0900> 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