#374 Image Manipulation
- Download:
- source codeProject Files in Zip (54.2 KB)
- mp4Full Size H.264 Video (30.4 MB)
- m4vSmaller H.264 Video (14.5 MB)
- webmFull Size VP8 Video (15.3 MB)
- ogvFull Size Theora Video (30.3 MB)
3年前にRyan BatesがDaily Stampというアプリケーションを書きました。毎日必ず行なうことを決めてこのアプリケーションに入力すると、カレンダーとスタンプが表示され、指定したタスクを実行した日にスタンプを押すことができます。
今回のエピソードでは、このスタンプをカスタマイズする機能にフォーカスして説明をおこないます。このアプリケーションでは、事前に設定されたスタンプの一覧から選択するか、好きな画像をアップロードしてカスタムのスタンプを作ることができます。アップロードされた画像は自動的にサイズ調整されてエフェクトがかけられ、ゴム印のようになります。スタンプの色を選ぶこともできます。今回のエピソードで、これらの画像操作の方法を紹介します。キーとなる要素はImageMagickです。使える状態にするまでが難しいので、多くの人がこのツールに対して愛憎入り交じった思いを持っています。ですがひとたび設定がうまくいけば、とても強力なツールになります。画像操作をおこないたいのであれば、このソフトウェアを利用することでかなりのことが可能になります。まず最初にコマンドラインツールについて説明します。これを理解することで、およそImageMagickがどのようなことをできるのか、また画像を操作するときにどのようなステップが必要なのかがわかるようになります。
ImageMagickのインストールはややこしくなりがちです。Mac OS Xを使用しているのであれば、Homebrewを利用するのがいいでしょう。その場合は、brew install imagemagick
のコマンドを実行するだけです。インストール作業が完了したら、convertなど画像処理のためのコマンドが使えるようになります。
画像の元ファイル(GithubのマスコットOctocatのPNGファイル)に加えて、その画像に重ねるためのテクスチャで、ゴム印のように見える効果をつけた半透明の空白の画像もあります。最初の目標は、元の画像をリサイズして切り取って、重ねるテクスチャとうまく合うようにすることです。convert
コマンドのドキュメントを見ると、渡すことができるオプションのリストがあり、そこに画像の切り取りとリサイズのオプションも含まれています。いずれのオプションも位置情報のパラメータをとり、ドキュメントには引数を指定して振る舞いを変えるための複数の方法が記述されています。これは、今回のように形が整っていない画像から正方形のサムネイルを作成するときに便利です。オプションにcaretシンボルを付けて、アスペクト比を保ったままスタンプ画像の隙間を埋めます。まずはターミナルで、画像を70x70ピクセルにリサイズします。同じサイズに切り取ってオフセット値を与え、ファイルをsource.png
という名称で保存します。
$ convert octocat.png -resize '70x70^' -crop '70x70+0+0' source.png
結果の画像はこのようになりました。サイズは合っていますが、中心からずれてしまっています。これを中心から切り取るようにしなければいけません。そのためには、cropの前にgravityオプションを指定します。centerを指定することで、切り取り時に画像が中心に保たれます。
$ convert octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' source.png
これで画像を正しく切り取ることができるので、どんな画像もサムネイルに変換するコマンドができました。このサムネイルをグレイスケールに変換し、スタンプ画像に変換しやすいようにコントラストを強くします。そのためにいくつかのオプションを追加する必要があります。quantize
はカラーパレットをグレイスケールに設定します。colors
で変換をおこない、contrast
で画像をもう少しpopにします。quantize
に渡すオプションは大文字にしなくてはいけないので注意してください。
$ convert octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast source.png
グレイスケールの画像ができたので、その上に重ねる画像を合成してスタンプにテクスチャをつけます。composite
コマンドを使って、2つの画像を重ね合わせます。重ねる画像を元画像の上に合成して、元画像を新しい画像で上書きします。
$ composite stamp_overlay.png source.png source.png
画像にスタンプのテクスチャが重ね合わされたので、次に色を加えます。convert
コマンドは、すでにある画像の処理に使えるだけでなく、画像を新規に作る場合にも使えます。新規に70x70ピクセルの画像を作成し、色を赤にします。ここではcanvas
オプションを使いますが、ImageMagickのバージョンによっては代わりにcx
オプションを使うので注意してください。
$ convert -size 70x70 canvas:red colour.png
これによって正方形の赤の画像が生成されるので、元画像と合成します。別のファイルを処理する代わりに、同じconvertコマンドで合成処理をおこなえます。元画像を読み込んで、それをもう一つの画像とどう合成するかを指定します。copy-opacity
の透過設定を使って、元画像を赤いキャンバスとを合成させます。
$ convert -size 70x70 canvas:red source.png -compose copy-opacity -composite stamp.png
生成された画像は求めているものに近づきましたが、期待する結果を得るためには元画像を反転させる必要があります。そのためには、画像をロードするときにカッコでnegateオプションを渡して色を反転させます。
convert -size 70x70 canvas:red \( source.png -negate \) -compose copy-opacity -composite stamp.png
生成された画像は、求めている通りのものに見えます。別のスタンプを作るために色を変更するコマンドを設定するのは簡単でしょう。これについてもわかったので、全体を一つのコマンドにまとめて、一度で画像のサイズを変更してスタンプを作成できるようにします。このコマンドは長くて複雑に見えますが、期待通りの処理をしてくれます。
convert -size 70x70 canvas:red \( octocat.png -resize '70x70^' -gravity center -crop '70x70+0+0' -quantize GRAY -colors 256 -contrast stamp_overlay.png -composite -negate \) -compose copy-opacity -composite stamp.png
Railsアプリケーションに適用する
次にこれをRailsアプリケーションで動くようにします。一つのやり方は、このコマンドをRuby内で実行するという方法で、これも悪くはありません。この目的のためのgemもいくつか存在します。Image Sorceryは、軽量のコマンドラインラッパーの一つで、MiniMagickも似た機能を提供します。これらのgemのインタフェースは複雑な画像操作をおこなう場合には少し扱いにくいところもあるので、コマンド全体を一つにまとめて実行するのが結局は一番いい方法であるという場合が多いです。定番のRMagick gemも使えます。最近はあまりアップデートされておらず、メモリーリークを批判されることもありますが、コマンドラインインタフェースの代替としては有効な選択肢です。Cライブラリをラップしているので、インタフェースがコマンドラインと常に1対1で対応するわけではないですが、わかりやすいユーザガイドが提供されています。同じスタンプ画像を作成しながら、RMagickを使ったやり方を具体的に見ていきましょう。まずgemをインストールします。
$ gem install rmagick
単純なRubyスクリプトで画像処理ができるように設定してから、それをRailsアプリケーション内に移動させます。スクリプトは以下のとおりです。
require "rmagick" source = Magick::Image.read("octocat.png").first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) overlay = Magick::Image.read("stamp_overlay.png").first source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = "red" } colored.composite!(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) colored.write("stamp.png")
まずrmagick
をrequireし、元の画像をロードします。画像を読み込むと、複数レイヤーの画像の配列ができるので、その内の最初の画像を選択します。次にresize_to_fill
メソッドを使って画像を切り取ってリサイズし、返されたサムネール画像に対してquantize
を呼び出し、グレースケールに変換します。contrast
を呼び出してtrue
を渡すと、画像のコントラストが上がります。
次に重ねる画像を合成します。メイン画像と同じようにロードして、元画像に対してcomposite!
を呼び出して重ねる画像を合成します。次に赤の背景で新規画像を作成し、色を反転させた元画像に合成します。引数のMagick::CopyCapacityCompositeOp
が、コマンドラインで使用した-compose copy-opacity
オプションに対応することに注意してください。最後に画像を書き出します。
ではこれを試してみましょう。スクリプトを実行すると、返された画像はコマンドラインで生成したものと同じに見えるので、スクリプトは正しく動作しているようです。比較のために、Rubyでこのスクリプトを生成するための他のアプローチも掲載しておきます。
# Or through the command line: system <<-COMMAND convert -size 70x70 canvas:red \\( octocat.png \ -resize '70x70^' -gravity center -crop '70x70+0+0' \ -quantize GRAY -colors 256 -contrast stamp_overlay.png \ -composite -negate \ \\) -compose copy-opacity -composite stamp.png COMMAND
この方法の場合、ユーザからパラメータを受け付けるのであれば、shell injection攻撃に留意する必要があります。RMagickを使えば詳細に指定できるかも知れませんが、それは同時により動的な処理なので、潜在的なメモリリークの可能性に注意する必要があります。
アップロードされた画像がどう変換されるかを示す、簡単なRailsアプリケーションをすでに作成済みです。このアプリケーションには、アップロードされた画像をスタンプに変換するファイルアップロード用のダイアログを持ったページがあります。ファイルアップロードの処理のために、このサンプルではCarrierWaveに加えてRMagickを利用しています。(CarrierWaveについてはエピソード253で解説しています。)
gem 'rmagick' gem 'carrierwave'
Stamp
モデルは、StampUploader
という名前のCarrierWaveのアップローダをmountします。
class Stamp < ActiveRecord::Base attr_accessible :image mount_uploader :image, StampUploader end
StampUploader
クラスは、画像処理部分のコードを除いては標準的なものです。
# encoding: utf-8 class StampUploader < CarrierWave::Uploader::Base include CarrierWave::RMagick include Sprockets::Helpers::RailsHelper include Sprockets::Helpers::IsolatedHelper storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end def extension_white_list %w(jpg jpeg gif png) end %w[red green blue purple black].each do |color| version(color) { process stamp: color } end def stamp(color) manipulate! format: "png" do |source| overlay_path = Rails.root.join("app/assets/images/stamp_overlay.png") overlay = Magick::Image.read(overlay_path).first source = source.resize_to_fill(70, 70).quantize(256, Magick::GRAYColorspace).contrast(true) source.composite!(overlay, 0, 0, Magick::OverCompositeOp) colored = Magick::Image.new(70, 70) { self.background_color = color } colored.composite(source.negate, 0, 0, Magick::CopyOpacityCompositeOp) end end end
画像処理はstamp
メソッドでおこなわれます。複数バージョンのスタンプを作成するのに必要な処理はこれだけです。おこなっている処理としては、色のリストをループしてそれぞれの版を作成しています。stampメソッドがカスタムCarrierWaveメソッドのmanipulate!
を呼び出して、PNG画像の生成にRMagickを使うことを指示します。このメソッドはブロックをとり、そこに元画像を渡します。ブロックのコードは修正された画像を返し、基本的には以前使用した同じコードにいくつか変数を設定しただけのものです。本番環境用のアプリケーションでこのコードを使用する場合はバックグラウンド処理に移すべきですが、今回のエピソードの範囲外なのでその処理は割愛します。各色が別のバージョンなので、違うバージョンを渡すことでビューに表示されるスタンプの色を簡単に変えることができます。
<h1>Stamps</h1> <% @stamps.each do |stamp| %> <div class="stamp"> <%= image_tag stamp.image_url(:blue) %> </div> <% end %> <div class="clear"></div> <p><%= link_to "New Stamp", new_stamp_path %></p>
スタンプの色が変わりました。