ImageMagickのメモ

参考URL

ImageMagickの使い方のメモ.v6.3.0で動作するはず.

基本

形式の変換

ImageMagickを使用することにより,様々な画像を相互に変換することができる.拡張子から判断して形式を変換してくれる.たとえば,以下のようにするとpngをjpegに変換することができる.

convert from_file.png to_file.jpg

標準入力や,標準出力に書き出すこともできる.これにより他のImageMagickのコマンドとパイプによる連携をとることができる.ただし,ImageMagickが形式を知ることができるようにpng:gif:などの接頭辞を付ける必要がある.

convert from_file.png gif:- | display gif:-

Ghostscriptと連携することにより,psおよびepsやpdfを扱うことができる.このときラスタに変換しなければならないが,その解像度を-dencityで指定することができる.ここで-dencityの単位はDPIであるが,-unitを指定することによりセンチ毎の画素数に換えることもできる.

convert -density 144 some_plot.eps some_plot.png

入力画像として,組み込みの画像やパターンを使用することができる.たとえば,xc:色は,その色一色で塗られた画像を表わす.何も指定しなければ1x1な画像が作成されるが,事前に-sizeを用いて,画像のサイズを指定することもできる.これは-draw-annotateもしくは-fxを用いた画像の作成や,composite-compositeによる画像の合成に便利である.Windows環境においては,clipboard:を指定することによりクリップボードから画像を読みこんだり,書き出したりできる.

convert -size 256x128 xc:white out.png
convert -size 256x256 "xc:#FF6600" out.png
convert -size 128x256 xc:transparent \
     -fill "#0000FF80" -draw "rectangle 20,20,100,100" \
      out_transparent.png
convert  clipboard: clip_saved.png
convert -size 128x128 gradient:green-red \
        from_top_green_to_bottom_red.png
convert -size 100x200 plasma:            plasma.png
convert -size 30x288  plasma:fractal     pfrac.png
convert -size 120x10  plasma:yellow-blue pyb.png
convert -size 2x2     pattern:checkerboard  cb.png
convert -size 200x200 pattern:gray30        g30.png
convert label:"Hello World" hello_world.png

詳細は本家のマニュアルを見るべし

保存する形式が,pngなどの場合,-qualityで圧縮率を指定できる.たとえばpngの場合10の位が圧縮率,1の位が圧縮方式を表している.出力後の画像サイズが気になる場合-colors-depthなどで出力される色数や色深度をコントロールできるが,コマンドラインから調整を行うのは大変なので,これに関しては,どうしても気になる場合には他のソフトウェアを使用するのをおすすめする.それほど頭を使っていない方法として,一度gifに変換すると色数が256,色深度が8bitになるので,それを利用する方法がある.下手に減色すると,白背景の画像が白背景でなくなったりするので注意.

convert in.png -depth 8 -colors 256 -quality 95 out.png
convert in.png gif:- | convert gif:- -quality 95 out.png

合成

複数画像を読み込みそれぞれに変換を掛けた後に,合成したり別々に書き込んだりできる.変換をある画像にのみ適用したい場合は,()を用いて変換を局所化する.ここで,()も引数の1つなので,スペースに区切らなければならないし,シェルが解釈しないようにしなければならない.オプション-cloneは画像列の中から指定された順番にあるものを複製する.また,+cloneは最後の画像を複製する.また画像列の中を指定された2者を-swapを用いて入れ替えられる.また,+swapは最後の2つを入れ替える.そして-insertにより最後の画像を指定される位置に挿入することができる.ここで-insert -2+swapは等価である.画像列からの削除は-deleteを用いて行うことができる.

convert in1.png in2.png out.gif
convert in.png \( +clone -embose -colorspace gray \) \
               -compose multiply -composite out.png
convert in.png \( +clone -empose -colorspace gray \) \
               \( -clone 0 \) -delete 0 -swap 0,1 out.png

オプション-composeにより,-compositeの際の挙動を指定できる.マニュアルにあるものについては以下にそれぞれの挙動を示す.但し式中で[]は大かっこ,?:はif式である.

