今回は、Goで2Dグラフィックスを描画するために、パッケージの選定と、その中から実際にggを使って描画してみました。
Golangで画像描画するために、まず初めに検討したのは x/image でした。しかし、描画機能はあるものの、図形を描画したり、塗りつぶしたりするような機能はなく、扱うのがかなり面倒そうでした。
そこで、その他のパッケージを探したところ、ggとdraw2dを見つけました。
ggは、2Dグラフィックスを簡単に描画できるパッケージで、初めから線・短形・円といった図形を描画できるほか、塗りつぶしやグラデーションをかけることもでき、とにかく簡単に図形描画できるのが特徴です。
draw2dも、ggと同じような描画機能を持っているのですが、特徴としてはベクター描画のライブラリであることです。SVGやPDFで書き出すことかできるので、ラスター画像に比べてより軽量なグラフィックを描画することができます。
この2つの中から、今回は今回はggを選択しました。理由は、今回の調査をするにあたり、golangで書かれたgenerativeartを見ていたのですが、このプロジェクトが画像描画にggを使っていたためです。
go getでggを追加します。
$ go get github.com/fogleman/gg
まず、基本的な画像描画の流れを知るために、簡単な図形描画をやってみました。
package main import ( "github.com/fogleman/gg" ) func main() { dc := gg.NewContext(1000, 1000) // 背景の描画 dc.DrawRectangle(0, 0, 1000, 1000) dc.SetRGB(1, 1, 1) dc.Fill() dc.DrawCircle(400, 400, 300) dc.SetRGB(0, 0, 1) dc.Fill() dc.DrawRectangle(100, 100, 200, 200) dc.SetRGB(1, 1, 0) dc.Fill() dc.SavePNG("out.png") }
すると、以下の様な画像が生成できます。
基本的な描画の流れとしては、描画コンテキストを生成し、描画コンテキストのメソッドを使って図形を描画していきます。また、描画に関しては、 DrawXXX
系のメソッドで何をどこに描画するかを指定し、 SetRGB
でカラー指定、 Fill()
や Storoke()
で描画方法を指定します。
基本的な描画方法がわかったところで、乱数を使った図形描画をやってみました。ただ描画するだけでは面白くないので、bildを使い、合成モード Addを使って描画してみます。
まず、bildを追加します。
$ go get github.com/anthonynsimon/bild
次に、コードを書いていきます。
package main import ( "image" "image/png" "math/rand" "os" "github.com/anthonynsimon/bild/blend" "github.com/fogleman/gg" ) func main() { rand.Seed(0) dc := gg.NewContext(1000, 1000) dc.DrawRectangle(0, 0, 1000, 1000) dc.SetRGB(0, 0, 0) dc.Fill() img := dc.Image() img = pt1(img) save("pt1.png", img) } func pt1(base image.Image) image.Image { bg := base for i := 0; i < 100; i++ { dc := gg.NewContext(1000, 1000) dc.DrawCircle(float64(rand.Intn(1000)), float64(rand.Intn(1000)), float64(rand.Intn(100))) dc.SetRGBA(rand.Float64(), rand.Float64(), rand.Float64(), 0.8) dc.Fill() fg := dc.Image() bg = blend.Add(bg, fg) } return bg } func save(filePath string, img image.Image) { imgFile, err := os.Create(filePath) defer imgFile.Close() if err != nil { log.Println("Cannot create file:", err) } png.Encode(imgFile, img) }
以下の様な画像が生成されました。合成モードaddを使っているため、色が重なったところは明るくなっています。
さらに、ラインを使った図形も合成してみたいと思います。こちらは一気に描画してから、先ほどの画像に合成モード difference で上に合成します。
package main import ( "image" "image/png" "log" "math/rand" "os" "github.com/anthonynsimon/bild/blend" "github.com/fogleman/gg" ) func main() { rand.Seed(0) dc := gg.NewContext(1000, 1000) dc.DrawRectangle(0, 0, 1000, 1000) dc.SetRGB(0, 0, 0) dc.Fill() img := dc.Image() img = pt1(img) img = pt2(img) save("pt2.png", img) } func sample1() { dc := gg.NewContext(1000, 1000) // 背景の描画 dc.DrawRectangle(0, 0, 1000, 1000) dc.SetRGB(1, 1, 1) dc.Fill() dc.DrawCircle(400, 400, 300) dc.SetRGB(0, 0, 1) dc.Fill() dc.DrawRectangle(100, 100, 200, 200) dc.SetRGB(1, 1, 0) dc.Fill() dc.SavePNG("out.png") } func pt1(base image.Image) image.Image { bg := base for i := 0; i < 100; i++ { dc := gg.NewContext(1000, 1000) dc.DrawCircle(float64(rand.Intn(1000)), float64(rand.Intn(1000)), float64(rand.Intn(100))) dc.SetRGBA(rand.Float64(), rand.Float64(), rand.Float64(), 0.8) dc.Fill() //dc.Stroke() fg := dc.Image() bg = blend.Add(bg, fg) } return bg } func pt2(base image.Image) image.Image { dc := gg.NewContext(1000, 1000) for i := 0; i <= 1000; i += 25 { dc.DrawLine(0, 0, float64(i), 1000) dc.SetLineWidth(5) dc.SetRGB(1, 1, 1) dc.Stroke() dc.DrawLine(0, 1000, float64(i), float64(i)) dc.SetLineWidth(5) dc.SetRGB(1, 1, 1) dc.Stroke() } return blend.Difference(base, dc.Image()) } func save(filePath string, img image.Image) { imgFile, err := os.Create(filePath) defer imgFile.Close() if err != nil { log.Println("Cannot create file:", err) } png.Encode(imgFile, img) }
以下の様な画像が生成されます。
ggとbildを使って2Dグラフィックスの描画をやってみました。bildを組み合わせることで、単純な描画だけでなく、合成モードを指定して描画することもできたので、ちょっとした表現であれば合成モードを組み合わせて出来そうだなと思いました。