でかいjpeg画像をmini_magickで高速にサムネイル化(リサイズ&Crop)する方法

画像をサムネイル化する必要があったのでどうしたら速くなるのかしらべてたら本当は速いImageMagick: サムネイル画像生成を10倍速くする方法によると 普通にImageMagickでも十分速いみたい。

なのでrubyのImageMagickラッパーのmini_magickを使ってどれぐらい速くなるのか調査

準備

image_magickのインストール brew install imagemagick

mini_magickのインストール gem install mini_magick

-defineオプションをつける

リサイズの対象がjpegでリサイズ後のサイズがだいたいわかっているときは-define jpeg:size=100x100ってオプション(100x100はリサイズ後のサイズ)をつけてやると省メモリで速くなるらしいのでどれぐらい変わるか調査。 ついでにmini_magick経由と直接コマンドを実行するのでどれくらいかわるかも調べる。

使ったコードは以下のとおり。

require 'benchmark'
require 'mini_magick'


n = 10
blob = File.open('cat2.jpg').read

Benchmark.bm(22) do |x|
  x.report("mini_magic define on:") {
    n.times do
      image = MiniMagick::Image.read(blob)
      image.define "jpeg:size=100x100"
      image.resize "100x100"
      image.write 'resized_cat.jpg'
    end
  }

  x.report("mini_magic define off:") {
    n.times do
      image = MiniMagick::Image.read(blob)
      image.resize "100x100"
      image.write 'resized_cat.jpg'
    end
  }


  x.report("sh define on:"){
    n.times do
       `convert -define jpeg:size=100x100 -resize 100x100 cat.jpg resized_cat3.jpg`
    end
  }

  x.report("sh no define off:"){
    n.times do
       `convert -resize 100x100 cat.jpg resized_cat3.jpg`
    end
  }

ちなみにcat2.jpgはだいたい2400*3000で6.4MBぐらいのでっかい画像を使って同じ処理を10回繰り返した。 結果は以下のとおり。

                             user system total real
mini_magic define on: 0.140000 0.260000 3.650000 ( 4.029392)
mini_magic define off: 0.140000 0.240000 11.170000 ( 12.378788)
sh define on: 0.000000 0.010000 1.810000 ( 2.036538)
sh define off: 0.000000 0.000000 11.440000 ( 12.624999

defineオプションすげー!って感じです。あとやっぱり直にImageMagickを呼んだほうが速くなるみたい。

切り取り方法を変えてみる

リサイズして中心を四角くCropしてサムネを作りたかったのでCrop方法もちょっと試してみた。

1つめはshapeを使って画像の要らないとこをそぎ取る。 2つめは単純にCropする。 3つめはcarrierwaveでのCrop方法にdefineオプションつけたもの。処理的には切り取りたいサイズの透明な画像を中心に置いて重なった部分だけを残す感じ。

require 'benchmark'
require 'mini_magick'


n = 10
blob = File.open('cat2.jpg').read

Benchmark.bm(22) do |x|

  x.report("mini_magic crop1:"){
    n.times do
      image = MiniMagick::Image.read(blob)
      image.define "jpeg:size=100x100"
      w = image[:width]
      h = image[:height]
      if w < h
        remove = ((h - w)/2).round
        image.shave("0x#{remove}")
      elsif w > h
        remove = ((w - h)/2).round
        image.shave("#{remove}x0")
      end
      image.resize "100x100"
      image.write 'resized_crop_cat.jpg'
    end
  }


  x.report("mini_magic crop2:"){
    n.times do
      image = MiniMagick::Image.read(blob)
      width = height = 100
      cols, rows = image[:dimensions]
      if width != cols || height != rows
        scale = [width/cols.to_f, height/rows.to_f].max
        cols = (scale * (cols + 0.5)).round
        rows = (scale * (rows + 0.5)).round
      end
      crop_x = crop_y = 0
      crop_val = ((cols - rows).abs / 2).round

      if cols < rows
        crop_y = crop_val
      else
        crop_x = crop_val
      end

      image.define "jpeg:size=#{cols}x#{rows}"
      image.resize "#{cols}x#{rows}"
      image.crop "100x100+#{crop_x}+#{crop_y}"
      image.write 'resized_crop_cat2.jpg'
    end
  }

  x.report("mini_magic crop3:"){
    n.times do

      img = MiniMagick::Image.read(blob)
      width = 100
      height = 100
      cols, rows = img[:dimensions]
      img.define "jpeg:size=#{width}x#{height}"
      img.combine_options do |cmd|
        if width != cols || height != rows
          scale = [width/cols.to_f, height/rows.to_f].max
          cols = (scale * (cols + 0.5)).round
          rows = (scale * (rows + 0.5)).round
          cmd.resize "#{cols}x#{rows}"
        end
        cmd.gravity 'Center'
        cmd.background "rgba(255,255,255,0.0)"
        cmd.extent "#{width}x#{height}" if cols != width || rows != height
      end
      img.write 'resized_crop_cat3.jpg'
    end
  }
end

結果は以下の通り。

                             user system total real
mini_magic crop1: 0.160000 0.260000 4.630000 ( 5.327175)
mini_magic crop2: 0.160000 0.260000 4.230000 ( 4.850669)
mini_magic crop3: 0.150000 0.240000 3.900000 ( 4.906052)

3つめのやり方が結構速い。