Slide 1

Slide 1 text

GoImagick  詳解   〜  Golang  &  ImageMagick  〜 2016年4月23日(土)   “よや”  [email protected]

Slide 2

Slide 2 text

自己紹介 (@yoya) •  プロフィール   –  hAps://osdn.jp/users/yoya/      (フリーランスなのでお仕事下さい)     •  ImageMagick  のストーカーしてます   –  hAp://d.hatena.ne.jp/yoya/searchdiary?word=ImageMagick   •  PHP  でバイナリ弄るのが持ちネタ   •  Golang  は触り始めて一年ちょっと ?

Slide 3

Slide 3 text

発表の目的 •  GoImagick を使う人を増やしたい   – (というかGoImagick  自体を弄る人が欲しい)  

Slide 4

Slide 4 text

もくじ •  GoImagick  とは   •  セッティング   •  使い方   •  内部の話   •  最近のトピック   •  蛇足   •  まとめ  

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

つまり? •  convert  コマンドと PHP  imagick  のコードを見 れば GoImagick  の使い方が分かる   – convert  コマンド   •  wand/mogrify.c   – PHP  imagick   •  hAp://php.net/manual/ja/imagick.transformimage.php   似た名前と引数の   メソッドが GoImagick  に   もあると期待出来る

Slide 9

Slide 9 text

ここから本題 •  GoImagick のセッティング   •  GoImagick  の使い方   •  GoImagick  の内部   – pkg-­‐config   – cgo  

Slide 10

Slide 10 text

GoImagick  セッティング  (MacOS)  (1/2) •  少し前まで master         •  今のやり方 $  sudo  port  install  ImageMagick          #  ImageMagick  v6.8.9-­‐9以降   $  go  get  gopkg.in/gographics/imagick.v2/imagick   $  sudo  port  install  ImageMagick      #  古いやり方   $  go  get  github.com/gographics/imagick   以下のエラーが出る   expects  import  "gopkg.in/gographics/imagick.v2/imagick”  

Slide 11

Slide 11 text

GoImagick  セッティング  (MacOS)  (2/2) •  ImageMagick  を brew  と  ports  両方とも入れて る場合         •  pkg-­‐config  の実行パスを変えればOK $  PATH=/opt/local/bin:$PATH  go  install  gopkg.in/gographics/imagick.v2/imagick   /*   #cgo  !no_pkgconfig  pkg-­‐config:  MagickWand  MagickCore   */   import  "C"   リンク先は   pkgconfig  コマンドで決める

Slide 12

Slide 12 text

GoImagick  セッティング  (CentOS) •  RPM  を使う場合         •  ImageMagick  を自分でbuildする場合 $  (cd  ImageMagick-­‐6.9.3-­‐8  ;  ./configure  ;  make  install)          #  ImageMagick  v6.8.9-­‐9以降  (最近の ImageMagick  はこっち)   $  go  get  gopkg.in/gographics/imagick.v2/imagick   $  sudo  yum  install  ImageMagick-­‐devel          #  ImageMagick  v6.8.9-­‐8以前 (dpkg  も多分これ)   $  go  get  gopkg.in/gographics/imagick.v1/imagick  

Slide 13

Slide 13 text

