#!ruby -Ks # -*- mode: ruby; encoding: sjis -*- # Last updated: <2014/07/07 00:54:50 +0900> # # Mechanical texture generator # メカっぽいテクスチャを生成するRubyスクリプト require 'dxruby' require 'cairo' require 'tempfile' require 'stringio' require 'optparse' Version = "0.0.1" class MechaTextGen attr_accessor :image attr_accessor :width attr_accessor :height attr_accessor :divide_num attr_accessor :surface attr_accessor :context def initialize(w = 512, h = 512, divide_num = 4) self.width = w self.height = h self.divide_num = divide_num end # # テクスチャを生成 # def make_texture self.surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, self.width, self.height) self.context = Cairo::Context.new(surface) ct = self.context c = 0.25 ct.set_source_rgb(c, c, c) ct.rectangle(0, 0, self.width, self.height) ct.fill x0, y0, x1, y1 = 0, 0, self.width - 1, self.height - 1 div_rect(ct, x0, y0, x1, y1, rand(self.divide_num) + 1, 1, 0, 4, 0.25) convert_surface_to_dxruby_image end # # 矩形を分割しながら描画(再帰呼び出し有) # # @param [Object] ct context # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Number] d 分割数 # @param [Number] v 0ならx方向に、1ならy方向に、それ以外なら長辺方向に分割 # @param [Number] cnt 現在何回目の分割か # @param [Number] cnt_max 何回まで分割するか # @param [Number] colv 塗り潰し色の明るさを1.0未満の値で指定 # def div_rect(ct, x0, y0, x1, y1, d, v, cnt, cnt_max, colv) return if cnt > cnt_max rect = get_div_rect(x0, y0, x1, y1, d, v) rect.each do |r| tx0, ty0, tx1, ty1 = r a = 2 l = 0.0 rr = 3 c0 = 0.0 draw_rect(ct, tx0, ty0, tx1, ty1, [c0, c0, c0, 0.2], [l, l, l, 1.0], 0, true, false, 0) c1 = colv + (Random.rand(10) + 5) / 100.0 draw_rect(ct, tx0 + a, ty0 + a, tx1 - a, ty1 - a, [c1, c1, c1, 1.0], [l, l, l, 1.0], 0, true, false, rr) d = rand(self.divide_num) + 1 div_rect(ct, tx0, ty0, tx1, ty1, d, 2, cnt + 1, cnt_max, colv + 0.1) if cnt == cnt_max w = tx1 - tx0 h = ty1 - ty0 aw = 54 if w > aw and h > aw and rand(100) < 75 r = rand(100) if r < 50 draw_line_box_a(ct, tx0, ty0, tx1, ty1, 0.0, rand(4) + 10, 5) else draw_line_box_a(ct, tx0, ty0, tx1, ty1, 1.0, rand(6) + 16, 8) end else fill_rand_rect(ct, tx0, ty0, tx1, ty1, 10) end aw = 32 draw_rivet(ct, tx0, ty0, tx1, ty1, 8) if w > aw and h > aw end end end # # 矩形を分割 # # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Number] d 分割数 # @param [Number] v 0ならx方向に、1ならy方向に、それ以外なら長辺方向に分割 # @param [Number] rv ぶれる量(1.0未満の値) # @return [Array] x0,y0,x1,y1が複数入った配列を返す # def get_div_rect(x0, y0, x1, y1, d, v, bure = 0.6) rw = x1 - x0 rh = y1 - y0 tw = rw th = rh ydiv_enable = true case v when 0 ydiv_enable = false when 1 ydiv_enable = true else ydiv_enable = (tw > th)? false : true end if ydiv_enable th = rh.to_f / d else tw = rw.to_f / d end rect = [] rx0, ry0 = x0, y0 rx1, ry1 = x1, y1 if ydiv_enable d.times do |i| rv = Random.rand(th * bure) - th * (bure / 2.0) ry1 = (i < (d-1))? (ry0 + th + rv).floor : y1 rect.push([rx0, ry0, rx1, ry1]) ry0 = ry1 end else d.times do |i| rv = Random.rand(th * bure) - th * (bure / 2.0) rx1 = (i < (d-1))? (rx0 + tw + rv).floor : x1 rect.push([rx0, ry0, rx1, ry1]) rx0 = rx1 end end return rect end # # 矩形を描画 # # @param [Object] context # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Array] fc 塗り潰し色。[r,g,b,a]の配列 # @param [Array] lc 線色。[r,g,b,a]の配列 # @param [Number] lw 線幅 # @param [Boolean] fill_ok trueなら塗り潰す # @param [Boolean] line_ok trueなら線を描く # @param [Number] rr 0以外なら角丸矩形を描画 # def draw_rect(ct, x0, y0, x1, y1, fc, lc, lw, fill_ok, line_ok, rr = 0) w = x1 - x0 h = y1 - y0 ct.set_line_join(Cairo::LineJoin::ROUND) # 結合点の種類を指定 ct.set_line_cap(Cairo::LineCap::ROUND) # 線の終点の種類を指定 ct.set_line_width((line_ok)? lw : 0) # 線幅を指定 ct.set_source_rgba(fc[0], fc[1], fc[2], fc[3]) # 塗り潰し色を指定 if rr == 0 ct.rectangle(x0, y0, w, h) # 矩形を描画 else ct.rounded_rectangle(x0, y0, w, h, rr, rr) # 角丸矩形を描画 end ct.fill_preserve if fill_ok # 塗りつぶし ct.set_source_rgba(lc[0], lc[1], lc[2], lc[3]) if line_ok # 線の色を指定 ct.stroke # 線を描画 end # # 与えられた範囲内にランダムな大きさの矩形を1つだけ描画 # # @param [Object] context # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Number] a 余白 # def fill_rand_rect(ct, x0, y0, x1, y1, a) x0 += a y0 += a x1 -= a y1 -= a w = x1 - x0 h = y1 - y0 return if w < 0 or h < 0 rw = w / 2 rh = h / 2 tx0 = x0 + rand(rw) ty0 = y0 + rand(rh) tx1 = x1 - rand(rw) ty1 = y1 - rand(rh) tx0, tx1 = tx1, tx0 if tx0 > tx1 ty0, ty1 = ty1, ty0 if ty0 > ty1 tw = tx1 - tx0 th = ty1 - ty0 cw = 8 return if tw < cw or th < cw fc = Random.rand(0.25) * 4.0 lc = Random.rand(0.25) * 4.0 v = Random.rand(0.2) + 0.1 draw_rect(ct, tx0, ty0, tx1, ty1, [fc, fc, fc, v], [lc, lc, lc, 0.5], 1, true, true, 2) end def draw_rand_rect(ct, x0, y0, x1, y1, cnt, a) x0 += a y0 += a x1 -= a y1 -= a w = x1 - x0 h = y1 - y0 lw = 1 rr = 3 ct.set_line_join(Cairo::LineJoin::ROUND) ct.set_line_cap(Cairo::LineCap::ROUND) ct.set_line_width(lw) cnt.times do |i| fc = 1.0 ct.set_source_rgba(fc, fc, fc, 0.1) x, y = rand(w) + x0, rand(h) + y0 xw, yh = rand(w / 3) + 8, rand(h / 3) + 16 xw = x1 - x if x + xw > x1 yh = y1 - y if y + yh > y1 ct.rounded_rectangle(x, y, xw, yh, rr, rr) ct.fill end end # # リベットを描画 # # @param [Object] ct context # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Number] a 矩形の四隅からどれだけ離れた場所に描画するか # def draw_rivet(ct, x0, y0, x1, y1, a) draw_rivet_one(ct, x0 + a, y0 + a) draw_rivet_one(ct, x1 - a, y0 + a) draw_rivet_one(ct, x0 + a, y1 - a) draw_rivet_one(ct, x1 - a, y1 - a) end # # リベットを1つ描画 # # @param [Object] ct context # @param [Number] x 描画位置x # @param [Number] y 描画位置y # def draw_rivet_one(ct, x, y) c = 0.6 4.step(0.5, -0.5) do |r| ct.set_source_rgb(c, c, c) ct.arc(x, y, r, 0, 2 * Math::PI) ct.fill c += 0.1 c = 1.0 if c >= 1.0 end end # # 線を複数引く # # @param [Object] ct context # @param [Number] x0 矩形の左上x # @param [Number] y0 矩形の左上y # @param [Number] x1 矩形の右下x # @param [Number] y1 矩形の右下y # @param [Number] col 線の色。1.0未満の値 # @param [Number] xd 間隔 # @param [Number] lw 線幅 # def draw_line_box_a(ct, x0, y0, x1, y1, col, xd, lw) a = 20 # b = rand(6) + 10 b = xd cnt = rand(4) + 3 x0 += a y0 += a x1 -= a y1 -= a w = x1 - x0 h = y1 - y0 xadd_enable = (w > h)? true : false w *= (Random.rand(0.6) + 0.3) h *= (Random.rand(0.6) + 0.3) if xadd_enable if rand(100) > 50 x = x0 xd = b else x = x1 xd = -b end xb = x y = (rand(100) > 50)? y0 : (y1 - h) yb = y + h yd = 0 else if rand(100) > 50 y = y0 yd = b else y = y1 yd = -b end yb = y x = (rand(100) > 50)? x0 : (x1 - w) xb = x + w xd = 0 end ct.set_line_join(Cairo::LineJoin::ROUND) ct.set_line_cap(Cairo::LineCap::ROUND) ct.set_line_width(lw) cnt.times do |i| ct.set_source_rgb(col, col, col) ct.move_to(x, y) ct.line_to(xb, yb) ct.stroke x += xd y += yd xb += xd yb += yd break if x < x0 or xb > x1 break if y < y0 or yb > y1 end end # # convert rcairo surface to DXRuby Image # def convert_surface_to_dxruby_image temp = StringIO.new("", 'w+') self.surface.write_to_png(temp) temp.rewind self.image.dispose if self.image self.image = Image.loadFromFileInMemory(temp.read) temp.close end # # save surface to png # # @param [String] fn png file path # def save_surface(fn) self.surface.write_to_png(fn) end end if $0 == __FILE__ args = {} args[:output] = "out.png" args[:width] = 512 args[:height] = 512 args[:seed] = 1 OptionParser.new do |parser| parser.on('-r VALUE', '--rand VALUE', 'random seed') { |v| args[:seed] = v } parser.on('--width VALUE', "image width [#{args[:width]}]") { |v| args[:width] = v.to_i } parser.on('--height VALUE', "image height [#{args[:height]}]") { |v| args[:height] = v.to_i } parser.on('-o VALUE', '--output VALUE', "save filename [#{args[:output]}]") { |v| args[:output] = v } parser.on_tail("-h", "--help", "Show this message") do puts parser exit end parser.parse!(ARGV) end srand(args[:seed].to_i) savefile = args[:output] font = Font.new(12) mtex = MechaTextGen.new(args[:width], args[:height]) mtex.make_texture barimg = Image.new(256, 160, [96, 0, 0, 0]) filenum = 0 save_ok = 0 msg_enable = true Window.resize(512, 512) # main loop Window.loop do break if Input.keyPush?(K_ESCAPE) # J : texture size up if Input.keyPush?(K_J) args[:width] *= 2 args[:height] *= 2 mtex = MechaTextGen.new(args[:width], args[:height]) mtex.make_texture end # K : texture size down if Input.keyPush?(K_K) args[:width] /= 2 args[:height] /= 2 mtex = MechaTextGen.new(args[:width], args[:height]) mtex.make_texture end # R or Mouse left button : remake texture if Input.keyPush?(K_R) or Input.mousePush?(M_LBUTTON) mtex.make_texture end # S : save image if Input.keyPush?(K_S) dir, bsn = File.split(args[:output]) bsn_ne = File.basename(bsn, ".png") begin savefile = File.join(dir, "#{bsn_ne}_#{filenum}.png") filenum += 1 end while FileTest.file?(savefile) mtex.save_surface(savefile) save_ok = 120 end # M : message on / off msg_enable = !msg_enable if Input.keyPush?(K_M) # draw image if mtex.image.width <= Window.width and mtex.image.height <= Window.height x = (Window.width - mtex.image.width) / 2 y = (Window.height - mtex.image.height) / 2 Window.draw(x, y, mtex.image) else d = mtex.image.width d = mtex.image.height if mtex.image.width < mtex.image.height sd = Window.width sd = Window.height if Window.width > Window.height scale = sd.to_f / d Window.drawScale(0, 0, mtex.image, scale, scale, centerx = 0, centery = 0) end # draw message if save_ok > 0 Window.draw(0, 0, barimg) Window.drawFont(4, 4, "Save Ok [#{savefile}]", font) save_ok -= 1 else if msg_enable Window.draw(0, 0, barimg) msg = [ "R or Mouse left Button : Remake", "S : Save Image", "M : Message On / Off", "J, K : size +/-", "ESC : Exit", " image size #{args[:width]} x #{args[:height]}", ] msg.each_with_index do |s, i| Window.drawFont(4, 20 + i * 16, s, font) end end end end end