名前疑似コード
cleardst' = (0,0,0,0)
dst dst' = dst
src dst' = src
src-over
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1/dst'.a*[src.a*src.x + (1-src.a)*dst.a*dst.x]
dst-over
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1/dst'.a*[dst.a*dst.x + (1-dst.a)*src.a*src.x]
src-in
dst'.a = src.a*dst.a
dst'.x = dst.x
dst-in
dst'.a = src.a*dst.a
dst'.x = src.x
src-out
dst'.a = (1-src.a)*dst.a
dst'.x = dst.x
dst-out
dst'.a = src.a*(1-dst.a)
dst'.x = src.x
src-atop
dst'.a = dst.a
dst'.x = src.a*src.x + (1-src.a)*dst.x
dst-atop
dst'.a = src.a
dst'.x = dst.a*dst.x + (1-dst.a)*src.x
xor
dst'.a = src.a + dst.a - 2*src.a*dst.a
dst'.x = 1/dst'.a*[(1-dst.a)*src.a*src.x + (1-src.a)*dst.a*dst.x]
plus
dst'.a = min(src.a + dst.a, 1)
dst'.x = 1/dst'.a*[src.a*src.x + dst.a*dst.x]
multily
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1/dst'.a*[src.a*src.x*dst.a*dst.x 
                    + (1-dst.a)*src.a*src.x 
                    + (1-src.a)*dst.a*dst.x]
screen
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1 - 1/dst'a*[ src.a*(1-src.x)*dst.a*(1-dst.x)
                       + (1-dst.a)*src.a*(1-src.x)
                       + (1-src.a)*dst.a*(1-dst.x) ]
overlay
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = dst.x < 0.5 ? 
             1/dst'.a*[ 2*src.a*src.x*dst.a*dst.x 
                        + (1-dst.a)*src.a*src.x     + (1-src.a)*dst.a*dst.x ]
           : 1 - 1/dst'.a*[ 2*src.a*(1-src.x)*dst.a*(1-dst.x)
                        + (1-dst.a)*src.a*(1-src.x) + (1-src.a)*dst.a*(1-dst.x) ]
darken
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = src.a*src.x < dst.a*dst.x ? 
            1/dst'.a*[src.a*src.x + (1-src.a)*dst.a*dst.x]
          : 1/dst'.a*[dst.a*dst.x + (1-dst.a)*src.a*src.x]
lighten
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = src.a*src.x > dst.a*dst.x ? 
            1/dst'.a*[src.a*src.x + (1-src.a)*dst.a*dst.x]
          : 1/dst'.a*[dst.a*dst.x + (1-dst.a)*src.a*src.x]
color-dodge
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = src.x+dst.x >= 1 ? 
            1 - 1/dst'.a*[(1-src.a)*dst.a*(1-dst.x) + (1-dst.a)*dst.a*(1-src.x)]
          : 1 - 1/dst'.a*[ src.a*dst.a*((1-src.x)+(1-dst.x)-1)/(1-src.x) 
                          + (1-src.a)*dst.a*(1-dst.x) + (1-dst.a)*src.a*(1-src.x) ]
color-burn
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = src.x+dst.x <= 1 ? 
            1/dst'.a*[ (1-src.a)*dst.a*dst.x + (1-dst.a)*src.a*src.y ]
          : 1/dst'.a*[ src.a*dst.a*(src.x + dst.x - 1)/src.x 
                          + (1-src.a)*dst.a*dst.x     + (1-dst.a)*src.a*src.x ]
hard-light
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = src.x < 0.5 ?  
             1/dst'.a*[ 2*src.a*src.x*dst.a*dst.x 
                        + (1-dst.a)*src.a*src.x     + (1-src.a)*dst.a*dst.x ]
           : 1 - 1/dst'.a*[ 2*src.a*(1-src.x)*dst.a*(1-dst.x)
                        + (1-dst.a)*src.a*(1-src.x) + (1-src.a)*dst.a*(1-dst.x) ]
soft-light
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x =  1/dst'.a*[ (
               src.x < 0.5 ?
                    src.a*dst.a*dst.x*(1 + (dst.x-1)(2*src.x-1)) 
             : dst.x <= 0.125 ?
                    src.a*dst.a*dst.x*(1 + (dst.x-1)(3-8*dst.x)(2*src.x-1)) 
             :
                    src.a*dst.a*dst.x*(1 + (sqrt(dst.x)-1)(2*src.x-1)) 
                    src.a*dst.a*dst.x 
                    )  + (1-dst.a)*src.a*src.x + (1-src.a)*dst.a*dst.x ]
difference
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1/dst'.a*[src.a*src.x + dst.a*dst.x - 2*dst.a*src.a*min(src.x, dst.x)]
exclusion
dst'.a = 1 - (1-src.a)*(1-dst.a)
dst'.x = 1/dst'.a*[src.a*dst.a*(1 - (1-src.x)(1-dst.x))
                     + (1-dst.a)*src.a*src.x + (1-src.a)*dst.a*dst.x ]

拡大・縮小

ImageMagickを用いることで,画像を簡単に拡大・縮小することができる.たとえば,ある画像の各辺とも半分にしたい場合,次のように書くだけでよい.100%よりも大きな値を入力すれば拡大され,小さな値を入力すれば縮小される.

