YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

GoのimageパッケージでGopherをぐるぐる回そう

はじめに

こんにちは、Go界のスタンリー・キューブリックです。このエントリはGo Advent Calendar 2013の22日目のエントリです。

2013年はGoカンファレンスも2回開催できましたし、私としてもかなりGoの広まりを実感出来た1年でした。来年はもっと多くの方にGoを使ってもらえたらと思いつつ、さらにプロダクション事例などを期待しています。

さて、Goなんですが、ネイティブにビルドされるとか、並列化が簡単にできるとか、そういうところがよく取り上げられますが、個人的にはimageパッケージを標準で持っているのが大きいと思っています。ということで、今回はimageパッケージを使ってアニメーションGIFを作ってみたいと思います。

imageパッケージとは

名前のとおり画像データを簡単に扱えるようにしたパッケージで、Go1.2の時点ではGIF、JPEG、PNGなどを簡単に読み込み・書き出し出来るようになっています。今回はPNG画像を読み込んで、アニメーションGIFにするということをやってみたいと思います。

まずこの可愛らしいGopherちゃんの画像を用意します。今回はサンプルということで、正方形の画像を用意しました。

f:id:ymotongpoo:20131222232717p:plain

クリスマスなので、このGopherちゃんがぐるぐると回転するアニメーションGIFができたらカワイイと思いませんか?思いますね!?じゃあ実際にぐるぐる回してみましょう!!

簡単に使い方解説

画像データの読み込み・書き込み

Goっぽく、まずはファイルを読み込んで(io.Readerを用意)して、そのあとDecoderに渡す、という流れです。

// Open source PNG file.
file, err := os.Open("image/gopher.png")
if err != nil {
    panic(err)
}
defer file.Close()

data, err := png.Decode(file)
if err != nil {
    panic(err)
}

逆に書き込みは画像データと書き込み先のio.Writerを用意しておいて、Encodeに渡すという流れ。

// Dump image data into file.
file, err = os.Create("image/rotate-gopher.gif")
if err != nil {
    panic(err)
}
defer file.Close()

err = gif.EncodeAll(file, &dst)
if err != nil {
    panic(err)
}

画像データの操作

読み込みと書き出しがわかったので、肝心の画像データの操作についてですが、扱う画像形式によって必要となる画像データ型が異なります。(image.Image、image.Palettedなど)ですが基本的には、領域を用意し(image.Rectangle)、色データを操作する(xxx.Setメソッド)という流れになります。

original := image.NewPaletted(r, palette.WebSafe)
for x := r.Min.X; x < r.Max.X; x++ {
    for y := r.Min.Y; y < r.Max.Y; y++ {
        original.Set(x, y, data.At(x, y))
    }
}
dst.Image = append(dst.Image, original)

たったこれだけで画像が扱えるんです。簡単!!ただし、現状では画像の入出力系の基本的な操作しか出来ないので、アフィン変換などは自分で実装する必要があります。では実際に作ってみた画像を見てください!

f:id:ymotongpoo:20131222234948g:plain

回っていますね!

これであなたもローリングGopherをいつでも作れます!今年のクリスマスはぜひローリングGopherちゃんと戯れてください!次はyanolabさんです。

サンプルコード全体

package main

import (
    "image"
    "image/color/palette"
    "image/gif"
    "image/png"
    "log"
    "os"
)

func main() {
    // Open source PNG file.
    file, err := os.Open("image/gopher.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    data, err := png.Decode(file)
    if err != nil {
        panic(err)
    }

    // Define destination boundary. Expecting original image is square.
    r := data.Bounds()

    // Prepare distination image buffer.
    dst := gif.GIF{
        Image: []*image.Paletted{},
    }

    // Rotate original image and store them into destination.
    original := image.NewPaletted(r, palette.WebSafe)
    for x := r.Min.X; x < r.Max.X; x++ {
        for y := r.Min.Y; y < r.Max.Y; y++ {
            original.Set(x, y, data.At(x, y))
        }
    }
    dst.Image = append(dst.Image, original)

    clockwise := image.NewPaletted(r, palette.WebSafe)
    for x := r.Min.X; x < r.Max.X; x++ {
        for y := r.Min.Y; y < r.Max.Y; y++ {
            clockwise.Set(x, y, data.At(-y+r.Max.Y, x))
        }
    }
    dst.Image = append(dst.Image, clockwise)

    upsidedown := image.NewPaletted(r, palette.WebSafe)
    for x := r.Min.X; x < r.Max.X; x++ {
        for y := r.Min.Y; y < r.Max.Y; y++ {
            upsidedown.Set(x, y, data.At(-y+r.Max.Y, -x+r.Max.X))
        }
    }
    dst.Image = append(dst.Image, upsidedown)

    counterclockwise := image.NewPaletted(r, palette.WebSafe)
    for x := r.Min.X; x < r.Max.X; x++ {
        for y := r.Min.Y; y < r.Max.Y; y++ {
            counterclockwise.Set(x, y, data.At(x, -y+r.Max.X))
        }
    }
    dst.Image = append(dst.Image, counterclockwise)

    // Post process
    dst.Delay = make([]int, len(dst.Image))
    dst.LoopCount = 100

    // Dump image data into file.
    file, err = os.Create("image/rotate-gopher.gif")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    err = gif.EncodeAll(file, &dst)
    if err != nil {
        panic(err)
    }
    log.Println("wrote out rotate-gopher.gif")
}