今まで、Cairo  + ruby/SDL で、モジュールみたいの作ろうとしてきた。
ここで又、中間報告で、載せます。
今回は、RMagick も、絡めました。
svgも使いたいので、rsvg2も入れました。
cairoは、rsvg2に含まれます。
色々と、たくさんの機能を詰め込むより、最低限にまとめてみました。
 Cairoの他の関数を使いたい時は、別にクラスを作って組み込みます。
この時は、 Phase#use を使います。
クラスの中に変数が欲しいときは、モジュールを extend して組み込みます。
このくらいで、いいような気がします。
後、キャプチャーも出来るようにしておきました。
環境はubuntu11.04
Fiberクラスを使っているので、rubyのバージョンは1.9です。

demo

載せたプログラムを起動すると、こんな感じになってます。
作ったクラスを、だいたい使ってます。
if __FILE__ == $0
end
で、囲まれた部分が本体です。


sdl, rsvg2, RMagickは、

sudo apt-get install libsdl-dev 
sudo apt-get install libsdl-image1.2-dev 
sudo apt-get install libsdl-mixer1.2-dev 
sudo gem1.9.1 install rubysdl 
sudo apt-get install imagemagick 
sudo apt-get install libmagick9-dev 
sudo gem1.9.1 install rmagick 
sudo apt-get install libcairo2-dev 
sudo gem1.9.1 install cairo
sudo apt-get install librsvg2-bin
sudo gem1.9.1 install rsvg2
 
で、入手します。 

#####################

require 'sdl'
require 'rsvg2'
require 'RMagick'

def cairo2magick(cr)
  magick = Magick::Image.new(cr.width, cr.height)
  magick.import_pixels(0,0, cr.width, cr.height, 'BGRA', cr.data)
end

def magick2cairo(mg)
  @pixels = mg.export_pixels_to_str(0, 0, mg.columns, mg.rows, 'BGRA') 
  Cairo::ImageSurface.new(@pixels, 0, mg.columns, mg.rows, @pixels.size / mg.rows) 
end

class Path < Cairo::Path
  def initialize
    super
    yield(self) if block_given?
  end

  def circle(*block)
    super
    self
  end

  def rectangle(*block)
    super
    self
  end

  def polyline(points)
    flag = true
    points.each do |x, y|
      if flag
        self.move_to(x, y)
      else
        self.line_to(x, y)
      end
      flag = false
    end
    self
  end

  def polygon(points)
    self.polyline(points)
    self.close
    self
  end
end

module Styling
  def styles(styles)
    styles.each do |style, value|
      self[style] = value
    end
    self[:matrix].origin(self[:x], self[:y])
    yield(self) if block_given?
    self
  end

  def x=(x)
    self[:x] = x
    self[:matrix].origin(self[:x], self[:y])
  end

  def y=(y)
    self[:y] = y
    self[:matrix].origin(self[:x], self[:y])
  end

  def matrix=(matrix)
    self[:matrix] = matrix
    self[:matrix].origin(self[:x], self[:y])
  end
end

module Matrixing
  def  scale(sx, sy=sx)
    self[:matrix] * Affine.new.scale(sx, sy)
    yield(self) if block_given?
    self
  end

  def rotate(degree)
    self[:matrix] * Affine.new.rotate(degree)
    yield(self) if block_given?
    self
  end
  
  def skewX(degree)
    self[:matrix] * Affine.new.skewX(degree)
    yield(self) if block_given?
    self
  end
  
  def skewY(degree)
    self[:matrix] * Affine.new.skewY(degree)
    yield(self) if block_given?
    self
  end

  def translate(tx, ty)
    self[:matrix] * Affine.new.translate(tx, ty)
    yield(self) if block_given?
    self
  end
end

