About this Blog

This Blog has English posts and Japanese posts. About Mac, iOS, Objective-C, and so on.

2013年5月19日日曜日

自作ペイントソフトで筆のような効果を出す

学校の課題で簡単なペイントソフトを作りました。
1つオリジナルの味付けをとのことだったので、書道っぽくマウスの移動速度に応じて描く線の太さを変えるようにしました。
基本的には、一定時間ごとに検出されるマウスの位置情報から、移動量を速度とみなして、その逆数に比例した線の太さを設定します。
こうすると、ゆっくり動かした時には太い線が、早く動かした時には細い線が描けます。
が、1回ごとの移動量だけを元に計算すると、変動が大きく線がボコボコになってしまいます。
そこで、過去の移動量もストアしておき、平均移動量を使うことにしました。


計算としては、以下のようになります。
 最新の移動量 = { (1つ前のX座標 - 現在のX座標)の絶対値 + (1つ前のY座標 - 現在のY座標)の絶対値 } * 0.5
 新しい平均移動量 = { (過去の平均移動量)*重み + 現在の速度} / (重み+1)
 筆の太さ = 適切なパラメータ / 新しい平均移動量

2点間の距離の計算は、近似して計算の負荷を減らしています。


実行結果は以下の通り。左が平均化していないもの、右が平均化したものです。



ソースコードは以下の通り。Ruby + GTK2です。(課題自体はCだったのですが、コードが膨大だったので)
コード中では「移動量」ではなく「速度」(velocity, v)としています。厳密には正しくないですがまあ気にしないで。

calligraphy.rb
require 'gtk2'

# 筆に関するパラメータ
# いろいろ変えてみてください。
INIT_FUDE_VELOCITY = 2 # 筆の平均速度の初期値
FUDE_PARAM = 50        # 速度の逆数に乗ずる定数
WEIGHT = 5             # 平均速度算出のための重み

# 筆に関する変数を用意
$velocity = INIT_FUDE_VELOCITY
$x0 = 0
$y0 = 0
$x1 = 0
$y1 = 0

# ウィンドウとドローイングエリアの作成など
window = Gtk::Window.new
window.set_size_request(640, 640)
window.set_app_paintable(true)
window.set_events(Gdk::Event::BUTTON_PRESS_MASK | Gdk::Event::BUTTON_RELEASE_MASK | Gdk::Event::BUTTON_MOTION_MASK)
window.realize
drawable = window.window

# グラフィックコンテキスト(GC)の用意
gc = Gdk::GC.new(drawable)
black = Gdk::Color.new(0, 0, 0)
colormap = Gdk::Colormap.system
colormap.alloc_color(black, false, true)
gc.set_foreground(black)

window.signal_connect('motion_notify_event') do |win, evt| #マウスが動いた時
  $x1 = evt.x
  $y1 = evt.y
  v = (($x1-$x0).abs + ($y1-$y0).abs)/2.0             # 最新の速度を計算
  $velocity = (WEIGHT*$velocity + v)/(WEIGHT+1).to_f  # 平均速度を計算し直す
  fude_width = FUDE_PARAM/$velocity                   # 筆の太さを決定する
  gc.set_line_attributes(fude_width,Gdk::GC::LINE_SOLID,Gdk::GC::CAP_ROUND,Gdk::GC::JOIN_BEVEL) # 筆の太さを設定する
  drawable.draw_line(gc, $x0, $y0, $x1, $y1)
  $x0 = evt.x
  $y0 = evt.y
end

window.signal_connect('button_press_event') do |win, evt| #マウスボタンが押された時
  $x0 = evt.x
  $y0 = evt.y
end

window.signal_connect('button_release_event') do |win, evt| #マウスボタンが離された時
  $v = INIT_FUDE_VELOCITY
end

window.signal_connect('destroy') do
  Gtk.main_quit
end

window.show_all

Gtk.main


参考
 gtk2-tut - Ruby-GNOME2 Project Website
 Ruby/GTK - Ruby-GNOME2 Project Website
 ウインドウへの直接描画(Gdk::Drawable編) - Ruby-GNOME2 Project Website
 [Ruby] Ruby-GNOME2(Ruby/GTK2) 線を引く
 [Ruby] Ruby-GNOME2(Ruby/GTK2) マウスのイベントを拾う


ちなみに、GtkのRubyバインディングは
$sudo gem install gtk2
でインストールできます。(Ruby1.9.3で確認)

最後にもう1枚。

0 件のコメント:

コメントを投稿