##環境
- ruby歴 初心者
- rails5
- ruby 2.4.1p111
- imagemagick 6.9.8-8 Q16 x86_64
- minimagick 4.5.1
##これはなに?
ImageMagick での自動トリミングを ruby で実現するために
ruby のインタフェースで作られた MiniMagick を利用してトリミングを実現しました。
その際に学んだことや発生した問題も踏まえて書き留めて置きたいと思います。
ちなみに筆者は画像処理についての知識は薄いのであしからず
また、コメントは細かいところでも大歓迎です。
##発端
まず、やりたかったことは 透過部分のトリミング になります。
※背景は透過で、見やすいように枠線だけ加工して入れてます。
ということで、オリジナル画像にトリミングをかけていきます。
ImageMagick でトリミングしたい場合は -trim オプションをつけるだけでOKでした。(すばらしい!)
convert <path/to/オリジナル画像> -trim <path/to/ほしい画像>
さて、次に ruby の場合です。
調査したところ実現する方法としては、下記の2通りありました。
- MiniMagick::Image#trim を用いて処理
- MiniMagick::Tool::Convert を用いて CLI のように処理
もちろん 1. が前述した convert 処理してるんだろうなと思い
そちらの手法で処理をしました。
##導入
基本的には下記の流れです。
- imagemagick のインストール
- minimagick の gem インストール
細かい部分は公式を見てください。
実装部分はこんな感じ
def trimming(input_path, output_path)
image = MiniMagick::Image.open(input_path)
image.trim
image.write(save_path)
end
おお、簡単じゃないか..........っと!!!
ところがどっこい、世の中そんな甘くなかった
##問題
鼻歌まじりに、次のサンプルをトリミングにかけた時
衝撃の結果になりました。
なぜか上部の黒い部分まで切り取られてでてきやがったぜ....どういうことだってばよ
※「ちゃんと仕様みとけクソ」というツッコミありがとうございますm(_ _)m
なんでこんなことになったしまったのか、この時点でようやく trim の仕様を確認しに行く次第でした。
##原因
まずは、ここをよく読んで下さい。
This option removes any edges that are exactly the same color as the corner
pixels.
「コーナー」のピクセルと同じ色のエッジにそってトリミングします」
OMG、書いてあります。
ちゃんと書いてあるんです。
んんっ??
コーナー......(いろいろ調べてみたら、左上隅の1ピクセルが対象となるらしい)
なるほどねー、左上隅のピクセル確かに黒いわー
今まで黒い部分が設置してなかったから気づかなかった....
期待してるトリミング画像ほしけりゃ左上隅のピクセル透過色にしないと無理ってことね。
いやいやいやいや、どうやるんですか
指定するピクセルで自動トリミングとかできないの??
(できる方法あったら教えてくださいm(_ _)m)
##解決
ここでさらに公式をチェックしたところ.....ありました。
どうやら、トリミングしたい色のボーダーラインを追加してから -trim するようです。
なるほど、たしかにそれでできるね。
(ボダーラインみたいなゴリゴリじゃなくカラーを指定させてほしい......)
もはや、MiniMagickの範疇を超えているので、結局CLI風味で実装することになった。
def trimming(input_path, output_path)
# trim image
MiniMagick::Tool::Convert.new do |convert|
convert << "#{input_path}"
convert.merge! ["-bordercolor", "none"]
convert.merge! ["-border", "1x1"]
convert.merge! ["-trim"]
convert << "#{output_path}"
end
end
MiniMagickを使ってはいるが、結局のところCLIで下記のコマンドを実行しているだけのよう
convert <input_path> -bordercolor none -border 1x1 -trim <output_path>
※ -bordercolor
については透過でほしかったので none
にしております
結果、うまくトリミングできました。
##おまけ
画像以外に元画像におけるトリミング領域の座標も欲しかったので取ってみました。
※公式にも実装方法は書かれております。
def trimming(input_path, output_path)
# trim image
MiniMagick::Tool::Convert.new do |convert|
convert << "#{input_path}"
convert.merge! ["-bordercolor", "none"]
convert.merge! ["-border", "1x1"]
convert.merge! ["-trim"]
convert << "#{output_path}"
end
# get trimmed image bounding box
info = MiniMagick::Tool::Convert.new do |convert|
convert << "#{save_path}"
convert.merge! ["-set", "page", "%[fx:page.width-2]x%[fx:page.height-2]+%[fx:page.x-1]+%[fx:page.y-1]"]
convert.merge! ["-format", "{ \"x\": \"%X\", \"y\": \"%Y\", \"width\": \"%w\", \"height\": \"%h\" }"]
convert << "info:"
end
# parse to json
trimmed_bounding_box = JSON.parse(info.delete('+'))
end
convert.merge! ["-set", "page", "%[fx:page.width-2]x%[fx:page.height-2]+%[fx:page.x-1]+%[fx:page.y-1]"]
ここの指定がミソっぽいですね。
追加したボーダーライン分の座標を調整しています。
##学んだこと
・自動トリミングの仕組み(左上隅ピクセル情報を用いてトリミングされる)
・指定の色でトリミングする場合はボーダーラインを追加してからトリミングする