class Sprite < Struct.new(:x, :y, :fill, :line_width, :stroke, :path, :matrix, :fiber)
  include Styling
  include Matrixing
  def initialize(path=Path.new)
    self[:x] = 0
    self[:y] = 0
    self[:fill] = nil
    self[:line_width] = 2.0
    self[:stroke] =nil
    self[:matrix] = Affine.new
    self[:path] = path
    self[:fiber] = nil
    yield(self) if block_given?
  end

  def path
    yield(self[:path]) if block_given?
    self[:path]
  end

  def path=(path)
    self[:path] = path
  end

  def draw(cr, matrix = Cairo::Matrix.identity)
    self[:fiber].resume if self[:fiber]
    cr.matrix = self[:matrix].transform_matrix * matrix
    cr.new_path
    cr.append_path(self[:path])
    if self[:stroke]
      cr.set_source_color(self[:stroke])
      cr.line_width = self[:line_width]
      cr.stroke(true)
    end
    if self[:fill]
      cr.set_source_color(self[:fill])
      cr.fill
    end
  end
end

class Image < Struct.new(:x, :y, :image, :alpha, :matrix, :fiber)
  include Styling
  include Matrixing
  def initialize(cairo_image=nil)
    self[:x] = 0
    self[:y] = 0
    self[:image] = cairo_image
    self[:alpha] = 1.0
    self[:matrix] = Affine.new
    self[:fiber] = nil
    yield(self) if block_given?
  end

  def image
    yield(self[:image]) if block_given?
    self[:image]
  end

  def image=(cairo_image)
    self[:image] = cairo_image
  end

  def draw(cr, matrix = Cairo::Matrix.identity)
    if self[:image]
      self[:fiber].resume if self[:fiber]
      cr.matrix = self[:matrix].transform_matrix * matrix
      cr.new_path
      cr.set_source(self[:image])
      cr.paint(self[:alpha])
    end
  end
end

class Text < Struct.new(:x, :y, :text, :size, :color, :align, :matrix, :fiber)
  include Styling
  include Matrixing
  def initialize(text="")
    self[:x] = 0
    self[:y] = 0
    self[:text] = text
    self[:size] = 20
    self[:color] = :black
    self[:align] = :left_top
    self[:matrix] = Affine.new
    self[:fiber] = nil
    yield(self) if block_given?
  end

  def draw(cr, matrix = Cairo::Matrix.identity)
    self[:fiber].resume if self[:fiber]
    cr.matrix = self[:matrix].transform_matrix * matrix
    cr.new_path
    cr.set_source_color(self[:color])
    cr.font_size = self[:size] if self[:size]
    if self[:align] != :none
      ext = cr.text_extents(self[:text])
      case self[:align]
      when :left_top
        cr.move_to(-ext.x_bearing, ext.height)
      when :top_right_top
        cr.move_to(-ext.x_bearing - ext.width, ext.height)
      when :left_bottom
        cr.move_to(-ext.x_bearing, 0)
      when :right_bottom
        cr.move_to(-ext.x_bearing - ext.width, 0)
      when :center
        cr.move_to((ext.x_bearing + ext.width) / -2, ext.height / 2)
      end
    end
    cr.show_text(self[:text])
  end
end

class Svg < Struct.new(:x, :y, :svg, :matrix , :fiber)
  include Styling
  include Matrixing
  def initialize(svg=nil)
    self[:x] = 0
    self[:y] = 0
    self[:svg] = nil
    self.svg = svg
    self[:matrix] = Affine.new
    self[:fiber] = nil
    yield(self) if block_given?
  end
  
  def svg=(svg)
    self[:svg] = RSVG::Handle.new_from_data(svg) if svg
  end
  
  def draw(cr, matrix = Cairo::Matrix.identity)
    self[:fiber].resume if self[:fiber]
    if self[:svg]
      cr.matrix = self[:matrix].transform_matrix * matrix
      cr.render_rsvg_handle(self[:svg])
    end
  end
end

