Slide 1

Slide 1 text

GoImagick  でサムネール作成   〜  Golang  &  ImageMagick  〜 2016年4月8日(金)   “よや”  [email protected]

Slide 2

Slide 2 text

自己紹介 (@yoya) •  プロファイル   – hAps://osdn.jp/users/yoya/   •  ImageMagick  のストーカーしてます   – hAp://d.hatena.ne.jp/yoya/searchdiary? word=ImageMagick   •  昔、PHP  でバイナリを弄ってました   – hAps://github.com/yoya/IO_MIDI   – hAps://github.com/yoya/IO_JPEG   •  Golang  は触り始めて一年ちょっと ?

Slide 3

Slide 3 text

GoImagick とは •  hAps://github.com/gographics/imagick   •  MagickWand  API  (C言語)の Go  バインディング   –  Go  言語で ImageMagick  の機能が使えます  

Slide 4

Slide 4 text

公式サイトからもリンク •  hAp://imagemagick.org/script/api.php#go     •  MagickWand  と MagickCore  の Go  バインディ ング   – GoImagick はMagickCore の定義を取り入れるけ ど、関数は MagickWand  のだけ使います。  

Slide 5

Slide 5 text

MagickWand と MagickCore •  GoImagick は MagickWand の関数を使う ImageMagick   MagickCore   (magick) coders MagickWand   (wand) uWliWes   画像処理   本体は   ココ PerlMagick PHP  imagick convert   コマンドはここ 使い易くする   為のAPI GoImagick JPEGやPNG   の入出力

Slide 6

Slide 6 text

つまり? •  convert  コマンドと PHP  imagick  のコードを見 ると GoImagick  の使い方も分かる   – convert  コマンド   •  wand/mogrify.c   – PHP  imagick   •  hAp://php.net/manual/ja/imagick.transformimage.php  

Slide 7

Slide 7 text

なぜ ImageMagick  を使うのか? •  Golang  標準で image  パッケージあるよね?   – 機能少ないし 対応形式は JPEG,GIF,PNG  だけ   •  libpng  や jpeglib  を直接使わないの?   – go-­‐thumber  がそうだけど cgo  は難易度高い     •  他にも画像変換ツールがあるのでは?   – ImageMagick  は困った時に検索で探しやすい   •  (恐らく人による。自分は ImageMagick  が楽)  

Slide 8

Slide 8 text

ImageMagick  を使う理由(1/2) •  画像を処理したいメソッドが大体揃っている   – リサイズ   – フィルタ   – 画像合成   – 文字入れ     Gopher

Slide 9

Slide 9 text

ImageMagick  を使う理由(2/2) •  メジャーな画像フォーマットからマイナーなものま で100種類以上に対応してる   •  hAp://www.imagemagick.org/script/formats.php png jpeg gif inline sixel webp svg pdf 超メジャー 最近の キワモノ系   (Webの  base64画像とか) ベクター画像 dcm 医療系   (DICOM等)

Slide 10

Slide 10 text

    ここから本題

Slide 11

Slide 11 text

GoImagick 導入 (MacOS編) •  少し前まで   •  今のやり方 $  sudo  port  install  ImageMagick   $  go  get  github.com/gographics/imagick            #  ImageMagick  v6.8.8以前 (rpm や dpkg  とかで古い場合)   $  go  get  gopkg.in/gographics/imagick.v1/imagick          #  ImageMagick  v6.8.9以降 (macports  や最新版を使う場合)   $  go  get  gopkg.in/gographics/imagick.v2/imagick   以下のエラーが出ます   expects  import  "gopkg.in/gographics/imagick.v2/imagick"

Slide 12

Slide 12 text

