7839

雑草魂エンジニアブログ

【Ruby】cairoで画像作成を実装する(画像にスタンプと文字を追加する)

Railsアプリケーションにおいて、バックエンド側で以下のような画像作成をしたいと思い、サンプルアプリケーションを作ってみたので備忘録として残しておく。

ラジオ体操のスタンプみたいw

Ruby/Railsでどのライブラリを使うか

画像編集をしたいと思った場合、以下のライブラリが見つかった。

画像編集ソフトであるImageMagickは本当に色々な編集が可能である。今回の既存の画像にスタンプを追加するぐらいであれば、ベクター画像処理で十分であると判断し、rcairoを選定することとした。

cairoとは

cairo は 2 次元ベクター画像を描画するためのライブラリである。

cairographics.org

使い方としては、SVG(Scalable Vector Graphics)によく似ている感覚であった。(ベクター形式なので当たり前かw)

Docker設定

今回、Rails環境をDocker(Alpine Linux)で構築していたため、cairoをDocker imageにインストールすることにした。

cairo-devが用意されていたので、その他必要なパッケージも含め以下の通りインストールを行なった。

RUN apk add --update --no-cache \
  make \
  g++ \
  jpeg-dev \
  cairo-dev \
  giflib-dev \
  pango-dev \
  libtool \
  autoconf \
  automake \
  font-noto-cjk

文字の描画で日本語フォントが必要だったため、「font-noto-cjk」を追加した。

Rails設定

Ruby で cairo を使うには rcairo を使う必要がある。rcairo は Ruby と cairo のインターフェースである。

gem 'cairo'

Gemfileに上記を追記して、bundle installを行う。

今回、画像作成処理に関してはRakeタスクで実行することを想定した。

$ bundle exec rails g task calender_image

lib/tasks配下にcalender_image.rakeというファイルが生成される。

画像作成(描画処理)

スタンプを追加する下地の画像は一時的に以下のディレクトリに入れておいた。

lib/
└── tasks/
    ├── calender_image.rake
    └── img/
        └── calender_Aug.png

実際に作成したコードは以下の通りである。今回は、カレンダー画像の8/1の部分に星形のスタンプを追加し、カレンダーの下部にメッセージを追加した。

namespace :calender_image do

  desc "create calender image"
  task :create do
    open("#{Rails.root}/lib/tasks/img/calender-add-stamp.png", 'wb') do |output|
      open("#{Rails.root}/lib/tasks/img/calender_Aug.png", 'rb') do |input|
        new_image = draw_image(input, "継続は力なり。今年の夏も成長できる、ステキな夏になりますように。")
        output.write(new_image.read)
      end
    end
  end

end

def draw_image(io, message)

  surface = Cairo::ImageSurface.from_png(io)
  context = Cairo::Context.new(surface)

  # 星の座標
  star_left_top_x = 187
  star_left_top_y = 257

  context.set_source_color(Cairo::Color.parse("#FFC700")) # 星の色設定
  context.set_line_width(5) # 星の線幅設定

  star_points = [
    [ 54.32, 0 ],
    [ 71.09, 34 ],
    [ 108.63, 39.46 ],
    [ 81.47, 65.92 ],
    [ 87.89, 103.3 ],
    [ 54.32, 85.67 ],
    [ 20.74, 103.32 ],
    [ 27.16, 65.94 ],
    [ 0, 39.46 ],
    [ 37.54, 34 ],
    [ 54.32, 0 ]
  ]

  # 星の描画処理
  context.move_to(star_left_top_x + star_points[0][0], star_left_top_y)
  for i in 0..10 do
    context.line_to(star_points[i][0] + star_left_top_x, star_points[i][1] + star_left_top_y)
  end

  context.stroke

  context.set_source_color(Cairo::Color.parse("#000000"))
  context.select_font_face("Noto Sans CJK JP", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
  context.set_font_size(26)
  context.move_to(40,810)
  context.show_text("今日の一言")
  context.select_font_face("Noto Sans CJK JP", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD)
  context.move_to(70,860)
  context.show_text("#{message}")

  io = StringIO.new
  surface.write_to_png(io)
  io.pos = 0
  io
end

以下のコマンドを実行することで、Rakeタスクを動かすことができ、実際に画像作成を行うことができる。

$ bundle exec  rails calender_image:create 

rcairoの各種メソッドは、rcairoのGitHubのDocを参考にしてほしい。(日本語の説明もあって感謝しかない。)

画像描画の流れは以下の通りである。

  1. サーフェス (Cairo::Surface) を作成する
  2. 作成したサーフェス用のコンテキスト (Cairo::Context) を作成する
  3. コンテキストに対して描画処理を行う
    • 星の描画
    • 文字の追加

(注意事項)

  • 座標系は、左上を(0,0)とする
  • コンテキストについても、座標系は左上が原点(0,0)となる

まとめ

cairoを使って画像作成処理を簡単に実装することができた。

あとは、スタンプの位置を日付ごとにどの位置とすべきか算出するロジックを追加することで、任意の日付にスタンプを追加することができる予定である。

参考資料