The Go Blog
Go インターフェースの実践:GIF デコーダー
はじめに
2011年5月10日にサンフランシスコで開催された Google I/O カンファレンスで、Go 言語が Google App Engine で利用可能になったことを発表しました。 Go は、App Engine で利用可能になった最初のマシンコードに直接コンパイルされる言語であり、画像操作などの CPU 集約型のタスクに適しています。
その流れで、Moustachio というプログラムをデモンストレーションしました。このプログラムは、次のような画像を

口ひげを追加して結果を共有することで、簡単に改善できます

アンチエイリアスされた口ひげのレンダリングを含むすべてのグラフィック処理は、App Engine で実行されている Go プログラムによって行われます。(ソースは appengine-go プロジェクト で入手できます。)
Web 上のほとんどの画像(少なくとも口ひげを生やす可能性のある画像)は JPEG ですが、他にも無数のフォーマットが流通しており、Moustachio がアップロードされた画像をいくつかのフォーマットで受け入れるのは妥当と思われました。Go 画像ライブラリには既に JPEG と PNG デコーダーが存在していましたが、由緒ある GIF フォーマットは存在していなかったため、発表に合わせて GIF デコーダーを作成することにしました。そのデコーダーには、Go のインターフェースがいくつかの問題の解決を容易にする方法を示すいくつかの部分があります。このブログ記事の残りの部分では、いくつかのインスタンスについて説明します。
GIF フォーマット
まず、GIF フォーマットの概要を簡単に説明します。GIF 画像ファイルは*パレット化*されています。つまり、各ピクセル値は、ファイルに含まれる固定カラーマップへのインデックスです。GIF フォーマットは、ディスプレイのピクセルあたり通常 8 ビット以下の時代のものであり、カラーマップを使用して限られた値のセットを画面を点灯させるために必要な RGB(赤、緑、青)トリプルに変換していました。(これは、たとえば、エンコーディングが個別のカラー信号を個別に表すため、カラーマップを持たない JPEG と対照的です。)
GIF 画像には、ピクセルあたり 1〜8 ビット(両端を含む)を含めることができますが、ピクセルあたり 8 ビットが最も一般的です。
多少簡略化すると、GIF ファイルには、ピクセル深度と画像の寸法を定義するヘッダー、カラーマップ(8 ビット画像の場合は 256 の RGB トリプル)、ピクセルデータが含まれています。ピクセルデータは、LZW アルゴリズムを使用して圧縮された一次元ビットストリームとして保存されます。これは、コンピューターで生成されたグラフィックスには非常に効果的ですが、写真画像にはあまり適していません。圧縮されたデータは、1 バイトのカウント(0〜255)とその多くのバイトが続く長さで区切られたブロックに分割されます