class Use < Struct.new(:x, :y, :matrix ,:fiber)
  include Styling
  include Matrixing
  def initialize(content)
    @content = content
    self[:x] = 0
    self[:y] = 0
    self[:matrix] = Affine.new
    self[:fiber] = nil
    yield(self) if block_given?
  end

  def draw(cr, matrix = Cairo::Matrix.identity)
    self[:fiber].resume if self[:fiber]
    cr.matrix = self[:matrix].transform_matrix * matrix
    @content.draw(cr)
  end
end

module AddContent
  def sprite(path=Path.new)
    @contents << Sprite.new(path)
    yield(@contents.last) if block_given?
    @contents.last
  end

  def image(cairo_image=nil)
    @contents << Image.new(cairo_image)
    yield(@contents.last) if block_given?
    @contents.last
  end

  def text(text="")
    @contents << Text.new(text)
    yield(@contents.last) if block_given?
    @contents.last
  end

  def group
    @contents << Group.new
    yield(@contents.last) if block_given?
    @contents.last
  end
  
  def svg(svg=nil)
    @contents << Svg.new(svg)
    yield(@contents.last) if block_given?
    @contents.last
  end

  def use(content)
    @contents << Use.new(content)
    yield(@contents.last) if block_given?
    @contents.last
  end
end

class Group < Struct.new(:x, :y, :matrix, :fiber)
  include Styling
  include Matrixing
  include AddContent
  def initialize
    self[:x] = 0
    self[:y] = 0
    self[:matrix] = Affine.new
    self[:fiber] = nil
    @contents = []
    yield(self) if block_given?
  end

  def draw(cr, matrix = Cairo::Matrix.identity)
    self[:fiber].resume if self[:fiber]
    @contents.each do |obj|
      obj.draw(cr, self[:matrix].transform_matrix * matrix)
    end
  end
end

class Phase < Struct.new(:background, :handles, :fiber, :task)
  include AddContent
  def initialize(w, h)
    self[:background] = nil
    self[:handles] = {}
    self[:fiber] = nil
    self[:task] = nil
    @w, @h = w, h
    @image = Cairo::ImageSurface.new(w, h)
    @cr = Cairo::Context.new(@image)
    @contents = []
    self[:handles][SDL::Event::Quit] = proc{self.quit}
    self[:handles][SDL::Event::KeyDown] = proc{|e| self.quit if e.sym == SDL::Key::ESCAPE}
    yield(self) if block_given?
  end

  def styles(styles)
    styles.each do |style, value|
      self[style] = value
    end
    self.background = self[:background] if self[:background]
    yield(self) if block_given?
    self
  end

  def background=(color)
    self[:background] = color
    @contents.unshift Sprite.new.styles(
      :path => Path.new.rectangle(0, 0, @w, @h),
      :fill => color
      )
  end

  def handling(event)
    if handle = self[:handles][event.class]
      handle[e]
    end
  end
  
  def quit
    throw(:quit)
  end

  def draw(flag=true)
    self[:task][] if self[:task]
    if flag
      @contents.each {|obj| obj.draw(@cr)}
      @image
    else
      nil
    end
  end
end

class Screen < Struct.new(:w, :h, :flags, :fps, :phase, :caption, :icon, :capture)
  def initialize(w=nil, h=nil)
    self[:w] = w
    self[:h] = h
    self[:flags] = SDL::INIT_VIDEO
    self[:fps] = 30
    self[:phase] = nil
    self[:caption] = ""
    self[:icon] = ""
    self[:capture]= nil
    if block_given?
      yield(self)
      self.run
    end
  end

  def styles(styles)
    styles.each do |style, value|
      self[style] = value
    end
    if block_given?
      yield(self)
      self.run if self[:phase]
    end
    self
  end

  def phase
    self[:phase] = Phase.new(self[:w], self[:h]) if self[:phase].nil?
    yield(self[:phase]) if block_given?
    self[:phase]
  end

  def phase=(phase)
    self[:phase] = phase
  end

  def run
    SDL.init(self[:flags])
    SDL::WM.set_caption(self[:caption], self[:icon])
    screen = SDL::Screen.open(self[:w], self[:h], 32, 0)
    fps = Fps.new(self[:fps])
    catch(:quit) do
      loop do
        while event = SDL::Event.poll
          if handle = self[:phase].handles[event.class]
            handle[event]
          end
        end
        if image = self[:phase].draw(fps.wait)
          self[:capture][image] if self[:capture]
          surface = SDL::Surface.new_from(
            image.data, self[:w], self[:h], 32, image.stride, 0, 0, 0, 0)
          screen.put(surface,0,0)
          screen.flip
        end
      end
    end
  end
