Share
Embed
Transcript
40 slides
-
自己紹介 (@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 は触り始めて一年ちょっと ? -
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) uWliWes 画像処理 本体は ココ 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 -
なぜ ImageMagick を使うのか? • Golang 標準で image パッケージあるよね? – 機能少ないし
対応形式は JPEG,GIF,PNG だけ • libpng や jpeglib を直接使わないの? – go-‐thumber がそうだけど cgo は難易度高い • 他にも画像変換ツールがあるのでは? – ImageMagick は困った時に検索で探しやすい • (恐らく人による。自分は ImageMagick が楽) -
ImageMagick を使う理由(2/2) • メジャーな画像フォーマットからマイナーなものま で100種類以上に対応してる • hAp://www.imagemagick.org/script/formats.php png jpeg
gif inline sixel webp svg pdf 超メジャー 最近の キワモノ系 (Webの base64画像とか) ベクター画像 dcm 医療系 (DICOM等) -
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" -
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") } -
ResizeImage(640,480,… 実行 • 縦横のアスペクト比が… – サムネール画像としてはNG
$ go run resize640x480.go gopher.png 250px 340px 480px 640px ふくよかな Gopher! -
アスペクト比を保つ方法(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 -
アスペクト比を保つ方法(2/2) • 出力サイズを変えない – マージンをつける (内接) • 480 ×
(250/340) • => 352x480 – クロップする (外接) • 640 × (340/250) • => 640x870 • ResizeImage だけでは無理 250px 340px 480px 870px 640px 640px 352px 480px -
マージン(内接)の方法 • 描画領域を広げる (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 -
クロップ(外接)の方法(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 -
クロップ(外接)の方法(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 -
マージンやクロップの注意点 • マージンをどこに つけるか、どこを クロップするか は画像次第 250px
340px 480px 870px 640px 640px 352px 480px 640px 352px 870px 480px -
画像合成 • CompositeImage で合成できる _ = mw1.ReadImage(“gopher.png”) _
= mw2.ReadImage(“blind.png”) _ = mw1.CompositeImage(mw2, imagick.COMPOSITE_OP_OVER, 45, 28) CompositeImage gopher.png blind.png -
文字入れ (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) -
文字入れ (2/5) • PixelWand で色を表現 • DrawingWand で色と文字を設定する
pw := imagick.NewPixelWand() defer pw.Destroy() _ = pw.SetColor(”rgb(0, 0, 0)”) dw.SetFillColor(pw) dw.AnnotaWon(0, 0, “Gopher!!”) -
文字入れ (3/5) • DrawingWand の文字を MagickWand の画像に 描画
• (0,0)を基準に文字を貼るので殆ど見えない – 見えてるのは p の下にはみ出た部分 • Gravity 方式で配置しよう _ = mw.DrawImage(dw) あれれ? -
文字入れ (4/5) • CENTER 指定と SOUTH 指定 dw.setGravity(imagick.GRAVITY_CENTER)
dw.AnnotaWon(0, 0, “Gopher!!!”) mw.DrawImage(dw) GRAVITY_SOUTH -
文字入れ (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”) -
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() -
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 本体 -
ところで • hAp://imagemagick.org/script/api.php#go • なぜ GoImagick と ImageMagick本家で説明 に食い違いがあるのか。
• なぜ GoImagick と ImageMagick本家で説明 に食い違いがあるのか – GoImagick ⇨ MagickWand – ImageMagick本家 ⇨ MagickWand + MagickCore -
MagickWand と MagickCore • おおまかな構造 ImageMagick MagickCore (magick)
coders MagickWand (wand) uWliWes 画像処理 本体は ココ PerlMagick PHP imagick convert コマンドはここ 使い易くする 為のAPI GoImagick ??? JPEGやPNG の入出力 -
つまりどういう事? • 利用する関数は MagickWand だけ – MagickCore ではない (型の取り込みで include
し てるだけ) • PerlMagick より PHP imagick のサンプルが参 考になるという事 -
最近のトピック • メモリ管理を色々と修正 (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 -
メモリ管理を色々と修正 (1/3) • フォント名一覧取得でメモリリークした – 似たような漏れが他にもあるのでは? – Malloc
してる箇所が見当たらないのに、free してるけ どそのポインタは大丈夫なの? • 関連するバグを調査 – 似たようなリークがあちこちにあった • 文字列のリストを取得する系メソッドが大体ダメ – Wand API の中で AcquireMagickMemory(ImageMagick のメモリ管理)で 取得したメモリを、標準の free で解放してた • RelinquishMemory を使うべき -
メモリ管理を色々と修正 (3/3) • 結構大量に修正 – 余計な修正をcommitしちゃったけどマージしてく れた
– CREDITS に名前載せてくれた やさしい -
Magick.IniWalize() に Mutex • Magick.IniWalize() や Terminate() をマルチス レッドで呼ぶと競合するので Mutex
をかけた い • Sync.once で IniWalize を一度だけ呼べばよく ない? • でもユーザに気をつけろというより仕組みを 入れた方がよくない? • 協議続行中 -
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 } -
GCに任せる(2/2) • defer で destroy する問題点 (一般論) – 毎回 defer
書くのは面倒だし忘れたりする – 関数の外に return 出来ない – 明示的に new するものはまだ良いけど、新しく MagickWand を返すメソッドもあって漏れがち • CropImage • TransformImage • これらの戻り値も Destroy しないとリークする • defer mw.Destroy といちいち書かなくてもよく なると嬉しい! -
まとめ • MagickWand で画像を処理する • DrawingWand で文字を描画する •
PixelWand で文字の色を指定する • defer mw.Destroy() を忘れずに – CropImage や TransformImage が返すのも Destroy() をお 忘れずに • ここまでの話を聞けば、Golang でサムネール画像を 作れるはず – Let’s try!