ピクセルデータのブロック解除
Go で GIF ピクセルデータをデコードするには、`compress/lzw` パッケージの LZW デコンプレッサーを使用できます。ドキュメントに記載されているように、「r から読み取られたデータを解凍することによって読み取りを満たす」オブジェクトを返す `NewReader` 関数があります。
func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser
ここで、`order` はビットパッキング順序を定義し、`litWidth` はビット単位のワードサイズであり、GIF ファイルの場合はピクセル深度(通常は 8)に対応します。
しかし、デコンプレッサーはバイトのストリームを必要としますが、GIF データはアンパックする必要があるブロックのストリームであるため、入力ファイルを最初の引数として `NewReader` に渡すことはできません。この問題に対処するために、入力 `io.Reader` をブロック解除するためのコードでラップし、そのコードを再び `Reader` を実装するようにすることができます。言い換えれば、ブロック解除コードを `blockReader` と呼ぶ新しいタイプの `Read` メソッドに配置します。
`blockReader` のデータ構造体を次に示します。
type blockReader struct {
r reader // Input source; implements io.Reader and io.ByteReader.
slice []byte // Buffer of unread data.
tmp [256]byte // Storage for slice.
}
リーダー `r` は、画像データのソースであり、おそらくファイルまたは HTTP 接続です。`slice` と `tmp` フィールドは、ブロック解除の管理に使用されます。`Read` メソッド全体を次に示します。Go でのスライスと配列の使用法の良い例です。
1 func (b *blockReader) Read(p []byte) (int, os.Error) {
2 if len(p) == 0 {
3 return 0, nil
4 }
5 if len(b.slice) == 0 {
6 blockLen, err := b.r.ReadByte()
7 if err != nil {
8 return 0, err
9 }
10 if blockLen == 0 {
11 return 0, os.EOF
12 }
13 b.slice = b.tmp[0:blockLen]
14 if _, err = io.ReadFull(b.r, b.slice); err != nil {
15 return 0, err
16 }
17 }
18 n := copy(p, b.slice)
19 b.slice = b.slice[n:]
20 return n, nil
21 }
2〜4 行目は単なる健全性チェックです。データを配置する場所がない場合は、ゼロを返します。これは決して起こるべきではありませんが、安全のために良いことです。
5行目は、`b.slice`の長さをチェックすることで、前回の呼び出しから残っているデータがあるかどうかを尋ねます。そうでない場合、スライスの長さはゼロになり、`r`から次のブロックを読み取る必要があります。
GIFブロックは、6行目で読み取られるバイトカウントで始まります。カウントがゼロの場合、GIFはこれを終端ブロックとして定義するため、11行目で`EOF`を返します。
これで`blockLen`バイトを読み取る必要があることがわかったので、`b.slice`を`b.tmp`の最初の`blockLen`バイトにポイントし、ヘルパー関数`io.ReadFull`を使用してその数のバイトを読み取ります。その関数は、正確にその数のバイトを読み取ることができない場合にエラーを返します。これは決して起こるべきではありません。そうでない場合は、読み取る準備ができている`blockLen`バイトがあります。
18〜19行目は、`b.slice`から呼び出し側のバッファにデータをコピーします。`ReadFull`ではなく`Read`を実装しているため、要求されたバイト数よりも少ないバイト数を返すことができます。そのため、`b.slice`から呼び出し側のバッファ(`p`)にデータをコピーするだけで簡単です。`copy`からの戻り値は転送されたバイト数です。次に、`b.slice`を再スライスして最初の`n`バイトを削除し、次の呼び出しに備えます。
Goプログラミングでは、スライス(`b.slice`)を配列(`b.tmp`)に結合するのは良いテクニックです。この場合、`blockReader`タイプの`Read`メソッドは割り当てを行わないことを意味します。また、カウントを保持する必要がなく(スライスの長さに暗黙的に含まれています)、組み込みの`copy`関数は、コピーする必要がある以上のものをコピーしないことを保証します。(スライスについて詳しくは、Goブログのこの記事をご覧ください。)
`blockReader`タイプが与えられれば、次のように入力リーダー(たとえば、ファイル)をラップするだけで、画像データストリームのブロックを解除できます。
deblockingReader := &blockReader{r: imageFile}
このラッピングにより、ブロック区切りのGIF画像ストリームが、`blockReader`の`Read`メソッドの呼び出しによってアクセスできる単純なバイトストリームに変換されます。
ピースの接続
`blockReader`が実装され、ライブラリからLZWコンプレッサーが利用可能になったので、画像データストリームをデコードするために必要なすべてのピースが揃いました。コードから直接この雷鳴でそれらを縫い合わせます
lzwr := lzw.NewReader(&blockReader{r: d.r}, lzw.LSB, int(litWidth))
if _, err = io.ReadFull(lzwr, m.Pix); err != nil {
break
}
それだけです。
最初の行は`blockReader`を作成し、それを`lzw.NewReader`に渡してデコンプレッサーを作成します。ここで、`d.r`は画像データを保持する`io.Reader`であり、`lzw.LSB`はLZWデコンプレッサーのバイト順序を定義し、`litWidth`はピクセル深度です。
デコンプレッサーが与えられると、2行目は`io.ReadFull`を呼び出してデータを解凍し、画像`m.Pix`に保存します。`ReadFull`が戻ると、画像データが解凍され、表示の準備ができた画像`m`に保存されます。
このコードは初めて動作しました。本当に。
`NewReader`呼び出しを`blockReader`を`NewReader`の呼び出し内に構築したのと同じように`ReadFull`の引数リストに配置することで、一時変数`lzwr`を回避できますが、それは1行のコードに詰め込みすぎかもしれません。
結論
Goのインターフェースにより、このようにピースパーツを組み立ててデータを再構築することにより、ソフトウェアを簡単に構築できます。この例では、タイプセーフなUnixパイプラインと同様に、`io.Reader`インターフェースを使用してデブロッカーとデコンプレッサーをチェーン接続することにより、GIFデコードを実装しました。また、デブロッカーを`Reader`インターフェースの(暗黙の)実装として記述しました。これにより、処理パイプラインに適合させるための追加の宣言やボイラープレートは必要ありませんでした。ほとんどの言語でこのデコーダーを इतनीコンパクトでありながらクリーンかつ安全に実装することは困難ですが、インターフェースメカニズムと einige の規則により、Goではほとんど当然のことになります。
それは別の絵に値します、今回はGIFです

GIFフォーマットは、http://www.w3.org/Graphics/GIF/spec-gif89a.txtで定義されています。
次の記事:外部Goライブラリに注目
前の記事:Google I/O 2011でのGo:ビデオ
ブログインデックス