[インデックス 16767] ファイルの概要
このコミットは、Go言語の標準ライブラリ image/gif
パッケージにGIF画像の書き込み(エンコード)機能を追加するものです。これまではGIF画像の読み込み(デコード)のみがサポートされていましたが、この変更により、Goプログラム内でGIF画像を生成し、ファイルやストリームに書き出すことが可能になります。
コミット
commit 9ebc5be39c5bad22ca0f97849d1ad475bacdc950
Author: Andrew Bonventre <andybons@chromium.org>
Date: Mon Jul 15 10:57:01 2013 +1000
image/gif: add writer implementation
R=r, nigeltao
CC=golang-dev
https://golang.org/cl/10896043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9ebc5be39c5bad22ca0f97849d1ad475bacdc950
元コミット内容
image/gif: add writer implementation
このコミットは、Goの image/gif
パッケージにGIF画像の書き込み(エンコード)機能を追加します。
変更の背景
Go言語の image
パッケージは、様々な画像フォーマットの読み書きをサポートすることを目的としています。このコミット以前は、image/gif
パッケージはGIF画像のデコード(読み込み)機能のみを提供していました。しかし、画像処理アプリケーションやツールにおいて、GIF画像を生成したり、既存の画像をGIF形式で保存したりするニーズは高く、エンコード機能の追加は自然な拡張でした。
特に、アニメーションGIFの生成は、Webアプリケーションやグラフィックツールにおいて重要な機能であり、このエンコーダの実装は、Goエコシステムにおける画像処理能力を大きく向上させるものでした。
前提知識の解説
GIF (Graphics Interchange Format)
GIFは、CompuServeによって開発されたビットマップ画像フォーマットです。特徴としては以下の点が挙げられます。
- インデックスカラー: 最大256色(2^8色)のパレットを使用します。各ピクセルはパレット内の色のインデックスで表現されます。
- 可逆圧縮: LZW (Lempel-Ziv-Welch) アルゴリズムを用いた可逆圧縮により、画質を損なわずにファイルサイズを削減します。
- アニメーション: 複数の画像を連続して表示することでアニメーションを表現できます。各フレームには表示遅延時間や透過色などの設定が可能です。
- 透過: 1色を透過色として指定できます。
- インターレース: 画像を段階的に表示するインターレース方式をサポートします。
LZW (Lempel-Ziv-Welch) 圧縮
LZWは、データ圧縮アルゴリズムの一種で、GIF、TIFF、PDFなどのフォーマットで広く利用されています。基本的な原理は、入力データストリームから繰り返し出現するパターン(シーケンス)を辞書に登録し、そのパターンをより短いコードに置き換えることで圧縮を実現します。
GIFにおけるLZW圧縮は、ピクセルデータを効率的に圧縮するために使用されます。エンコーダは、ピクセルデータのシーケンスを読み込み、辞書に基づいてコードを生成し、出力ストリームに書き込みます。デコーダは、これらのコードを読み込み、辞書を使って元のピクセルデータを再構築します。
Go言語の image
パッケージ
Go言語の標準ライブラリ image
パッケージは、画像データの抽象的な表現(image.Image
インターフェース)と、様々な画像フォーマット(JPEG, PNG, GIFなど)のエンコード/デコード機能を提供します。
image.Image
: 画像のピクセルデータと境界を表現するインターフェース。image.Paletted
: パレットカラー画像を表す構造体。GIF画像は通常この形式で扱われます。image/color.Palette
: 色の配列を表す型。インデックスカラー画像のパレットとして使用されます。image/draw
: 画像の描画操作(リサイズ、合成、色変換など)を行うためのパッケージ。特に、減色処理(クオンタイゼーション)やディザリング(誤差拡散)に利用されます。
技術的詳細
このコミットで追加されたGIFエンコーダは、GIF89a仕様に準拠しています。主な実装は src/pkg/image/gif/writer.go
にあり、以下の主要なコンポーネントと機能を含んでいます。
encoder
構造体: GIFエンコード処理のメインロジックをカプセル化します。io.Writer
インターフェースを介して出力ストリームにデータを書き込みます。writeHeader()
: GIFファイルのヘッダ(GIF89a
シグネチャ、論理スクリーン記述子、グローバルカラーテーブル)を書き込みます。グローバルカラーテーブルは、最初の画像のパレットに基づいて生成されます。アニメーションGIFの場合、NETSCAPE2.0
アプリケーション拡張ブロックも書き込まれ、ループカウントが設定されます。writeColorTable(p color.Palette, size int)
: 指定されたパレットをGIFのカラーテーブル形式で書き込みます。パレットサイズに合わせてパディングが行われます。writeImageBlock(pm *image.Paletted, delay int)
: 個々の画像フレーム(image.Paletted
)をGIFの画像記述子ブロックとして書き込みます。- グラフィックコントロール拡張: 遅延時間(
delay
)や透過色(transparentIndex
)が設定されている場合、グラフィックコントロール拡張ブロックが書き込まれます。 - 画像記述子: 画像の位置とサイズが書き込まれます。
- ローカルカラーテーブル: 各フレームが独自のパレットを持つ場合、ローカルカラーテーブルが書き込まれます。
- LZW圧縮:
compress/lzw
パッケージを使用してピクセルデータがLZW圧縮され、ブロック形式で出力されます。blockWriter
というヘルパー型が、LZWエンコーダからの出力をGIFのデータブロック形式(ブロックサイズとデータ)に変換する役割を担います。
- グラフィックコントロール拡張: 遅延時間(
Encode(w io.Writer, m image.Image, o *Options) error
: 単一のimage.Image
をGIF形式でエンコードする関数です。- 入力画像が
image.Paletted
でない場合、またはパレットサイズがOptions.NumColors
を超える場合、image/draw
パッケージのQuantizer
とDrawer
を使用して、指定された色数に減色(クオンタイゼーション)およびディザリング処理が行われます。デフォルトではcolor.Plan9Palette
とdraw.FloydSteinberg
が使用されます。
- 入力画像が
EncodeAll(w io.Writer, g *GIF) error
: 複数のimage.Paletted
画像を含むGIF
構造体(アニメーションGIF用)をエンコードする関数です。各フレームの遅延時間とループカウントが考慮されます。
依存関係の変更
src/pkg/go/build/deps_test.go
の変更は、image/gif
パッケージが image/draw
パッケージに依存するようになったことを示しています。これは、GIFエンコーダが画像の減色やディザリングのために image/draw
の機能を利用するためです。
reader.go
の変更
src/pkg/image/gif/reader.go
のコメントが更新され、パッケージがGIFのデコーダだけでなくエンコーダも実装していることが明記されました。
テストデータの追加
src/pkg/image/testdata/video-005.gray.gif
という新しいバイナリテストデータが追加されています。これは、エンコーダのテストに使用される可能性のあるGIF画像です。
コアとなるコードの変更箇所
src/pkg/go/build/deps_test.go
--- a/src/pkg/go/build/deps_test.go
+++ b/src/pkg/go/build/deps_test.go
@@ -202,7 +202,7 @@ var pkgDeps = map[string][]string{
"go/build": {"L4", "OS", "GOPARSER"},
"html": {"L4"},
"image/draw": {"L4"},
-" image/gif": {"L4", "compress/lzw"},
+" image/gif": {"L4", "compress/lzw", "image/draw"},
"image/jpeg": {"L4"},
"image/png": {"L4", "compress/zlib"},
"index/suffixarray": {"L4", "regexp"},
image/gif
パッケージの依存関係に image/draw
が追加されました。
src/pkg/image/gif/reader.go
--- a/src/pkg/image/gif/reader.go
+++ b/src/pkg/image/gif/reader.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package gif implements a GIF image decoder.
+// Package gif implements a GIF image decoder and encoder.
//
// The GIF specification is at http://www.w3.org/Graphics/GIF/spec-gif89a.txt.
package gif
パッケージのコメントが更新され、エンコーダ機能が追加されたことが示されています。
src/pkg/image/gif/writer.go
(新規ファイル)
このファイルはGIFエンコーダの主要な実装を含みます。
log2
ヘルパー関数: パレットサイズから必要なビット数を計算します。writeUint16
ヘルパー関数: 16ビット整数をリトルエンディアンで書き込みます。encoder
構造体: エンコード状態を保持します。blockWriter
構造体: LZWエンコーダからの出力をGIFのデータブロック形式に変換します。writeHeader()
: GIFヘッダ、論理スクリーン記述子、グローバルカラーテーブル、およびアニメーション用のNETSCAPE2.0拡張を書き込みます。writeColorTable()
: カラーパレットをGIF形式で書き込みます。writeImageBlock()
: 個々の画像フレームをGIFの画像ブロックとして書き込みます。グラフィックコントロール拡張(遅延、透過)とLZW圧縮されたピクセルデータを含みます。Options
構造体: エンコードオプション(色数、クオンタイザ、ドロワー)を定義します。EncodeAll()
: 複数の画像(アニメーションGIF)をエンコードします。Encode()
: 単一の画像をエンコードします。必要に応じて減色処理を行います。
src/pkg/image/gif/writer_test.go
(新規ファイル)
このファイルはGIFエンコーダのテストを含みます。
readImg
、readGIF
ヘルパー関数: テスト用の画像を読み込みます。delta
、averageDelta
ヘルパー関数: 画像間の色の差を計算し、エンコード後の画質を評価します。TestWriter
: 単一画像のエンコードとデコードを行い、元の画像との差を許容範囲内で検証します。TestEncodeAll
: 複数の画像(アニメーション)のエンコードとデコードを行い、ループカウントや遅延時間などのメタデータが正しく保持されているかを検証します。BenchmarkEncode
、BenchmarkQuantizedEncode
: エンコード性能を測定するためのベンチマークテストです。
src/pkg/image/testdata/video-005.gray.gif
(新規バイナリファイル)
エンコーダのテストに使用される新しいGIFテストデータです。
コアとなるコードの解説
writer.go
の encoder
と blockWriter
encoder
構造体は、GIFエンコード処理の中心です。io.Writer
をラップし、GIFの各セクション(ヘッダ、画像ブロックなど)を順次書き込むメソッドを提供します。
特に注目すべきは blockWriter
です。GIFの画像データは、LZW圧縮されたバイト列が255バイト以下のブロックに分割され、各ブロックの先頭にそのブロックのサイズを示す1バイトが付加される形式で格納されます。compress/lzw
パッケージの lzw.NewWriter
は、このブロック形式を意識せずに連続したバイト列を期待します。そこで blockWriter
が io.Writer
インターフェースを実装し、lzw.NewWriter
からの出力を受け取り、それをGIFのブロック形式に変換して実際の出力ストリーム(e.w
)に書き込む役割を担っています。これにより、LZW圧縮ロジックとGIFのブロック構造の間の結合が疎になり、コードの再利用性と保守性が向上しています。
// blockWriter writes the block structure of GIF image data, which
// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
// writer given to the LZW encoder, which is thus immune to the
// blocking.
type blockWriter struct {
e *encoder
}
func (b blockWriter) Write(data []byte) (int, error) {
// ... (エラーハンドリング) ...
total := 0
for total < len(data) {
n := copy(b.e.buf[1:256], data[total:]) // 最大255バイトをバッファにコピー
total += n
b.e.buf[0] = uint8(n) // ブロックサイズを先頭に書き込む
_, b.e.err = b.e.w.Write(b.e.buf[:n+1]) // ブロックサイズとデータを書き出す
// ... (エラーハンドリング) ...
}
return total, b.e.err
}
Encode
関数の減色処理
Encode
関数は、入力された image.Image
が image.Paletted
でない場合、またはパレットの色数が多すぎる場合に、自動的に減色処理を行います。
func Encode(w io.Writer, m image.Image, o *Options) error {
// ... (境界チェック、オプション設定) ...
pm, ok := m.(*image.Paletted)
if !ok || len(pm.Palette) > opts.NumColors {
// 入力画像がPalettedでない、またはパレットの色数が多すぎる場合
// 新しいPaletted画像を生成し、減色・ディザリングを行う
pm = image.NewPaletted(b, color.Plan9Palette[:opts.NumColors]) // デフォルトパレット
if opts.Quantizer != nil {
pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
}
opts.Drawer.Draw(pm, b, m, image.ZP) // ディザリング描画
}
return EncodeAll(w, &GIF{
Image: []*image.Paletted{pm},
Delay: []int{0},
})
}
この部分は、GIFがインデックスカラー形式であるという制約に対応するための重要なロジックです。image/draw
パッケージの Quantizer
と Drawer
インターフェースを利用することで、様々な減色アルゴリズムやディザリング手法を柔軟に適用できるよう設計されています。これにより、元の画像の色数が256色を超える場合でも、品質を保ちつつGIF形式に変換することが可能になります。
関連リンク
- GIF89a Specification
- Go image/gif package documentation (このコミット後の状態)
- Go image package documentation
- Go image/draw package documentation
- Go compress/lzw package documentation
参考にした情報源リンク
- golang/go GitHub repository
- Go Code Review Comments for CL 10896043 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)
- LZW (Lempel-Ziv-Welch) 圧縮アルゴリズム
- GIF (Graphics Interchange Format)
- Go言語の画像処理ライブラリ
image
パッケージの解説 (一般的なGoのimage
パッケージに関する情報) - Go言語の画像処理:image/drawパッケージで画像を操作する (Goの
image/draw
パッケージに関する一般的な情報)