convert from_image.png -resize 50% to_image.png

もちろん,幅x高さと書くことで具体的な大きさを指定することもできる.この場合は,入力画像のサイズにより,拡大を行うのか縮小を行うのかが決定される.

convert from_image.png -resize 512x256 to_image.png

拡大・縮小の際には,画像の縦横比(アスペクト比)が保存される.上の例でfrom_image.pngの画像サイズが,512x512であるなら変換後のサイズは256x256となり,1024x256であるなら変換後のサイズは512x128となる.つまり,変換後のサイズがこれら指定された値を越えることがない.

幅,高さのうち片方のみを指定することもできる.この場合も画像の縦横比は保存される.つまり,以下のように書くと,入力画像が512x512であるなら256x256に,1024x256なら256x64になる.

convert from_image.png -resize 256x to_image.png

もし,縦横比を変化させてもよく,変換後のサイズをきっちり指定された値にしてほしい場合には,!を使用する.以下の例では,入力画像のサイズによらず出力画像のサイズが512x256になる.

convert from_image.png -resize 512x256! to_image.png

サイズ指定に>を使用すると,もしどちらかの辺が指定サイズを越えている場合にのみ拡大・縮小が行われる.これはサムネイルなどの作成に便利である.また,<を指定することで,どちらかの辺が指定サイズを下回っている場合のみに拡大・縮小を行うことができる.

convert from_image.png -resize "256x256>" to_image.png

サムネイルの作成については,そのままズバリ-thumbnailというオプションがある.画像のプロファイル情報を取り除く以外は,-resizeと同じである.

また@を末尾につけることにより面積を指定できる.

convert from_image.png -resize 65536@ to_image.png

もっと拡大・縮小

ImageMagickは,畳込み積分により拡大・縮小を行っている.ユーザは畳込み積分を行う際に用いるフィルタ(窓関数)を-filter-supportを用いることにより指定することができる.それぞれの窓関数の形を以下に示す.ここで,-supportはこの窓関数の幅の倍率である.1より大きいとぼやけ,1より小さいと先鋭に(ジャギがより目立つように)なる.

Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc

特に指定しない限り,フィルタはImageMagickが適切に選んでくれる.ImageMagickのマニュアルによると,The Mitchell filter is used if the image supports a palette, supports a matte channel, or is being enlarged, otherwise the Lanczos filter is used.とのことである.

しかし,このフィルタを適切に指定した場合もある.たとえば,gnuplotなどのプロット結果を縮小する場合はその一例である.もちろんgnuplotにpng出力機能はあるのだが,gnuplotのpng出力は見栄えが悪く,epsから変換するにしてもgsのpng出力もそこまで質が高くないので,epsで出力した後に2から4倍程度の大きさでレンダリングさせた後に縮小するというのは常套手段である.このときフィルタを指定しない場合に,Lanczosフィルタを用いられるが,これは歓迎されない.プロットを見るとわかるのだが,Lanczosフィルタは2を越えたあたりで,非ゼロの値を取っている.これにより,白地の上に黒線があるような画像を縮小した場合に,それに併走する線が表われてしまう場合がある.例を以下に示す.

convert -size 8x8 "xc:white" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter point -resize 800% t_orgw.png convert -size 8x8 "xc:white" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter lanczos -resize 90% -filter point -resize 800% t_lw.png

フィルタが負の値を1から2付近でとっているということは,周りの色と自分の色との差が少からず強調されるということだ.灰色の中に黒い点が1つあるような画像を縮小すると,黒い点の周りに明るい灰色が表われることになる.標準で使用されるMitchellLanczosも1から2の間で負の値をとるため,この現象を望まない場合注意が必要だ.いささか恣意的な例ではあるが,例を下に示しおく.画像はそれぞれ8倍されている.

convert -size 8x8 "xc:#666666" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter point -resize 800% t_org.png convert -size 8x8 "xc:#666666" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter lanczos -resize 90% -filter point -resize 800% t_l.png convert -size 8x8 "xc:#666666" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter mitchell -resize 90% -filter point -resize 800% t_m.png convert -size 8x8 "xc:#666666" -fill black -draw "rectangle 2,2,5,5" -filter point -resize 200% -filter blackman -resize 90% -filter point -resize 800% t_b.png

レシピ

文字列を画像に

文字列の画像を作成する

convert -pointsize 16 -fill "#F80"\
        -background white\
        -font AvantGarde-Book\
        label:'foo@example.com'\
        -trim -depth 8 -colors 8 -quality 105 mail.png

メールアドレスなどを画像化できる.

といった結果になる.-fontに指定できるフォント名は,convert -list typeにより得ることができる.また,パスを用いて指定することもできる.

