rubyで画像の拡大縮小(RMagickの拡大縮小関係メソッドの違い)

RMagick

RMagickでは画像を

require 'RMagick'
img = Magick::Image.read('original.png').first

として読み込みます*1
RMagickには拡大縮小のメソッドとして何種類かあります。パラメータとしては拡大(縮小)率scaleを指定、または縦横ピクセル(width, height)を指定、の二種類があります。

  • resizeメソッド

resizeメソッドは,(元画像によるところはありますが)画質もよいですがファイルサイズは大きくなりがちです。また,リサイズの計算を行うためのフィルタも指定ができ,処理内容を調整することができます。

  • scaleメソッド

scaleメソッドは,resizeよりも画質がやや悪くファイルサイズはやや小さくなる傾向があります。

  • thumbnailメソッド

thumbnailメソッドは,本来元画像の10%より小さい画像を作るためのメソッドで,ファイルサイズは画像のサイズにしては大きくなる傾向があります。ですが,実行速度は速くできます。

  • sampleメソッド

sampleメソッドは,通常リサイズの場合に発生する中間色を一切作らないため,実行速度は速くファイルサイズも小さくできますがこの中で一番画質を犠牲にしています。
第26回 RMagickを用いた画像処理(1)リサイズ:Ruby Freaks Lounge|gihyo.jp … 技術評論社

Christy(ImageMagickのチーフアーキテクトだ)はこれら4つのメソッドの違いを次のように説明している:
各メソッドの特徴(引用註:上記のメソッド順に並び替えました)

resize まぁ、あれだ。何でもありのメソッドだ。使用するフィルタを選んだり、ぼかしたりシャープにしたりもできる。フィルタは縮小時にはLanczosFilter、拡大時はMitchellFilterをデフォルトで使用する。代表色を作るとき、実は隣接ピクセルのアルファチャネル情報も考慮している。透明度の低いピクセルは高いピクセルよりも重みを多く持つ。
scale sampleに似ているが、浮動小数点によるサンプリングを実施し、周囲のピクセル情報も代表色に取り入れている。
thumbnail 範囲内のピクセルを5点サンプリングし、その情報を元に代表色を決定・リサイズする。resizeメソッドで画像を1/5のサイズに縮小するときと同じ。プロファイルやコメント情報はstripされる。
sample 範囲内の中央ピクセルの情報だけを使ってリサイズする(つまり、画像中に無い色は生成されない)。resizeをPointFilterで使用したときと同じ。

Making Thumbnails with RMagick - selflearn @ ウィキ - アットウィキ

画質は主観になるけど、sampleメソッドはまず使うことはないだろうと思えるくらい汚い。それ以外の3つは大差ない感じに見えた。
んでそれぞれのファイルサイズの結果は以下。すべてにおいて、scaleメソッドがファイルサイズ最小値となった。
これらの結果から、個人的には、実行速度よりファイルサイズを小さくしたいときはscaleメソッドを、名前のとおり速度を重視したサムネイル画像を作るならthumbnailメソッドを使うのが良いかなぁと思った。
RubyのRMagickで画像をリサイズする - アインシュタインの電話番号

これらの引用では、各メソッドを遅い順に並べている、のかと思います。
resizeは良いフィルタを使うので遅い、のですかね。

メソッドによる画質比較

http://rmagick.rubyforge.org/resizing-frame.html
を参照。

resizeで速く処理

JPEGファイルの場合には、ファイル自身の特徴として8x8単位でタイリングされてます。のでImageMagickの場合は、次のようにすることで速くなるそうです。

convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg
本当は速いImageMagick: サムネイル画像生成を10倍速くする方法 - 昼メシ物語

各辺の長さとして1/2, 1/4, 1/8、面積比にすると1/4, 1/16, 1/64である画像を、オリジナルの画像の代わりにして処理を行うので、メモリ消費が少なくて処理速度が上がるようです。

サムネイル化したい画像が巨大ファイルサイズのJPEGデータだった場合、全データの読み込み&画像処理よりもっと良い方法がある。
それはImage::Infoの属性self.sizeを指定してファイルを読むことだ。求めているサムネイルサイズのちょうど2倍のサイズを指定してから読み込むと、ImageMagickJPEGライブラリはファイルのデータ全部を読み込まず、必要な量だけを読み込んでくれるというわけだ。その後、適度に小さくなって読み込まれた画像を使ってサムネイル化する。
大きなファイルの読み込みはメモリを圧迫して無駄も多いので、ぜひ活用してほしい。
Making Thumbnails with RMagick - selflearn @ ウィキ - アットウィキ

これをRMagickでもやってみましょう。

  • ふつうにresize
Magick::Image.read("image.jpg").first.resize(scale).write("out.jpg")
  • オンメモリ縮小resize(image.jpgのサイズはMagick::Image.pingで求めて(w1, h1)、縮小後out.jpgのサイズは(w2, h2)に)
img_ping = Magick::Image.ping("image.jpg").first
h1 = img_ping.rows
w1 = img_ping.columns
img = Magick::Image.read("image.jpg").first{self.define("jpeg", "size", w2 + "x" + h2)}
img.resize(h2 / img.rows).write("out.jpg")

ここでimgは、(w2, h2)よりも大きいサイズの縮小image.jpgになるのでimg.rowsはh1ともh2とも異なります。なので縮小率scaleはh2 / img.rowsとして算出する必要があります。

速くなる?

少しサンプルを取って確認したところ、所要時間が4〜6%ほど少なくなりました。

*1:クラスメソッドreadは画像を要素とする配列を返しますので、firstメソッドで画像のみを取り出します。マニュアルに書いてあるんだけど読めてなくて苦労したorz