end

class Capture < Struct.new(:w, :h, :phase, :file_name, :loop_size, :delay)
  def initialize(w=nil, h=nil)
    self[:w] = w
    self[:h] = h
    self[:phase] = nil
    self[:file_name] = nil
    self[:loop_size] = 1
    self[:delay] = 30
    @imageList = Magick::ImageList.new
    if block_given?
      yield(self)
      self.run
    end
  end

  def styles(styles)
    styles.each do |style, value|
      self[style] = value
    end
    if block_given?
      yield(self)
      self.run if self[:phase]
    end
    self
  end

  def write(file_name, loop_size=1)
    self[:file_name] = file_name
    self[:loop_size] =loop_size
    if block_given?
      yield(self)
      self.run if self[:phase]
    end
    self
  end

  def phase
    self[:phase] = Phase.new(self[:w], self[:h]) if self[:phase].nil?
    yield(self[:phase]) if block_given?
    self[:phase]
  end

  def phase=(phase)
    self[:phase] = phase
  end

  def run
    self[:loop_size].times do
      @imageList << cairo2magick(self[:phase].draw)
    end
    @imageList.delay = self[:delay]
    @imageList.write(self[:file_name]) if self[:file_name]
  end
end

class Fps
  attr_reader :fps
  def initialize(fps = 30)
    @fps = fps
    @note = SDL.getTicks
    @wt = 1000 / @fps
  end

  def wait
    ret = false
    while SDL.getTicks - @note < @wt
      sleep(0.001)
      ret = true
    end
    @note += @wt
    ret
  end
end

class Affine
  def initialize(ox=0, oy=0)
    @ox, @oy = ox, oy
    @matrix = Cairo::Matrix.identity
  end

  def origin(ox, oy)
    @ox, @oy = ox, oy
    self
  end
  
  def matrix(xx, yx, xy, yy, x0, y0)
    @matrix = Cairo::Matrix.new(xx, yx, xy, yy, x0, y0)
    self
  end

  def scale(sx, sy=sx)
    @matrix = Cairo::Matrix.scale(sx, sy)
    self
  end
  
  def rotate(degree)
    radian = -degree * Math::PI / 180
    @matrix = Cairo::Matrix.rotate(radian)
    self
  end

  def skewX(degree)
    radian = degree * Math::PI / 180
    @matrix = Cairo::Matrix.new(1, 0, Math::tan(radian), 1, 0, 0)
    self
  end
  
  def skewY(degree)
    radian = -degree * Math::PI / 180
    @matrix = Cairo::Matrix.new(1, Math::tan(radian), 0, 1, 0, 0)
    self
  end

  def translate(tx, ty)
    @matrix = Cairo::Matrix.translate(tx, ty)
    self
  end

  def multiply(other)
    @matrix = @matrix.multiply(other.transform_matrix)
    self
  end

  alias * multiply

  def transform_matrix
    @matrix * Cairo::Matrix.translate(@ox, @oy)
  end

  def transform_point(x, y)
    ix, iy = x - @ox, y - @oy
    x,  y = @matrix.transform_point(ix, iy)
    [x + @ox, y + @oy]
  end
end