-background transparentにより背景色を透明にできるが,IEでは正しく表示されない.具体的には以下のようになる.

色が違うのは,減色を行っているため.

文字列の画像を作成2

convert -size 88x33 xc:white\
        -fill "#F92"\
        -strokewidth 2 -stroke "#F80"\
        -pointsize 48\
        -draw 'text 2 26 "<=>"'\
        -depth 8 -colors 32 -quality 105 ss.png

別のやり方,この方法だと予め画像のサイズを指定しなければならない.しかし,フォントのまわりを-strokeで指定した色により縁取ることができる.

グラディエント

convert -size 256x64 xc:white \
        -tile gradient:white-'#99f' \
        -strokewidth 2 -stroke '#bbf' \
        -pointsize 58 -font AvantGarde-Demi \
        -annotate 0x0+2+48 'SKK = I' \
        -trim -depth 8 -colors 256 -quality 95 g.png 

同様のテクニックを用いることで,以下のようなものも.

convert -size 1024x256 xc:white \
        -pointsize 128 -font Palatino-Bold \
        -strokewidth 11 -stroke '#F66' \
        -annotate 0x0+2+128 "S x y z = x z (y z)" -blur 0x2 \
        -strokewidth 4 -stroke '#FFF' \
        -tile 'gradient:white-#F66' \
        -annotate 0x0+2+128 "S x y z = x z (y z)" \
        -trim -resize 50% -depth 8 -colors 256 -quality 95 g2.png

影(注:v6.0.4.v6.0.4には-shadowはなかった)

convert -pointsize 48 -fill "#F80"\
        -background transparent\
        -font './mika.ttf' \
        label:@s.txt\
        \( +clone \
           -bordercolor transparent -border 3 \
           -channel rgb -fx '0' \
           -channel a -gaussian 3x2 \) \
        \( +clone -channel rgba -fx "1" \) \
        -swap 0,2 -flatten \
        -trim -depth 8 -colors 256 -quality 105 j.png 

文字に影を付ける.-shadowのあるバージョンだともっとすっきりするかも.

影2

convert -size 512x48 xc:white \
        -pointsize 48 \
        -stroke gray -strokewidth 1 \
        -fill gray -annotate 0x40+2+42 'K a b = a' -blur 2x2 \
        \( gradient:white-black \) -fx '1-(1-u)*(0.68-v)' \
        -stroke gray -strokewidth 1 \
        -fill lightgray -annotate 0x0+2+42 'K a b = a' \
        -trim +matte -depth 8 -colors 256 -quality 95 k.png

今度は斜めに.影を先に書いている.まず,-annotateで斜めに書きぼかし,gradient:white-blackと合成することにより,色の勾配を付ける.最後に-annotateにより同じ文字を今度はまっすぐ書いている.

「ぼかし」による縁取り

convert \( -pointsize 128 -font Times-Bold \
           -fill black -background transparent \
           label:'Hello' \
           -channel a -gaussian 0x2 -channel a -fx '-0.5*cos(Pi*3*u)+0.5' \) \
        \( -size 512x256 'xc:#0000FF' \)\
        -compose src-in -composite \
        -channel rgb -fx 'a*u+(1-a)' \
        +matte -resize 50% -trim -bordercolor white -border 2 \
        -colors 256 -quality 95 f.png

「ぼかし」でできた勾配を,-fxを使って繰り返しているだけ.3次関数とどっちがきれいな結果になるかは知らないが,cosのほうが記述が楽.あとはその透明度情報を用いて,-size 512x256 'xc:#0000FF'の画像を-compose src-in -compositeにより切り抜いている.ここで-channel rgb 'a*u+(1-a)'の部分は白背景と合成しているだけ.その後+matteで透明度情報を落とし,リサイズした後-trimし,-bordercolor white -border 2により余白を補っている.古いバージョンだとcosが引数として度を取るので注意.

その他

簡単な影付け

convert in1.png \
        -matte \
        -bordercolor gray   -border 1 \
        -bordercolor transparent -border 4 \
        -bordercolor black -border 1 \
        \( +clone +matte -fx '0' -fill white -opaque black \) \
        +swap -flatten \
        \( +clone +matte -fx '0' -fill gray -opaque black \
           -matte \
           -bordercolor transparent -border 4 \) \
        +swap -flatten \
        gif:- | convert - -quality 95 out1.png

高林さんのサイトのように,画像に枠を付ける.

透過が一色のみならgifのほうが幸せになれるか?-colorsとか-depthとかいろいろいじればpngでも小さい画像が作れるのだろうけど,よくわからなかったから一度gifを経由した.

-matteがいまいちよくわからない.+matteは,アルファ情報を捨てるみたい?