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. GoImagick  でサムネール作成  
    〜  Golang  &  ImageMagick  〜
    2016年4月8日(金)  
    “よや”  [email protected]

    View Slide

  2. 自己紹介 (@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  は触り始めて一年ちょっと

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10.  
     
    ここから本題

    View Slide

  11. 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"

    View Slide

  12. 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")  
    }  

    View Slide

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

    View Slide

  14. アスペクト比を保つ方法(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

    View Slide

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

    View Slide

  16. マージン(内接)の方法
    •  描画領域を広げる (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  

    View Slide

  17. クロップ(外接)の方法(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

    View Slide

  18. クロップ(外接)の方法(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

    View Slide

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

    View Slide

  20. 画像合成  

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

    View Slide

  21. 文字入れ  (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)  

    View Slide

  22. 文字入れ  (2/5)  

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

    View Slide

  23. 文字入れ  (3/5)  

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

    View Slide

  24. 文字入れ  (4/5)  

    •  CENTER 指定と SOUTH  指定
    dw.setGravity(imagick.GRAVITY_CENTER)  
    dw.AnnotaWon(0,  0,  “Gopher!!!”)  
    mw.DrawImage(dw)  
    GRAVITY_SOUTH  

    View Slide

  25. 文字入れ  (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”)  

    View Slide

  26. 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()  

    View Slide

  27. 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  本体

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. 最近のトピック
    •  メモリ管理を色々と修正 (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  

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. 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  
    }  

    View Slide

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

    View Slide

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

    View Slide