[インデックス 15568] ファイルの概要
このコミットは、Go言語の image/png
パッケージにおける DecodeConfig
関数が、1ビット、2ビット、4ビットのパレット化されたPNG画像に対して、正しくパレット情報を設定しないバグを修正するものです。以前のコードでは、8ビットのパレット化された画像のみが DecodeConfig
時にパレットをデコードしていましたが、この変更により、より低ビット深度のパレット化された画像も適切に処理されるようになります。
コミット
commit 9d6e02742cd9942e342914f0b3dbbb4496d40ecd
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date: Mon Mar 4 14:54:36 2013 +1100
image/png: always set up palette during DecodeConfig
The old code would decode the palette only for 8-bit
images during a DecodeConfig.
This CL keeps the behavior for 8-bit images and sets
up the decoded palette also for 1, 2 and 4-bit images.
Fixes #4279.
R=golang-dev, nigeltao
CC=golang-dev
https://golang.org/cl/7421048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9d6e02742cd9942e342914f0b3dbbb4496d40ecd
元コミット内容
image/png
: DecodeConfig
中に常にパレットを設定する
古いコードでは、DecodeConfig
中に8ビット画像に対してのみパレットをデコードしていました。
このCL(Change List)は、8ビット画像の動作を維持しつつ、1ビット、2ビット、4ビット画像に対してもデコードされたパレットを設定するようにします。
Fixes #4279.
R=golang-dev, nigeltao CC=golang-dev https://golang.org/cl/7421048
変更の背景
この変更は、Go言語の image/png
パッケージが、PNG画像のヘッダー情報のみを読み取る DecodeConfig
関数において、パレット化された画像(特に1ビット、2ビット、4ビット深度の画像)のパレット情報を正しく取得できないというバグ(Issue #4279)を修正するために行われました。
PNG画像フォーマットでは、色深度が8ビット以下のパレット化された画像の場合、PLTEチャンク(Palette Chunk)にカラーパレット情報が格納されます。DecodeConfig
関数は、画像全体のデコードを行わずに、画像の設定(幅、高さ、カラーモデルなど)のみを読み取ることを目的としています。しかし、これまでの実装では、8ビットのパレット化された画像に対してのみPLTEチャンクを読み込み、カラーモデルにパレットを設定していました。そのため、1ビット、2ビット、4ビットのパレット化された画像では、DecodeConfig
を呼び出しても color.Palette
が適切に初期化されず、nil
となってしまう問題がありました。
この問題は、DecodeConfig
の結果を利用して後続の画像処理を行うアプリケーションにおいて、パレット情報が不足しているために予期せぬエラーや不正確な処理を引き起こす可能性がありました。このコミットは、この不整合を解消し、すべてのビット深度のパレット化された画像に対して DecodeConfig
が完全なカラーモデル情報を提供するようにすることで、APIの整合性と信頼性を向上させます。
前提知識の解説
PNG (Portable Network Graphics) フォーマット
PNGは、可逆圧縮を特徴とするラスターグラフィックスファイルフォーマットです。ウェブ上で広く利用されており、透明度(アルファチャンネル)をサポートします。PNGファイルは、複数の「チャンク」と呼ばれるデータブロックで構成されており、それぞれが特定の情報(画像データ、メタデータ、カラー情報など)を保持しています。
主要なチャンクには以下のようなものがあります。
- IHDR (Image Header): 画像の幅、高さ、ビット深度、カラータイプ、圧縮方式、フィルタ方式、インターレース方式などの基本情報が含まれます。
- PLTE (Palette): カラータイプがパレット化された画像の場合に、使用されるカラーパレット情報(RGB値のリスト)が含まれます。
- IDAT (Image Data): 実際の画像ピクセルデータが含まれます。
- IEND (Image End): ファイルの終わりを示します。
PNGのカラータイプとビット深度
PNGは様々なカラータイプをサポートしており、それぞれが異なるビット深度と組み合わされます。
- 0 (Greyscale): グレースケール画像。ビット深度は1, 2, 4, 8, 16。
- 2 (Truecolor): RGBカラー画像。ビット深度は8, 16。
- 3 (Indexed-color / Paletted): パレット化された画像。ビット深度は1, 2, 4, 8。PLTEチャンクで定義されたパレットのインデックスがピクセル値として格納されます。
- 4 (Greyscale with Alpha): グレースケールとアルファチャンネル。ビット深度は8, 16。
- 6 (Truecolor with Alpha): RGBカラーとアルファチャンネル。ビット深度は8, 16。
このコミットで問題となっているのは、カラータイプ3(パレット化された画像)です。パレット化された画像では、各ピクセルはパレット内の色のインデックスを参照します。ビット深度が低いほど、パレット内の色の数が少なくなります(例: 1ビットは2色、2ビットは4色、4ビットは16色、8ビットは256色)。
Go言語の image
パッケージと image/png
パッケージ
Go言語の標準ライブラリには、画像処理のための image
パッケージと、特定の画像フォーマット(PNG、JPEG、GIFなど)を扱うためのサブパッケージ(image/png
、image/jpeg
など)が含まれています。
image.Config
構造体: 画像の基本的な設定(幅、高さ、カラーモデル)を保持します。image.Image
インターフェース: 画像データとそれに関連するメソッド(Bounds, ColorModel, At)を定義します。color.ColorModel
インターフェース: 画像のカラーモデルを表現します。color.Palette
はcolor.ColorModel
の一種で、パレット化された画像のカラーモデルを表します。png.DecodeConfig(r io.Reader) (image.Config, error)
関数:io.Reader
からPNG画像のヘッダー情報のみを読み込み、image.Config
構造体を返します。この関数は、画像全体のピクセルデータをデコードすることなく、画像のメタデータ(サイズやカラーモデル)を効率的に取得するために使用されます。
DecodeConfig
の役割
DecodeConfig
は、画像ファイルの先頭部分を解析し、画像の基本的な属性(幅、高さ、カラーモデル)を抽出します。これは、画像全体をメモリに読み込むことなく、画像の表示に必要な情報を事前に知りたい場合に非常に有用です。例えば、画像のサムネイルを生成する前に、その画像のサイズを知りたい場合などに利用されます。
パレット化された画像の場合、DecodeConfig
はPLTEチャンクを読み込み、その情報を image.Config
の ColorModel
フィールドに color.Palette
として設定することが期待されます。しかし、バグのあるバージョンでは、8ビットのパレット化された画像に対してのみこの処理が行われていました。
技術的詳細
このバグは、image/png
パッケージの reader.go
内にある DecodeConfig
関数の内部ロジックに起因していました。DecodeConfig
は、PNGチャンクを順次読み込み、IHDR(画像ヘッダー)とPLTE(パレット)チャンクを処理します。
問題の箇所は、PLTEチャンクの処理をスキップするかどうかの条件判断でした。以前のコードでは、d.cb
(カラータイプとビット深度の組み合わせを示す内部フラグ)が cbP8
(8ビットパレット)の場合にのみPLTEチャンクの処理を継続し、それ以外のパレット化された画像(cbP1
, cbP2
, cbP4
)ではPLTEチャンクの読み込みを途中で終了させていました。
具体的には、DecodeConfig
関数内のチャンクループにおいて、以下の条件分岐がありました。
// 古いコードの抜粋
if d.stage == dsSeenIHDR && d.cb != cbP8 {
break // IHDRを見た後で、8ビットパレットでない場合はループを抜ける
}
if d.stage == dsSeenPLTE && d.cb == cbP8 {
break // PLTEを見た後で、8ビットパレットの場合はループを抜ける
}
このロジックでは、dsSeenIHDR
ステージ(IHDRチャンクを読み込んだ後)で d.cb
が cbP8
でない場合(つまり、1, 2, 4ビットパレットの場合)、ループが break
されてしまい、PLTEチャンクが適切に処理されませんでした。その結果、image.Config
の ColorModel
フィールドが nil
の color.Palette
となってしまい、パレット情報が欠落していました。
このコミットでは、この条件判断を修正し、すべてのビット深度のパレット化された画像(1, 2, 4, 8ビット)に対してPLTEチャンクが適切に処理されるように変更しました。
コアとなるコードの変更箇所
変更は主に2つのファイルで行われました。
-
src/pkg/image/png/reader.go
DecodeConfig
関数内のチャンク処理ロジックが変更されました。
--- a/src/pkg/image/png/reader.go +++ b/src/pkg/image/png/reader.go @@ -652,10 +652,11 @@ func DecodeConfig(r io.Reader) (image.Config, error) { } return image.Config{}, err } - if d.stage == dsSeenIHDR && d.cb != cbP8 { + paletted := d.cb == cbP8 || d.cb == cbP4 || d.cb == cbP2 || d.cb == cbP1 + if d.stage == dsSeenIHDR && !paletted { break } - if d.stage == dsSeenPLTE && d.cb == cbP8 { + if d.stage == dsSeenPLTE && paletted { break } }
-
src/pkg/image/png/reader_test.go
- 新しいテストケース
TestPalettedDecodeConfig
が追加されました。 - テスト用のパレット化されたPNGファイル名のリスト
filenamesPaletted
が追加されました。
--- a/src/pkg/image/png/reader_test.go +++ b/src/pkg/image/png/reader_test.go @@ -38,6 +38,14 @@ var filenames = []string{\n "basn6a16",\n }\n \n +var filenamesPaletted = []string{\n +\t"basn3p01",\n +\t"basn3p02",\n +\t"basn3p04",\n +\t"basn3p08",\n +\t"basn3p08-trns",\n +}\n +\n var filenamesShort = []string{\n \t"basn0g01",\n \t"basn0g04-31",\n @@ -278,6 +286,31 @@ func TestReaderError(t *testing.T) {\n \t}\n }\n \n +func TestPalettedDecodeConfig(t *testing.T) {\n +\tfor _, fn := range filenamesPaletted {\n +\t\tf, err := os.Open("testdata/pngsuite/" + fn + ".png")\n +\t\tif err != nil {\n +\t\t\tt.Errorf("%s: open failed: %v", fn, err)\n +\t\t\tcontinue\n +\t\t}\n +\t\tdefer f.Close()\n +\t\tcfg, err := DecodeConfig(f)\n +\t\tif err != nil {\n +\t\t\tt.Errorf("%s: %v", fn, err)\n +\t\t\tcontinue\n +\t\t}\n +\t\tpal, ok := cfg.ColorModel.(color.Palette)\n +\t\tif !ok {\n +\t\t\tt.Errorf("%s: expected paletted color model", fn)\n +\t\t\tcontinue\n +\t\t}\n +\t\tif pal == nil {\n +\t\t\tt.Errorf("%s: palette not initialized", fn)\n +\t\t\tcontinue\n +\t\t}\n +\t}\n +}\n +\n func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {\n \tb.StopTimer()\n \tdata, err := ioutil.ReadFile(filename)\n ```
- 新しいテストケース
コアとなるコードの解説
src/pkg/image/png/reader.go
の変更
変更の核心は、DecodeConfig
関数内のチャンク読み込みループにおける条件判断の修正です。
-
paletted
変数の導入:paletted := d.cb == cbP8 || d.cb == cbP4 || d.cb == cbP2 || d.cb == cbP1
この行は、現在の画像がパレット化された画像であるかどうかを判断するための新しいブール変数paletted
を導入しています。d.cb
は内部的なカラータイプとビット深度の組み合わせを示すフラグであり、cbP8
,cbP4
,cbP2
,cbP1
はそれぞれ8ビット、4ビット、2ビット、1ビットのパレット化された画像に対応します。これにより、すべてのビット深度のパレット化された画像を統一的に識別できるようになりました。 -
最初の
if
条件の変更:if d.stage == dsSeenIHDR && !paletted { break }
以前はd.cb != cbP8
でした。これは「IHDRチャンクを読み込んだ後で、かつ8ビットパレットでない場合はループを抜ける」という意味でした。この条件が問題で、1, 2, 4ビットパレットの画像ではPLTEチャンクを読み込む前にループが終了してしまっていました。 新しい条件!paletted
は、「IHDRチャンクを読み込んだ後で、かつパレット化された画像でない場合はループを抜ける」という意味になります。これにより、パレット化された画像であれば、ビット深度に関わらずPLTEチャンクの処理に進むことができるようになりました。 -
2番目の
if
条件の変更:if d.stage == dsSeenPLTE && paletted { break }
以前はd.cb == cbP8
でした。これは「PLTEチャンクを読み込んだ後で、かつ8ビットパレットの場合はループを抜ける」という意味でした。 新しい条件paletted
は、「PLTEチャンクを読み込んだ後で、かつパレット化された画像である場合はループを抜ける」という意味になります。これは、パレット化された画像であればPLTEチャンクの処理が完了したことを示し、それ以上チャンクを読み込む必要がないため、ループを終了させるための適切な条件です。
これらの変更により、DecodeConfig
は、1ビット、2ビット、4ビット、8ビットのすべてのパレット化されたPNG画像に対して、IHDRチャンクの後にPLTEチャンクを正しく読み込み、image.Config
の ColorModel
フィールドに適切な color.Palette
を設定するようになりました。
src/pkg/image/png/reader_test.go
の変更
新しいテストケース TestPalettedDecodeConfig
は、この修正が正しく機能することを確認するために追加されました。
-
filenamesPaletted
の追加: このスライスには、様々なビット深度(1, 2, 4, 8ビット)のパレット化されたPNGテスト画像ファイル名が含まれています。これらはPNG Suite(PNGテスト画像の標準セット)から選ばれたものです。basn3p01
: 1ビットパレットbasn3p02
: 2ビットパレットbasn3p04
: 4ビットパレットbasn3p08
: 8ビットパレットbasn3p08-trns
: 8ビットパレット(透明度情報付き)
-
TestPalettedDecodeConfig
関数の実装: このテスト関数は、filenamesPaletted
の各ファイルに対して以下の処理を行います。- テスト画像ファイルを開きます。
png.DecodeConfig
を呼び出し、image.Config
を取得します。- 取得した
cfg.ColorModel
がcolor.Palette
型であることを確認します。 - さらに、取得した
color.Palette
がnil
でないこと(つまり、パレットが正しく初期化されていること)を確認します。
このテストの追加により、DecodeConfig
がすべてのビット深度のパレット化された画像に対して、期待通りにパレット情報を設定していることが自動的に検証されるようになりました。これは、回帰バグを防ぎ、コードの信頼性を高める上で非常に重要です。
関連リンク
- Go Issue #4279: https://code.google.com/p/go/issues/detail?id=4279 (元のGoプロジェクトのIssueトラッカーへのリンク。現在はGitHubに移行しているため、GitHubのIssueページにリダイレクトされる可能性があります。)
- Go CL 7421048: https://golang.org/cl/7421048 (GoのコードレビューシステムGerritの変更リストへのリンク)
参考にした情報源リンク
- PNG (Portable Network Graphics) Specification: https://www.w3.org/TR/PNG/
- Go
image
package documentation: https://pkg.go.dev/image - Go
image/png
package documentation: https://pkg.go.dev/image/png - PNG Suite (Test Images): http://www.schaik.com/pngsuite/
- Go Issue #4279 on GitHub: https://github.com/golang/go/issues/4279 (現在のIssueページ)
- Go
image/png
source code (reader.go): https://github.com/golang/go/blob/master/src/image/png/reader.go - Go
image/png
source code (reader_test.go): https://github.com/golang/go/blob/master/src/image/png/reader_test.go