if __FILE__ == $0

  module SpriteInfo
    attr_accessor :rx,:ry,:dx,:dy
  end

  class AnotherSprite
    def initialize
      url = "http://www.imagemagick.org/image/logo.jpg"
      magick = Magick::ImageList.new
      IO.popen("wget -q -O - %s" % url) do |io|
        magick.from_blob(io.read)
      end
      @pattern = Cairo::SurfacePattern.new(magick2cairo(magick))
      @pattern.matrix = Cairo::Matrix.translate(80,50)
    end

    def draw(cr)
      cr.new_path
      cr.circle(0,0,35)
      cr.set_source_color(:black)
      cr.line_width = 5
      cr.stroke(true)
      cr.set_source(@pattern)
      cr.fill
    end
  end

  magick = Magick::ImageList.new
  max = 50
  Screen.new.styles(:w=>350, :h=>250, :caption=>"Demo") do |scn|
    scn.phase.styles(:background => :gray) do |ph|
      ph.sprite.styles(:x=>150, :y=>180, :fill=>:green, :path=>Path.new.circle(0, 0, 30)).
        scale(2, 1).rotate(-45)
      url = "http://cairographics.org/cairo-banner.png"
      IO.popen("wget -q -O - %s" % url) do |io|
        ph.image(Cairo::ImageSurface.from_png(io)).styles(:x=>40, :y=>10)
      end
      ph.group.styles(:x=>50) do |g|
        g.svg.skewX(-45) do |svg|
          svg.svg = <<EOF
<svg width="320" height="30" >
<text x="200" y="160" fill="red" font-size="40px">SVG</text>
</svg>
EOF
        end
        g.text("Ruby/SDL").styles(:x=>200, :y=>150, :align=>:center) do |sp|
          sp.fiber = Fiber.new do
            loop do
              30.times {Fiber.yield}
              sp.matrix * Affine.new.rotate(15)
              Fiber.yield
            end
          end
        end
        g.fiber = Fiber.new do
          loop do
            30.times {Fiber.yield}
            g.x += 10
            g.x = 0 if g.x > 101
            Fiber.yield
          end
        end
      end
      ph.sprite.styles(:x=>35, :y=>150).skewY(45) do |sp|
        color = Cairo::Color.parse(:teal)
        color.alpha = 0.6
        sp.fill = color
        sp.path.rectangle(0, 0, 50, 50)
      end
      ph.use(AnotherSprite.new).styles(:x=>260, :y=>180)
      a= []
      r = 25
      (Math::PI / 2..Math::PI * 9 / 2).step(Math::PI * 4 / 5) do |theta|
        a << [r * Math::cos(theta), -r * Math::sin(theta)]
      end
      ph.sprite.styles(:x=>150, :y=>180, :fill=>:blue, :path=>Path.new.polyline(a))
      r=10
      ball = ph.sprite do |sp|
        sp.extend(SpriteInfo)
        sp.x, sp.y = scn.w / 2, scn.h / 2
        sp.matrix.skewX(-10)
        sp.rx = Range.new(r, scn.w - r)
        sp.ry = Range.new(r, scn.h - r)
        sp.dx = rand(5) + 2
        sp.dy = rand(5) + 2
        sp.path.circle(0, 0, r)
        sp.fill = :red
        sp.line_width = 5
        sp.stroke = :blue
      end
      ph.task = Proc.new do
        ball.dx *=  -1 if !ball.rx.include?(ball.x + ball.dx)
        ball.dy *=  -1 if !ball.ry.include?(ball.y + ball.dy)
        ball.x += ball.dx
        ball.y += ball.dy
      end
      i = 0
      scn.capture = proc do |img|
        if magick.size < max
          magick << cairo2magick(img) if i % 20 == 0
        else
          scn.capture = nil
        end
        i += 1
      end
    end
  end
  SDL.quit
  magick.delay = 20
  magick.write("/tmp/demo.gif")
  #`eog -n /tmp/demo.gif`
end