GoImagick 使用例 •  resize640x480.go   _  =  〜はエラーの値。ちゃんと拾って処理すべき   package  main   import  (                  "gopkg.in/gographics/imagick.v2/imagick”   )   func  main()  {                  imagick.IniWalize()                  defer  imagick.Terminate()                  mw  :=  imagick.NewMagickWand()                  defer  mw.Destroy()                  _  =  mw.ReadImage(”input.png”)                  _  =  mw.ResizeImage(640,  480,  imagick.FILTER_UNDEFINED,  1)                  _  =  mw.WriteImage("output.png")   }  

Slide 13

Slide 13 text

ResizeImage(640,480,…  実行   •  縦横のアスペクト比が…   – サムネール画像としてはNG        $  go  run  resize640x480.go  gopher.png   250px 340px 480px 640px ふくよかな   Gopher!

Slide 14

Slide 14 text

アスペクト比を保つ方法(1/2) •  出力サイズを変えてしまう   –  はみ出ないように (内接)   •  480  ×  (250/340)   •  =>  352x480   •  ResizeImage(352,480,…   –  減らさない  (外接)   •  640  ×  (340/250)     •  =>  640x870   •  ResizeImage(640,870,…   •  ResizeImage  だけで良い       250px 340px 480px 870px 640px 640px 352px 480px

Slide 15

Slide 15 text

アスペクト比を保つ方法(2/2) •  出力サイズを変えない   – マージンをつける (内接)   •  480  ×  (250/340)   •  =>  352x480   – クロップする  (外接)   •  640  ×  (340/250)     •  =>  640x870   •  ResizeImage  だけでは無理       250px 340px 480px 870px 640px 640px 352px 480px

Slide 16

Slide 16 text

マージン(内接)の方法 •  描画領域を広げる (ExtentImage)   – (640  –  352)  /  2)  =  144  ⇦  左右に144拡げる   480px 640px 144px 640px 352px 480px 480px 352px _  =  mw.ResizeImage(352,  480,  imagick.FILTER_UNDEFINED,  1)   _  =  mw.ExtentImage(-­‐144,  0,  640,  480)  //  -­‐extents   _  =  mw.ResetImagePage(“”)                                        //  +repage  

Slide 17

Slide 17 text

クロップ(外接)の方法(1/2) •  描画領域を削る (ExtentImage)   – (640  –  352)  /  2)  =  144   _  =  mw.ResizeImage(640,  870,  imagick.FILTER_UNDEFINED,  1)   mw2  =  mw.CropImage(0,  0,  640,  480)  //  -­‐crop   defer  mw2.Destory()   870px 640px 640px 480px 480px

Slide 18

Slide 18 text

クロップ(外接)の方法(2/2) •  リサイズとクロップ同時 (TransformImage)   – 250  ×  (480/640)  =  187.5   crop_src  :=  “250x187+0+0”   geom_dst  :=  “640x480”   mw2  =  mw.TransformImage(crop_src,  geom_dst)   defer  mw2.Destroy()   640px 480px 250px 340px 187px

Slide 19

Slide 19 text

マージンやクロップの注意点 •  マージンをどこに    つけるか、どこを    クロップするか    は画像次第 250px 340px 480px 870px 640px 640px 352px 480px 640px 352px 870px 480px

Slide 20

Slide 20 text

画像合成   •  CompositeImage で合成できる _  =  mw1.ReadImage(“gopher.png”)   _  =  mw2.ReadImage(“blind.png”)   _  =  mw1.CompositeImage(mw2,  imagick.COMPOSITE_OP_OVER,  45,  28)   CompositeImage gopher.png blind.png

Slide 21

Slide 21 text

文字入れ  (1/5)   •  DrawingWand  と PixelWand  を使う   – DrawingWand  でフォントを指定   •   (日本語を表示するなら必須)         – (蛇足)  QueryFont  で扱えるフォントが分かる dw  :=  imagick.NewDrawingWand()   defer  dw.Destroy()   _  =  dw.SetFont("Noto-­‐Sans-­‐CJK-­‐JP-­‐Medium”)   dw.SetFontSize(24)   fonts  :=  mw.QueryFont(“*”)   fmt.Prinv(“%#v”,  fonts)  

Slide 22

Slide 22 text

文字入れ  (2/5)   •  PixelWand で色を表現   •  DrawingWand  で色と文字を設定する         pw  :=  imagick.NewPixelWand()   defer  pw.Destroy()   _  =  pw.SetColor(”rgb(0,  0,  0)”)   dw.SetFillColor(pw)   dw.AnnotaWon(0,  0,  “Gopher!!”)  

Slide 23

Slide 23 text

文字入れ  (3/5)   •  DrawingWand  の文字を MagickWand  の画像に 描画   •  (0,0)を基準に文字を貼るので殆ど見えない   –  見えてるのは p  の下にはみ出た部分   •  Gravity 方式で配置しよう _  =  mw.DrawImage(dw)   あれれ?

Slide 24

Slide 24 text

文字入れ  (4/5)   •  CENTER 指定と SOUTH  指定 dw.setGravity(imagick.GRAVITY_CENTER)   dw.AnnotaWon(0,  0,  “Gopher!!!”)   mw.DrawImage(dw)   GRAVITY_SOUTH  

Slide 25

Slide 25 text

文字入れ  (5/5)   •  まとめ imagick.IniWalize()   defer  imagick.Terminate()   mw  :=  imagick.NewMagickWand()   defer  mw.Destroy()   dw  :=  imagick.NewDrawingWand()   defer  dw.Destroy()   pw  :=  imagick.NewPixelWand()   defer  pw.Destroy()   _  =  mw.ReadImage(os.Args[1])   _  =  dw.SetFont("Noto-­‐Sans-­‐CJK-­‐JP-­‐Medium")   dw.SetFontSize(24)   _  =  pw.SetColor("rgb(255,  0,  0)")   dw.SetFillColor(pw)   dw.SetGravity(imagick.GRAVITY_CENTER)   dw.AnnotaWon(0,  0,  "Gopher!!!")   _  =  mw.DrawImage(dw)   _  =  mw.WriteImage("output.png”)  

Slide 26

Slide 26 text

MagickWand  の注意点 •  メソッドが MagickWand  を返した時にも Destroy  が必要   – 中で new  相当の処理が動いてる crop_src  :=  “250x187+0+0”   geom_dst  :=  “640x480”   mw2  =  mw.TransformImage(crop_src,  geom_dst)   defer  mw2.Destroy()   _  =  mw.ResizeImage(640,  870,  imagick.FILTER_UNDEFINED,  1)   mw2  =  mw.CropImage(0,  0,  640,  480)  //  -­‐crop   defer  mw2.Destory()  

Slide 27

Slide 27 text

GoImagick の中身 •  使うだけでなく中身も見よう   – hAps://github.com/gographics/imagick   imagick$  ls   CREDITS    LICENSE    env.sh    imagick   History.md  README.md  examples   imagick$  ls  imagick  |  wc              73            73        1298   imagick$  ls  -­‐R  examples    |  wc              92            71          874   サンプルが沢山ある GoImagick  本体

Slide 28

Slide 28 text

ところで •  hAp://imagemagick.org/script/api.php#go   •  なぜ GoImagick  と ImageMagick本家で説明 に食い違いがあるのか。   •  なぜ GoImagick  と ImageMagick本家で説明 に食い違いがあるのか   – GoImagick  ⇨  MagickWand   – ImageMagick本家 ⇨  MagickWand  +  MagickCore

Slide 29

Slide 29 text

MagickWand と MagickCore •  おおまかな構造 ImageMagick   MagickCore   (magick) coders MagickWand   (wand) uWliWes   画像処理   本体は   ココ PerlMagick PHP  imagick convert   コマンドはここ 使い易くする   為のAPI GoImagick ??? JPEGやPNG   の入出力

Slide 30

Slide 30 text

grep  include * で確認 •  はい。MagickCore  も include  してます

Slide 31

Slide 31 text

MagickCore の利用例 •  型の定義を取り込みたいだけ。MagickCore   の関数を利用してる訳ではなさそう

Slide 32

Slide 32 text

つまりどういう事? •  利用する関数は MagickWand だけ   – MagickCore  ではない (型の取り込みで include  し てるだけ)   •  PerlMagick  より PHP  imagick  のサンプルが参 考になるという事

Slide 33

Slide 33 text

最近のトピック •  メモリ管理を色々と修正 (by  yoya)   –  fixed  to  memory  leak,  string  array  issue.   •  hAps://github.com/gographics/imagick/pull/37   •  hAps://github.com/gographics/imagick/pull/39   •  Magick.IniWalize()  に Mutex  をかけたい(協議中)   –  Fix  IniWalize/Terminate  race  condiWon  #43   •  hAps://github.com/gographics/imagick/pull/43   •  MagickWand  等の回収を  GC  に任せたい   –  Make  mw,  pi,  pw,  dw  objects  destroyable  in  GO  GC  #62   •   hAps://github.com/gographics/imagick/pull/62  

Slide 34

Slide 34 text

 メモリ管理を色々と修正 (1/3) •  フォント名一覧取得でメモリリークした   –  似たような漏れが他にもあるのでは?   –  Malloc  してる箇所が見当たらないのに、free  してるけ どそのポインタは大丈夫なの?   •  関連するバグを調査   –  似たようなリークがあちこちにあった   •  文字列のリストを取得する系メソッドが大体ダメ   –  Wand  API  の中で AcquireMagickMemory(ImageMagick  のメモリ管理)で 取得したメモリを、標準の free  で解放してた   •  RelinquishMemory  を使うべき  

Slide 35

Slide 35 text

 メモリ管理を色々と修正 (2/3) •  調査した   •  O  以外が    不具合の    あるメソッド  

Slide 36

Slide 36 text

 メモリ管理を色々と修正  (3/3) •  結構大量に修正       – 余計な修正をcommitしちゃったけどマージしてく れた   – CREDITS  に名前載せてくれた   やさしい

Slide 37

Slide 37 text

Magick.IniWalize()  に Mutex •  Magick.IniWalize()  や Terminate()  をマルチス レッドで呼ぶと競合するので Mutex  をかけた い   •  Sync.once  で  IniWalize  を一度だけ呼べばよく ない?   •  でもユーザに気をつけろというより仕組みを 入れた方がよくない?   •  協議続行中

Slide 38

Slide 38 text

GC  に任せる(1/2) •  runWme.SetFinalizer  を使う   •  GC  対象になると SetFinalizer  で指定したメソッ ド(Destroy)が呼ばれる   func  newMagickWand(mw  *C.MagickWand)  *MagickWand  {    mw  :=  &MagickWand{mw:  cmw}    runWme.SetFinalizer(mw,  Destroy)    mw.IncreaseCount()    return  mw   }  

Slide 39

Slide 39 text

GCに任せる(2/2) •  defer  で destroy  する問題点 (一般論)   – 毎回  defer  書くのは面倒だし忘れたりする   – 関数の外に  return  出来ない   – 明示的に new  するものはまだ良いけど、新しく MagickWand  を返すメソッドもあって漏れがち   •  CropImage   •  TransformImage   •  これらの戻り値も Destroy  しないとリークする   •  defer  mw.Destroy  といちいち書かなくてもよく なると嬉しい!  

Slide 40

Slide 40 text

まとめ •  MagickWand で画像を処理する   •  DrawingWand  で文字を描画する   •  PixelWand  で文字の色を指定する   •  defer  mw.Destroy()  を忘れずに   –  CropImage  や TransformImage  が返すのも Destroy()  をお 忘れずに   •  ここまでの話を聞けば、Golang  でサムネール画像を 作れるはず   –  Let’s  try!