GoImagick の使い方 (1/3) •  例えば、640x480  にリサイズする   ( _  =  〜はエラーの値。本来はチェックするべき)   package  main   import  (                  "gopkg.in/gographics/imagick.v2/imagick”   )   func  main()  {                  imagick.IniRalize()                  defer  imagick.Terminate()                  mw  :=  imagick.NewMagickWand()                  _  =  mw.ReadImage(”input.png”)                  _  =  mw.ResizeImage(640,  480,  imagick.FILTER_UNDEFINED,  1)                  _  =  mw.WriteImage("output.png")   }  

Slide 14

Slide 14 text

GoImagick  の使い方 (2/3)   •  縦横のアスペクト比が…   – サムネール画像としてはNG        $  go  run  resize640x480.go  gopher.png   250px 340px 480px 640px ふくよかな   Gopher!

Slide 15

Slide 15 text

GoImagick の使い方(3/3) •  サムネール画像作成の詳細はこちら   –  hAps://speakerdeck.com/yoya/goimagickthumbnail   •  今回は GoImagick 自体の解説 (残り3分で)

Slide 16

Slide 16 text

GoImagick のファイル •  ディレクトリ構成  >  imagick  と example  の2つ   – 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 17

Slide 17 text

動作環境の構築(初期化) •  ImageMagick  が動作する環境を用意する MagickCore  (ImageMagick  のコア部)   Genesis   (セマフォ用意) MagickWandGenesis 環境変数   取り込み SIG〜ハンドラ   設定 MagickCoreGenesis

Slide 18

Slide 18 text

ImageMagick の動作環境生成 •  MagickWandGenesis  を呼ぶだけ   •  imagick/magick_wand_env.go //  IniRalizes  the  MagickWand  environment   func  IniRalize()  {                  envSemaphore  <-­‐  struct{}{}    defer  func()  {                                  <-­‐envSemaphore                  }()      initOnce.Do(func()  {                                  C.MagickWandGenesis()                                  terminateOnce  =  &sync.Once{}      setCanTerminate()                  })   }   Cの関数呼ぶの   簡単ですね!   /*   #include     */   import  "C"   Cの関数を呼ぶ   のに必要  

Slide 19

Slide 19 text

ImageMagick の動作環境廃棄 •  MagickWandTerminus  を呼ぶだけ   •  Imagick/magick_wand_env.go   func  Terminate()  {                  envSemaphore  <-­‐  struct{}{}                  defer  func()  {                                  <-­‐envSemaphore                  }()                    if  terminateOnce  !=  nil  {                                  terminateOnce.Do(func()  {                                                  runRme.GC()                                                  terminate()                                  })                  }   }   func  terminate()  {    <-­‐canTerminate                  fmt.Println("C.MagickWandTerminus()")                  C.MagickWandTerminus()                  initOnce  =  sync.Once{}   }  

Slide 20

Slide 20 text

ユーザはどうすれば良い? •  プログラムのはじめに一度だけ呼べば良い   •  ライブラリとして用意する場合は別   – ライブラリの機能を使い終わったら imagick.Terminate()  で動作環境を消せるようにし た方がコンピュータに優しい   var  imagick_iniRalized  =  func()  bool  {                  imagick.IniRalize()                  return  true   }()  

Slide 21

Slide 21 text

MagickWand  の登場人物 •  3つのオブジェクトを介して操作する MagickWandAPI   MagickWand   (画像) DrawingWand   (描画) PixelWand   (ピクセル操作) mw  :=  imagick.NewMagickWand()   _  =  mw.ReadImage(”input.png”)   _  =  mw.ResizeImage(640,  480,  imagick.FILTER_UNDEFINED,  1)   _  =  mw.WriteImage("output.png”)   リサイズや   合成だけなら、   これだけでOK 文字入れは   これらが必須 文字入れは   これらが必須

Slide 22

Slide 22 text

MagickWand オブジェクト  (1/1) •  C.NewMagickWand  を呼んでカプセル化   •  Imagick/magick_wand.go   type  MagickWand  struct  {                  mw      *C.MagickWand                  init  sync.Once   }     func  newMagickWand(cmw  *C.MagickWand)  *MagickWand  {                  mw  :=  &MagickWand{mw:  cmw}                  runRme.SetFinalizer(mw,  Destroy)                  mw.IncreaseCount()                    return  mw   }   func  NewMagickWand()  *MagickWand  {                  return  newMagickWand(C.NewMagickWand())   }   GC  に回収   タイミングを   任せる   MagickWand  が   1つでもあるうちは   Terminus  を   動かさない  

Slide 23

Slide 23 text

MagickWand オブジェクト  (2/1) •  Destroy   •  Imagick/magick_wand.go   func  (mw  *MagickWand)  Destroy()  {                  if  mw.mw  ==  nil  {                                  return                  }                    mw.init.Do(func()  {                                  mw.mw  =  C.DestroyMagickWand(mw.mw)                                  relinquishMemory(unsafe.Pointer(mw.mw))                                  mw.mw  =  nil                                    mw.DecreaseCount()                  })   }   DestroyMagickWand  を呼ぶだけ   多分無駄だけど   念のため   MagickWand  が全て片付くまで   Terminus  は呼ばせない  

Slide 24

Slide 24 text

MagickWand オブジェクト  (3/1) •  ReadImage   •  Imagick/magick_wand_image.go   func  (mw  *MagickWand)  ReadImage(filename  string)  error  {                  csfilename  :=  C.CString(filename)                  defer  C.free(unsafe.Pointer(csfilename))                  ok  :=  C.MagickReadImage(mw.mw,  csfilename)                  return  mw.getLastErrorIfFailed(ok)   }   func  (mw  *MagickWand)  getLastErrorIfFailed(ok  C.MagickBooleanType)  error  {                  if  C.int(ok)  ==  0  {                                  return  mw.GetLastError()                  }  else  {                                  return  nil                  }   }   エラーは   GetLastError  で取得   メソッドを生やす   Go の文字列を   Cの文字列にする   Cの関数に渡す  

Slide 25

Slide 25 text

•  GoImagick はだいたい     こんなノリで作られてます

Slide 26

Slide 26 text

最近のトピック •  MagickWand  等の回収を  GC  に任せたい   – Make  mw,  pi,  pw,  dw  objects  destroyable  in  GO   GC  #62   •   hAps://github.com/gographics/imagick/pull/62   •  メモリリークをたくさん修正 (by  yoya)   – fixed  to  memory  leak,  string  array  issue.   •  hAps://github.com/gographics/imagick/pull/37   •  hAps://github.com/gographics/imagick/pull/39  

Slide 27

Slide 27 text

GC  に任せる(1/3) •  以前は MagickWand  をこういう使い方してた   •  いちいち defer  Destroy  するの面倒だよね?   func  main()  {                  imagick.IniRalize()                  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 28

Slide 28 text

GCに任せる(2/3) •  defer  で destroy  する問題点 (一般論)   – 毎回  defer  書くのは面倒だし忘れるかも?   – 関数の外に  return  出来ない   – New〜  じゃないのに新しく作った MagickWand  を 返すメソッドもあって漏れがち   •  CropImage   •  TransformImage   •  これらの戻り値も Destroy  しないとリークする  

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

 メモリ管理を色々と修正 (1/3) •  フォント名一覧取得でメモリリークしたので調査   •  同様のメモリリークがあちこちにあった   –  文字列のリストを取得する系メソッドが大体ダメ   –  ImageMagick  (MagickCore/MagickWand)  の中で AcquireMagickMemory(ImageMagick  のメモリ管理)で 取得したメモリを、標準の free  で解放してた   •  RelinquishMemory  を使うべき  

Slide 31

Slide 31 text

 メモリ管理を色々と修正 (2/3) •  全部の    メソッドを    調べた   •  O  以外は    不具合あり  

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

蛇足 •  表にできないけど大規模サイトに試験導入し てて、かなり安定して動いてる   •  GoImagick作者がニュージーランドの人なの で時差が少なくて、やりとりが楽   – 時差関係なく作者の反応良いです

Slide 34

Slide 34 text

まとめ •  GoImagick  は ImageMagick  Wand  API  のラッパー   •  import  先が最近変わったので注意   •  IniRlize をはじめに一度だけ実行する   –  MagickWand で画像を処理する   –  DrawingWand  で文字を描画する   –  PixelWand  で文字の色を指定する   •  MagickWand  等の回収は  GC  時に勝手にやってくれる事になった よ!   •  これで、GoImagick  で何かあっても自分でデバッグ出来るはず!   Let’s  Try!