Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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/pngimage/jpegなど)が含まれています。

  • image.Config 構造体: 画像の基本的な設定(幅、高さ、カラーモデル)を保持します。
  • image.Image インターフェース: 画像データとそれに関連するメソッド(Bounds, ColorModel, At)を定義します。
  • color.ColorModel インターフェース: 画像のカラーモデルを表現します。color.Palettecolor.ColorModel の一種で、パレット化された画像のカラーモデルを表します。
  • png.DecodeConfig(r io.Reader) (image.Config, error) 関数: io.Reader からPNG画像のヘッダー情報のみを読み込み、image.Config 構造体を返します。この関数は、画像全体のピクセルデータをデコードすることなく、画像のメタデータ(サイズやカラーモデル)を効率的に取得するために使用されます。

DecodeConfig の役割

DecodeConfig は、画像ファイルの先頭部分を解析し、画像の基本的な属性(幅、高さ、カラーモデル)を抽出します。これは、画像全体をメモリに読み込むことなく、画像の表示に必要な情報を事前に知りたい場合に非常に有用です。例えば、画像のサムネイルを生成する前に、その画像のサイズを知りたい場合などに利用されます。

パレット化された画像の場合、DecodeConfig はPLTEチャンクを読み込み、その情報を image.ConfigColorModel フィールドに 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.cbcbP8 でない場合(つまり、1, 2, 4ビットパレットの場合)、ループが break されてしまい、PLTEチャンクが適切に処理されませんでした。その結果、image.ConfigColorModel フィールドが nilcolor.Palette となってしまい、パレット情報が欠落していました。

このコミットでは、この条件判断を修正し、すべてのビット深度のパレット化された画像(1, 2, 4, 8ビット)に対してPLTEチャンクが適切に処理されるように変更しました。

コアとなるコードの変更箇所

変更は主に2つのファイルで行われました。

  1. 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
     		}
     	}
    
  2. 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 関数内のチャンク読み込みループにおける条件判断の修正です。

  1. 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ビットのパレット化された画像に対応します。これにより、すべてのビット深度のパレット化された画像を統一的に識別できるようになりました。

  2. 最初の if 条件の変更: if d.stage == dsSeenIHDR && !paletted { break } 以前は d.cb != cbP8 でした。これは「IHDRチャンクを読み込んだ後で、かつ8ビットパレットでない場合はループを抜ける」という意味でした。この条件が問題で、1, 2, 4ビットパレットの画像ではPLTEチャンクを読み込む前にループが終了してしまっていました。 新しい条件 !paletted は、「IHDRチャンクを読み込んだ後で、かつパレット化された画像でない場合はループを抜ける」という意味になります。これにより、パレット化された画像であれば、ビット深度に関わらずPLTEチャンクの処理に進むことができるようになりました。

  3. 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.ConfigColorModel フィールドに適切な color.Palette を設定するようになりました。

src/pkg/image/png/reader_test.go の変更

新しいテストケース TestPalettedDecodeConfig は、この修正が正しく機能することを確認するために追加されました。

  1. filenamesPaletted の追加: このスライスには、様々なビット深度(1, 2, 4, 8ビット)のパレット化されたPNGテスト画像ファイル名が含まれています。これらはPNG Suite(PNGテスト画像の標準セット)から選ばれたものです。

    • basn3p01: 1ビットパレット
    • basn3p02: 2ビットパレット
    • basn3p04: 4ビットパレット
    • basn3p08: 8ビットパレット
    • basn3p08-trns: 8ビットパレット(透明度情報付き)
  2. TestPalettedDecodeConfig 関数の実装: このテスト関数は、filenamesPaletted の各ファイルに対して以下の処理を行います。

    • テスト画像ファイルを開きます。
    • png.DecodeConfig を呼び出し、image.Config を取得します。
    • 取得した cfg.ColorModelcolor.Palette 型であることを確認します。
    • さらに、取得した color.Palettenil でないこと(つまり、パレットが正しく初期化されていること)を確認します。

このテストの追加により、DecodeConfig がすべてのビット深度のパレット化された画像に対して、期待通りにパレット情報を設定していることが自動的に検証されるようになりました。これは、回帰バグを防ぎ、コードの信頼性を高める上で非常に重要です。

関連リンク

参考にした情報源リンク