Ruby で GTK+2 グラフィック

GTK+ といっても、GUIを作りたいのではないのです(そのうち GUI も作りたくなるかも)。ではなくて、簡単に線を引いたり円を描いたりしたい。Green Shoes でやればいい? Ruby/Tk でやればいい? いや、Linux だったらやはり GTK+ でしょう。GTK+2 でやりたい。

Linux ならば多分 GTK+2 は入っている筈。なければ「ソフトウェアの管理」(Linux Mint の場合)などからインストールする。

Ruby としては、とりあえず Gem 'gtk2' を入れる。ついでに 'gtk3' も入れておくとよい。
リファレンスは下のリンクを。しかしあまり日本語に翻訳されていないので、サイトを wget で全ダウンロードして、必要な部分だけ HTML を勝手に(翻訳して)書き換えてローカルで参照する。
Ruby-GNOME2 Project Website - Ruby-GNOME2 Project Website


試行錯誤しまくって、ようやく簡単なお絵かき成功。やっとですよ。

コードは以下。

require 'bundler/setup'
require 'gtk2'

w = Gtk::Window.new
w.set_size_request(300, 300)
w.set_app_paintable(true)
w.realize

drawable = w.window
gc = Gdk::GC.new(drawable)
colormap = Gdk::Colormap.system

white = Gdk::Color.new(65535, 65535, 65535)
red   = Gdk::Color.new(65535, 0, 0)
colormap.alloc_color(white, false, true)
colormap.alloc_color(red  , false, true)

w.signal_connect("expose_event") do
  gc.set_foreground(white)
  drawable.draw_rectangle(gc, true, 0, 0, 300, 300)
  
  gc.set_foreground(red)
  drawable.draw_arc(gc, true, 0, 0, 300, 300, 0, 64 * 360)
end

w.show
Gtk.main

注意しておくと、お絵かきで色を使うには、colormap に色を登録しておかないといけないようです。上だと colormap.alloc_color(white, false, true) などのところですね。それから、ウィンドウを paintable にしておかないとお絵かきできません。これは上だと w.set_app_paintable(true) のところですね。

下などが参考になります。
ウインドウへの直接描画(2) - Gdk::Drawable - Ruby-GNOME2 Project Website
カラーの扱い - Ruby-GNOME2 Project Website


上のブロックの部分だけ変えます。すると中心から円が拡大していき、画面の縁に達したところで停止します。

w.signal_connect("expose_event") do
  gc.set_foreground(white)
  drawable.draw_rectangle(gc, true, 0, 0, 300, 300)
  
  r = 1
  id = Gtk.timeout_add(80) do
    gc.set_foreground(red)
    drawable.draw_arc(gc, true, 150 - r, 150 - r, r * 2, r * 2, 0, 64 * 360)
    Gtk.timeout_remove(id) if r > 150
    r += 1
  end
end

キモは Gtk.timeout_add(80) のブロックで、80ミリ秒毎にブロックの中を実行します。

モジュール化

汎用部分をモジュール化してみました。
mygtk.rb

require 'bundler/setup'
require 'gtk2'

module MyGtk
  W = Gtk::Window.new
  class Tool
    def initialize
      @drawable = W.window
      @gc = Gdk::GC.new(@drawable)
      @colormap = Gdk::Colormap.system
      @color = Gdk::Color.new(0, 0, 0)
    end
    
    def color(r, g, b)
      @color = Gdk::Color.new(r, g, b)
      @colormap.alloc_color(@color, false, true)
      @color
    end
    
    def rectangle(x, y, width, height, color = nil)
      set_color(color)
      @drawable.draw_rectangle(@gc, true, x, y, width, height)
    end
    
    def arc(x, y, width, height, d1, d2, color = nil)
      set_color(color)
      @drawable.draw_arc(@gc, true, x, y, width, height, d1, d2)
    end
    
    def set_color(color)
      @color = color if color
      @gc.set_foreground(@color)
    end
  end
  
  def self.app(width: 300, height: 300, &bk)
    W.set_size_request(width, height)
    W.set_app_paintable(true)
    W.realize
    
    W.signal_connect("expose_event") {Tool.new.instance_eval(&bk)}
    
    W.show
    Gtk.main
  end
end

 
上の画像はこれだけで描画できます。

require './mygtk'

MyGtk.app width: 300, height: 300 do
  white = color(65535, 65535, 65535)
  red   = color(65535, 0, 0)
  
  rectangle(0, 0, 300, 300, white)
  arc(0, 0, 300, 300, 0, 64 * 360, red)
end

 
円が拡大していくバージョンは下。

MyGtk.app width:300, height: 300 do
  white = color(65535, 65535, 65535)
  red   = color(65535, 0, 0)
  
  rectangle(0, 0, 300, 300, white)
  
  r = 1
  id = Gtk.timeout_add(80) do
    arc(150 - r, 150 - r, r * 2, r * 2, 0, 64 * 360, red)
    Gtk.timeout_remove(id) if r >= 150
    r += 1
  end
end

追記

これは結局ささやかな Gem にまで発展しました。以下を参照。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura