Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GoImagickThumbnail

yoya
April 08, 2016

 GoImagickThumbnail

GoImagickでサムネール作成

yoya

April 08, 2016
Tweet

More Decks by yoya

Other Decks in Programming

Transcript

  1. 自己紹介 (@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  は触り始めて一年ちょっと ?
  2. GoImagick とは •  hAps://github.com/gographics/imagick   •  MagickWand  API  (C言語)の Go

     バインディング   –  Go  言語で ImageMagick  の機能が使えます  
  3. 公式サイトからもリンク •  hAp://imagemagick.org/script/api.php#go     •  MagickWand  と MagickCore  の

    Go  バインディ ング   – GoImagick はMagickCore の定義を取り入れるけ ど、関数は MagickWand  のだけ使います。  
  4. MagickWand と MagickCore •  GoImagick は MagickWand の関数を使う ImageMagick  

    MagickCore   (magick) coders MagickWand   (wand) uWliWes   画像処理   本体は   ココ PerlMagick PHP  imagick convert   コマンドはここ 使い易くする   為のAPI GoImagick JPEGやPNG   の入出力
  5. つまり? •  convert  コマンドと PHP  imagick  のコードを見 ると GoImagick  の使い方も分かる

      – convert  コマンド   •  wand/mogrify.c   – PHP  imagick   •  hAp://php.net/manual/ja/imagick.transformimage.php  
  6. なぜ ImageMagick  を使うのか? •  Golang  標準で image  パッケージあるよね?   – 機能少ないし

    対応形式は JPEG,GIF,PNG  だけ   •  libpng  や jpeglib  を直接使わないの?   – go-­‐thumber  がそうだけど cgo  は難易度高い     •  他にも画像変換ツールがあるのでは?   – ImageMagick  は困った時に検索で探しやすい   •  (恐らく人による。自分は ImageMagick  が楽)  
  7. ImageMagick  を使う理由(2/2) •  メジャーな画像フォーマットからマイナーなものま で100種類以上に対応してる   •  hAp://www.imagemagick.org/script/formats.php png jpeg

    gif inline sixel webp svg pdf 超メジャー 最近の キワモノ系   (Webの  base64画像とか) ベクター画像 dcm 医療系   (DICOM等)
  8. 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"
  9. 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")   }  
  10. ResizeImage(640,480,…  実行   •  縦横のアスペクト比が…   – サムネール画像としてはNG      

     $  go  run  resize640x480.go  gopher.png   250px 340px 480px 640px ふくよかな   Gopher!
  11. アスペクト比を保つ方法(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
  12. アスペクト比を保つ方法(2/2) •  出力サイズを変えない   – マージンをつける (内接)   •  480  ×

     (250/340)   •  =>  352x480   – クロップする  (外接)   •  640  ×  (340/250)     •  =>  640x870   •  ResizeImage  だけでは無理       250px 340px 480px 870px 640px 640px 352px 480px
  13. マージン(内接)の方法 •  描画領域を広げる (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  
  14. クロップ(外接)の方法(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
  15. クロップ(外接)の方法(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
  16. 画像合成   •  CompositeImage で合成できる _  =  mw1.ReadImage(“gopher.png”)   _

     =  mw2.ReadImage(“blind.png”)   _  =  mw1.CompositeImage(mw2,  imagick.COMPOSITE_OP_OVER,  45,  28)   CompositeImage gopher.png blind.png
  17. 文字入れ  (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)  
  18. 文字入れ  (2/5)   •  PixelWand で色を表現   •  DrawingWand  で色と文字を設定する

            pw  :=  imagick.NewPixelWand()   defer  pw.Destroy()   _  =  pw.SetColor(”rgb(0,  0,  0)”)   dw.SetFillColor(pw)   dw.AnnotaWon(0,  0,  “Gopher!!”)  
  19. 文字入れ  (3/5)   •  DrawingWand  の文字を MagickWand  の画像に 描画  

    •  (0,0)を基準に文字を貼るので殆ど見えない   –  見えてるのは p  の下にはみ出た部分   •  Gravity 方式で配置しよう _  =  mw.DrawImage(dw)   あれれ?
  20. 文字入れ  (4/5)   •  CENTER 指定と SOUTH  指定 dw.setGravity(imagick.GRAVITY_CENTER)  

    dw.AnnotaWon(0,  0,  “Gopher!!!”)   mw.DrawImage(dw)   GRAVITY_SOUTH  
  21. 文字入れ  (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”)  
  22. 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()  
  23. 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  本体
  24. ところで •  hAp://imagemagick.org/script/api.php#go   •  なぜ GoImagick  と ImageMagick本家で説明 に食い違いがあるのか。

      •  なぜ GoImagick  と ImageMagick本家で説明 に食い違いがあるのか   – GoImagick  ⇨  MagickWand   – ImageMagick本家 ⇨  MagickWand  +  MagickCore
  25. MagickWand と MagickCore •  おおまかな構造 ImageMagick   MagickCore   (magick)

    coders MagickWand   (wand) uWliWes   画像処理   本体は   ココ PerlMagick PHP  imagick convert   コマンドはここ 使い易くする   為のAPI GoImagick ??? JPEGやPNG   の入出力
  26. つまりどういう事? •  利用する関数は MagickWand だけ   – MagickCore  ではない (型の取り込みで include

     し てるだけ)   •  PerlMagick  より PHP  imagick  のサンプルが参 考になるという事
  27. 最近のトピック •  メモリ管理を色々と修正 (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  
  28.  メモリ管理を色々と修正 (1/3) •  フォント名一覧取得でメモリリークした   –  似たような漏れが他にもあるのでは?   –  Malloc

     してる箇所が見当たらないのに、free  してるけ どそのポインタは大丈夫なの?   •  関連するバグを調査   –  似たようなリークがあちこちにあった   •  文字列のリストを取得する系メソッドが大体ダメ   –  Wand  API  の中で AcquireMagickMemory(ImageMagick  のメモリ管理)で 取得したメモリを、標準の free  で解放してた   •  RelinquishMemory  を使うべき  
  29. Magick.IniWalize()  に Mutex •  Magick.IniWalize()  や Terminate()  をマルチス レッドで呼ぶと競合するので Mutex

     をかけた い   •  Sync.once  で  IniWalize  を一度だけ呼べばよく ない?   •  でもユーザに気をつけろというより仕組みを 入れた方がよくない?   •  協議続行中
  30. 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   }  
  31. GCに任せる(2/2) •  defer  で destroy  する問題点 (一般論)   – 毎回  defer

     書くのは面倒だし忘れたりする   – 関数の外に  return  出来ない   – 明示的に new  するものはまだ良いけど、新しく MagickWand  を返すメソッドもあって漏れがち   •  CropImage   •  TransformImage   •  これらの戻り値も Destroy  しないとリークする   •  defer  mw.Destroy  といちいち書かなくてもよく なると嬉しい!  
  32. まとめ •  MagickWand で画像を処理する   •  DrawingWand  で文字を描画する   • 

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