Share
Embed
Transcript
34 slides
-
自己紹介 (@yoya) • プロフィール – hAps://osdn.jp/users/yoya/ (フリーランスなのでお仕事下さい)
• ImageMagick のストーカーしてます – hAp://d.hatena.ne.jp/yoya/searchdiary?word=ImageMagick • PHP でバイナリ弄るのが持ちネタ • Golang は触り始めて一年ちょっと ? -
もくじ • GoImagick とは • セッティング • 使い方
• 内部の話 • 最近のトピック • 蛇足 • まとめ -
GoImagick とは • hAps://github.com/gographics/imagick • MagickWand API (C言語)の Go
バインディング – Go 言語で ImageMagick の機能が使えます -
公式サイトからもリンク • hAp://imagemagick.org/script/api.php#go – MagickWand と MagickCore の Go
バインディング • GoImagick は MagickCore の定義を取り入れ るけど、関数は MagickWand だけ橋渡し 間違い -
MagickWand と MagickCore • GoImagick は MagickWand の関数を使う ImageMagick
MagickCore (magick) coders MagickWand (wand) uRliRes 画像処理 本体は ココ PerlMagick PHP imagick convert コマンドはここ 使い易くする 為のAPI GoImagick JPEGやPNG の入出力 -
つまり? • convert コマンドと PHP imagick のコードを見 れば GoImagick の使い方が分かる
– convert コマンド • wand/mogrify.c – PHP imagick • hAp://php.net/manual/ja/imagick.transformimage.php 似た名前と引数の メソッドが GoImagick に もあると期待出来る -
ここから本題 • GoImagick のセッティング • GoImagick の使い方 •
GoImagick の内部 – pkg-‐config – cgo -
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” -
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 コマンドで決める -
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 -
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") } -
GoImagick の使い方 (2/3) • 縦横のアスペクト比が… – サムネール画像としてはNG
$ go run resize640x480.go gopher.png 250px 340px 480px 640px ふくよかな Gopher! -
GoImagick の使い方(3/3) • サムネール画像作成の詳細はこちら – hAps://speakerdeck.com/yoya/goimagickthumbnail • 今回は
GoImagick 自体の解説 (残り3分で) -
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 本体 -
動作環境の構築(初期化) • ImageMagick が動作する環境を用意する MagickCore (ImageMagick のコア部) <module名>Genesis
(セマフォ用意) MagickWandGenesis 環境変数 取り込み SIG〜ハンドラ 設定 MagickCoreGenesis -
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 <wand/MagickWand.h> */ import "C" Cの関数を呼ぶ のに必要 -
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{} } -
ユーザはどうすれば良い? • プログラムのはじめに一度だけ呼べば良い • ライブラリとして用意する場合は別 – ライブラリの機能を使い終わったら imagick.Terminate() で動作環境を消せるようにし
た方がコンピュータに優しい var imagick_iniRalized = func() bool { imagick.IniRalize() return true }() -
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 文字入れは これらが必須 文字入れは これらが必須 -
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 を 動かさない -
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 は呼ばせない -
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の関数に渡す -
最近のトピック • 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 -
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") } -
GCに任せる(2/3) • defer で destroy する問題点 (一般論) – 毎回 defer
書くのは面倒だし忘れるかも? – 関数の外に return 出来ない – New〜 じゃないのに新しく作った MagickWand を 返すメソッドもあって漏れがち • CropImage • TransformImage • これらの戻り値も Destroy しないとリークする -
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! -
メモリ管理を色々と修正 (1/3) • フォント名一覧取得でメモリリークしたので調査 • 同様のメモリリークがあちこちにあった – 文字列のリストを取得する系メソッドが大体ダメ
– ImageMagick (MagickCore/MagickWand) の中で AcquireMagickMemory(ImageMagick のメモリ管理)で 取得したメモリを、標準の free で解放してた • RelinquishMemory を使うべき -
メモリ管理を色々と修正 (2/3) • 全部の メソッドを 調べた •
O 以外は 不具合あり -
メモリ管理を色々と修正 (3/3) • 結構大量に修正 – 余計な修正をcommitしちゃったけどマージしてく れた
– CREDITS に名前載せてくれた やさしい -
まとめ • GoImagick は ImageMagick Wand API のラッパー •
import 先が最近変わったので注意 • IniRlize をはじめに一度だけ実行する – MagickWand で画像を処理する – DrawingWand で文字を描画する – PixelWand で文字の色を指定する • MagickWand 等の回収は GC 時に勝手にやってくれる事になった よ! • これで、GoImagick で何かあっても自分でデバッグ出来るはず! Let’s Try!