RMagick/ImageMagickで位置決めをする Gravityの挙動まとめ

RubyからImageMagickを呼び出すRMagickを使って,センタリングした注釈を入れようとしたのですが,センタリングがうまく行かず,混乱に陥ったので備忘録としてまとめておきます.

やろうとしたことは,横400,縦25の画像で,(200,0)に文字列を描くと,

となるので(青い線は丁度真ん中の所),

としたいという事.もちろん,描く文字列に可能な限り依存したくない.
センタリングには,オブジェクトの配置を指定するGravityを利用して,gravityをCenterGravityにすれば良いかと思い,

gc = Magick::Draw.new
gc.gravity(Magick::CenterGravity)
gc.text(200, 0, "centering")

としました.結果は

と,右端に書かれてしまいました.

誤っていた点は,Gravityが,文字の場所だけでなく,座標系(座標の向きと原点)も変化させる点です.
予め正しくセンタリングされた図(2番目に示した図)のソース(関連部分)を示すと,

gc = Magick::Draw.new
gc.gravity(Magick::NorthGravity)
gc.text(0, 0, "center")

と,GravityがNorthGravity.そして,座標は(0,0)になります.

以下,詳細な説明.
例えば,デフォルトのNorthWestGravityでは,
左上が0,0で,x軸が左から右方向に取られ,y軸が上から下方向に取られています(普通の画像の座標系).次の図は,(0,0)の位置にGravity名(NorthWestGravity).(20,180),(180,80)などの座標は,その座標から文字列を開始した場合に文字列が書かれる場所,つまり

gc = Magick::Draw.new
gc.text(20, 180, "(20,180)")

として文字が書かれた場所を示しています(背景の升目は,10ずつ引かれています).
NorthWestGravityでは,文字列は指定した点((20,180)など)を文字列の左上の点として描かれます.

一方,GravityをCenterGravityにすると,x軸,y軸の向きに変更はありませんが,画像の中心が原点になります.更に,文字列は,x軸方向もy軸方向も,センタリングされます(文字列を描くよう指定した点が文字列の中心になります).

また,GravityをSouthEastGravityにすると,x軸が画像の右から左に,y軸が下から上にとられます.原点も,画像の右下になります.更に,文字列は,指定した点が描画する文字列の右下になります.

このように,座標の向きも原点も変化するので,注意が必要です.

以下,全Gravityのまとめ.
Gravityには,9種類あります.(赤文字は気をつける場所)

  • NorthWestGravity
    • 座標系は,x軸が右方向,y軸が下方向,原点は画像左上.

  • NorthGravity
    • 座標系は,x軸が右方向,y軸が下方向,原点は画像中央上.

  • NorthEastGravity
    • 座標系は,x軸が左方向,y軸が下方向,原点は画像右上.

  • WestGravity
    • 座標系は,x軸が右方向,y軸が下方向,原点は画像左中央.

  • CenterGravity
    • 座標系は,x軸が右方向,y軸が下方向,原点は画像真ん中.

  • EastGravity
    • 座標系は,x軸が左方向,y軸が下方向,原点は右中央.

  • SouthWestGravity
    • 座標系は,x軸が右方向,y軸が上方向,原点は画像左下.

  • SouthGravity
    • 座標系は,x軸が右方向,y軸が上方向,原点は画像中央下.

  • SouthEastGravity
    • 座標系は,x軸が左方向y軸が上方向,原点は画像右下.

以上.

下のスクリプトは,以上の画像を描いたスクリプトです.RMagickのマニュアルのサンプルプログラム( http://www.simplesystems.org/RMagick/doc/draw.html#gravity )を改変しました.RMagickをrubygemsを使ってインストールしたので,rubygemsをrequireしています.gems経由ではなくインストールした方は,この1行は要らないでしょう.

#!/usr/bin/env ruby

require 'rubygems'
require 'RMagick'

gravities =  [Magick::NorthWestGravity,Magick::NorthGravity,Magick::NorthEastGravity,
   Magick::WestGravity,Magick::CenterGravity,Magick::EastGravity,
   Magick::SouthWestGravity,Magick::SouthGravity,Magick::SouthEastGravity]

gravities.each do |gravity|
  puts gravity
  imgl = Magick::ImageList.new
  imgl.new_image(400,200, Magick::HatchFill.new('white', 'lightcyan2'))

  gc = Magick::Draw.new

  # Draw blue lines to indicate positioning
  gc.stroke('blue')
  gc.fill('transparent')
  gc.rectangle(20,20, 380,180)
  gc.line(200,20, 200,180)
  gc.line(20,100, 380,100)

  # Draw compass points.
  gc.gravity(gravity)
  gc.stroke('transparent')
  gc.fill('black')
  gc.text( 0, 0, gravity.to_s)
  
  [-180,0,180].each do |x|
    [-180, -100, -20, 20,100,180].each do |y|
      gc.text(x, y, "(#{x},#{y})")
    end
  end

  gc.draw(imgl)
  imgl.border!(1,1, "lightcyan2")
  imgl.write("grav_" + gravity.to_s + ".gif")
end