MiniMagick(ImageMagick)についてのメモ

## 概要
Rails学習者なら一度は使用したことがあるであろう有名なGem[「MiniMagick」](https://github.com/minimagick/minimagick)について、詳しく調べてみると学びが多かったのでまとめてみました。
実務経験を積んでいる方なら当たり前の知識かもしれませんが、深く考えずにGemを使用していた私にとっては興味深い内容でした。

そもそも[「ImageMagick」](https://imagemagick.org/)を各言語で使いやすくしたものの一つがMiniMagickということすら知りませんでした。(^^;;)
同じようなレベルの方にとっては面白い内容だと思います。
https://imagemagick.org/script/develop.php

## 環境
ruby 2.7.1
ImageMagick 7.0.10-23
MiniMagick 4.10.1

## ImageMagickの脆弱性
ImageMagickは扱えるファイルの数が多いことなどから、脆弱性が多く報告されており、使用の際は環境に応じた対策をとる必要があるようです。

### 参考
[さようなら ImageMagick](https://blog.cybozu.io/entry/2018/08/21/080000)
[「さようなら ImageMagick」の考察](https://qiita.com/yoya/items/2076c1f5137d4041e3aa)
[ImageMagickを使うWebアプリのセキュリティ](https://www.mbsd.jp/blog/20180831.html)

### 【実験】Railsのよく紹介されているようなバリデーション

例えば以下のバリデーションでは、``sample.pdf``というファイルを``sample.jpg``と拡張子を書き換えただけでバリデーションに掛からなくなります。

```ruby
def image?(picture)
  picture.content_type.in?(%("image/jpeg image/jpg image/png")) && picture.content_type != ""
end
```

ローカル環境で試しに``sample.pdf``を``sample.jpg``に書き換えて以下のコードを実行し、書き出されたファイルの拡張子をpdfに戻したところ、pdfとして表示することができました。
また、typeは拡張子の``JPEG``ではなくpdfの``AI``として認識されます。

```ruby:sample.rb
require "mini_magick"
image = MiniMagick::Image.open("sample.jpg")
puts image.type
image.write("output.jpg")

=> AI
```

危険性についてはうまく説明できませんが、想定していたバリデーションを簡単に抜けられていることを考えると怖いですね。
参考記事では、``policy.xml``を設定するように紹介していましたが、MiniMagickでの方法がよく分からなかったので、調べ次第追記します。
簡易的にこんなのもありかな？？

```ruby
types = [ "JPEG", "PNG" ]
types.include?(image.type)
```

## 最適化と実行速度
resizeとstripオプションくらいしか使用したことがなかったのですが、ImageMagickには他にも多くのオプションが用意されており、MiniMagickでも全てのオプションを使用することができます。

GoogleDevelopersで紹介されていたJPEG画像の最適化コードを元に簡単な実験をしてみたのですが、実行方法次第で4倍ほど実行速度に差がありました。

### 参考

[画像を最適化する](https://developers.google.com/speed/docs/insights/OptimizeImages?hl=ja)
[ImageMagick の JPEG オプション](https://qiita.com/yoya/items/216d2fc98deb88fa7157)

### サンプルコード
使用画像:
サイズ: 10.8M
大きさ: 6000×6000

```sample.rb
require "bundler/setup"
require "mini_magick"
require 'benchmark'

result = Benchmark.realtime do
  MiniMagick::Tool::Magick.new do |magick|
    magick << "images/test.jpg"
    magick.sampling_factor "4:2:0"
    magick.strip
    magick.interlace "JPEG"
    magick.colorspace "sRGB"
    magick.quality "85"
    magick << "output.jpg"
  end
  #処理概要 2.2035720000058063s
  #処理概要 2.2015189999947324s
  #処理概要 2.204152000005706s
  #サイズ 10.8M => 4.9M

  convert = MiniMagick::Tool::Convert.new
  convert << "images/test.jpg"
  convert.sampling_factor "4:2:0"
  convert.strip
  convert.interlace "JPEG"
  convert.colorspace "sRGB"
  convert.quality "85"
  convert << "output.jpg"
  convert.call
  #処理概要 2.202056000001903s
  #処理概要 2.1908320000002277s
  #処理概要 2.207652999997663s
  #サイズ 10.8M => 4.9M

  image = MiniMagick::Image.open("images/test.jpg")
  image.sampling_factor "4:2:0"
  image.strip
  image.interlace "JPEG"
  image.colorspace "sRGB"
  image.quality "85"
  image.write("output.jpg")
  #処理概要 9.05225000000064s
  #処理概要 9.08197300000029s
  #処理概要 8.971012000001792s
  #サイズ 10.8M => 5.4M

  cmd = "convert images/test.jpg -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace sRGB output.jpg"
  system cmd
  #処理概要 2.168391999999585s
  #処理概要 2.064037999996799s
  #処理概要 2.167836000000534s
  #サイズ 10.8M => 4.9M
end

puts "処理概要 #{result}s"
```

## あとがき
「実行速度遅いんだよな〜」「思ったより圧縮されないんだよな〜」と思いつつも、表面撫でたような調べ方しかしていなかったので、ImageMagickがこんなにも高機能なソフトウェアだと知りませんでした。
MiniMagickの書き方も色々あったんですね。
``MiniMagick::Image.open``、``MiniMagick::Image.new``くらいしか使っていなかったので驚きでした。
そして、全てREADMEに書いてあったのも...

日本語記事に甘えず、まずはREADMEを隅々まで読もうと、もう何度目になるか分からない決意を新たにしました。。。