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

[インデックス 19720] ファイルの概要

このコミットは、Go言語の標準ライブラリ image/png パッケージにPNG画像のインターレース(Adam7方式)のデコードサポートを追加するものです。これにより、プログレッシブ表示が可能なPNG画像をGoプログラムで正しく読み込めるようになります。

コミット

image/png: interlacing support for png.

Fixes #6293.

Image "testdata/benchRGB-interlace.png" was generated by opening "testdata/benchRGB.png" in the editor Gimp and saving it with interlacing enabled.

Benchmark:
BenchmarkDecodeRGB                   500           7014194 ns/op          37.37 MB/s
ok      pkg/image/png   4.657s

BenchmarkDecodeInterlacing           100          10623241 ns/op          24.68 MB/s
ok      pkg/image/png   1.339s

LGTM=nigeltao
R=nigeltao, andybons, matrixik
CC=golang-codereviews
https://golang.org/cl/102130044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5c2f01f3923cae85f50260778b8ad0f9d857fd1d

元コミット内容

commit 5c2f01f3923cae85f50260778b8ad0f9d857fd1d
Author: Dustin Long <dustmop@gmail.com>
Date:   Fri Jul 11 11:02:02 2014 +1000

    image/png: interlacing support for png.
    
    Fixes #6293.
    
    Image "testdata/benchRGB-interlace.png" was generated by opening "testdata/benchRGB.png" in the editor Gimp and saving it with interlacing enabled.
    
    Benchmark:
    BenchmarkDecodeRGB                   500           7014194 ns/op          37.37 MB/s
    ok      pkg/image/png   4.657s
    
    BenchmarkDecodeInterlacing           100          10623241 ns/op          24.68 MB/s
    ok      pkg/image/png   1.339s
    
    LGTM=nigeltao
    R=nigeltao, andybons, matrixik
    CC=golang-codereviews
    https://golang.org/cl/102130044

変更の背景

この変更の背景には、Go言語の image/png パッケージがPNG画像のインターレース形式(特にAdam7インターレース)をサポートしていなかったという問題があります。Issue #6293 で報告されているように、インターレースされたPNG画像をGoでデコードしようとするとエラーが発生していました。

インターレースは、画像が完全にダウンロードされる前に、低解像度から徐々に高解像度へと表示されるようにする技術です。ウェブブラウザなどで画像を読み込む際に、ユーザーに「画像が読み込まれている」という視覚的なフィードバックを提供し、ユーザーエクスペリエンスを向上させるために利用されます。PNG形式ではAdam7インターレース方式が採用されており、これは画像を7つのパスに分割して転送するものです。

このコミットは、Goの image/png パッケージがPNG標準に完全に準拠し、より幅広いPNG画像を扱えるようにするために、インターレースデコード機能を追加する必要性から生まれました。

前提知識の解説

PNG (Portable Network Graphics)

PNGは、ラスターグラフィックスファイル形式であり、可逆圧縮をサポートしています。ウェブ上で広く使用されており、透明度(アルファチャンネル)をサポートする点が特徴です。

インターレース (Interlacing)

インターレースとは、画像を転送する際に、最初から完全な画像を一度に送るのではなく、段階的に詳細な情報を送ることで、受信側で画像が徐々に鮮明になっていくように表示させる技術です。これにより、低速なネットワーク環境でもユーザーは画像の一部を早期に視認でき、読み込み中の待機時間を短く感じることができます。

Adam7インターレース

PNGで採用されているインターレース方式は「Adam7インターレース」と呼ばれます。これは、画像を7つの「パス」に分割して転送する複雑なアルゴリズムです。各パスは、元の画像から特定のピクセルを抽出して構成されます。

Adam7インターレースの仕組みは以下の通りです。

  1. パス1: 8x8ピクセルのブロックの左上隅のピクセル((0,0), (8,0), (0,8), (8,8) ...)を転送します。これにより、非常に粗いプレビューが表示されます。
  2. パス2: パス1で転送されなかったピクセルで、8x8ブロックの(4,0), (12,0) ... の位置にあるピクセルを転送します。
  3. パス3: 8x4ブロックの(0,4), (8,4) ... の位置にあるピクセルを転送します。
  4. パス4: 4x4ブロックの(2,0), (6,0) ... の位置にあるピクセルを転送します。
  5. パス5: 4x2ブロックの(0,2), (4,2) ... の位置にあるピクセルを転送します。
  6. パス6: 2x2ブロックの(1,0), (3,0) ... の位置にあるピクセルを転送します。
  7. パス7: 残りのすべてのピクセルを転送します。

各パスは、元の画像よりも小さい「サブ画像」として扱われ、それぞれが独立して圧縮・転送されます。受信側では、これらのサブ画像を元の画像にマージしていくことで、徐々に詳細な画像が再構築されます。

Go言語の image パッケージ

Go言語の image パッケージは、様々な画像形式(PNG, JPEG, GIFなど)のエンコードとデコードを扱うための基本的なインターフェースと実装を提供します。image.Image インターフェースは、画像データへの一般的なアクセス方法を定義しており、各画像形式のパッケージ(例: image/png)がこのインターフェースを実装します。

技術的詳細

このコミットの主要な技術的変更点は、src/pkg/image/png/reader.go におけるAdam7インターレースのデコードロジックの実装です。

  1. インターレースタイプの定義: itNone (0) と itAdam7 (1) という定数が追加され、PNGのIHDRチャンクで指定されるインターレース方式を識別します。

  2. interlaceScan 構造体と interlacing 配列: interlaceScan 構造体は、Adam7インターレースの各パスにおけるピクセルの配置とサイズを定義します。xFactor, yFactor はピクセル間の間隔、xOffset, yOffset は開始オフセットを示します。 interlacing グローバル変数は、Adam7の7つのパスそれぞれの interlaceScan 定義を配列として保持しています。これは、PNG仕様のAdam7インターレースのセクションに記載されている情報に基づいています。

  3. decoder 構造体への interlace フィールド追加: decoder 構造体に interlace int フィールドが追加され、デコード中のPNG画像がインターレースされているかどうか、およびその方式(現在はAdam7のみ)を保持します。

  4. IHDRチャンクのパースロジックの変更: parseIHDR 関数が変更され、IHDRチャンクのインターレース方式フィールド(d.tmp[12])を読み取り、itNone または itAdam7 以外の値であれば FormatError を返すようになりました。これにより、不正なインターレース方式が検出されます。

  5. decode 関数のリファクタリングとインターレース処理: decode 関数は、インターレースの有無に応じて処理を分岐するようになりました。

    • インターレースがない場合 (d.interlace == itNone) は、従来の単一パスのデコード処理 (readImagePass) を呼び出します。
    • Adam7インターレースがある場合 (d.interlace == itAdam7) は、まずフルサイズの空の画像を割り当てます。その後、7つのパスそれぞれに対して readImagePass を呼び出してサブ画像をデコードし、mergePassInto 関数を使ってそのサブ画像をフルサイズの画像にマージしていきます。
  6. readImagePass 関数の導入: readImagePass は新しく導入された関数で、単一の画像パス(インターレースされていない画像全体、またはAdam7インターレースの各サブ画像)を読み込み、デコードする役割を担います。この関数は、パス番号に基づいて画像の幅と高さを計算し、それに応じて image.NewGray, image.NewNRGBA などの画像オブジェクトを生成します。

  7. mergePassInto 関数の導入: mergePassInto は、Adam7インターレースの各パスでデコードされたサブ画像を、最終的なフルサイズの画像にマージする役割を担います。この関数は、各画像タイプ(*image.Alpha, *image.Gray, *image.NRGBA など)に対応し、interlacing 配列で定義された xFactor, yFactor, xOffset, yOffset を使用して、サブ画像のピクセルを正しい位置に配置します。

  8. テストケースの追加: src/pkg/image/png/reader_test.go に、インターレースされたPNG画像 (basn3p04-31i.png) をテストケースに追加し、BenchmarkDecodeInterlacing ベンチマークも追加されています。これにより、インターレースデコード機能が正しく動作し、そのパフォーマンスが測定できるようになりました。

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

このコミットにおけるコアとなるコードの変更は、主に src/pkg/image/png/reader.go に集中しています。

  • src/pkg/image/png/reader.go:

    • itNone, itAdam7 定数の追加 (L57-L60)
    • interlaceScan 構造体と interlacing グローバル変数の追加 (L63-L76)
    • decoder 構造体への interlace フィールド追加 (L87)
    • parseIHDR 関数におけるインターレース方式のチェックと設定 (L113-L120)
    • decode 関数の大幅な変更。インターレース処理の分岐と readImagePass, mergePassInto の呼び出し (L287-L317)
    • readImagePass 関数の新規追加 (L319-L507)
    • mergePassInto 関数の新規追加 (L510-L581)
    • ピクセルデータ処理ループにおける d.widthwidth に変更し、パスごとの幅に対応 (L370以降の多数の行)
  • src/pkg/image/png/reader_test.go:

    • filenames 配列に basn3p04-31i を追加 (L33)
    • sng 関数にビット深度に応じたパディング処理を追加 (L186-L191)
    • BenchmarkDecodeInterlacing ベンチマークの追加 (L350-L352)
  • src/pkg/image/png/testdata/benchRGB-interlace.png: 新規追加されたバイナリテストデータ

  • src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png: 新規追加されたバイナリテストデータ

  • src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng: 新規追加されたテストデータ(SNG形式)

コアとなるコードの解説

reader.go の変更

最も重要な変更は、decode 関数がインターレースの有無を判断し、Adam7インターレースの場合は7つのパスを個別にデコードし、それらを最終的な画像にマージするロジックが追加された点です。

  1. interlacing 配列: この配列はAdam7インターレースの各パスの特性を定義します。例えば、{8, 8, 0, 0} は最初のパスが8ピクセルごとにサンプリングされ、オフセットが(0,0)であることを示します。この定義に基づいて、各パスのサブ画像のサイズが計算されます。

  2. readImagePass 関数: この関数は、インターレースされていない画像全体、またはインターレースされた画像の単一のパス(サブ画像)を読み込み、デコードします。重要なのは、allocateOnly パラメータが true の場合、ピクセルデータを読み込まずに画像オブジェクトだけを割り当てる点です。これは、Adam7インターレースの最初のステップでフルサイズの画像を準備するために使用されます。また、pass 番号に基づいて widthheight を計算し、各パスのサブ画像の正しいサイズを決定します。

  3. mergePassInto 関数: この関数は、readImagePass でデコードされた各サブ画像を、事前に割り当てられたフルサイズの画像にコピーします。interlacing 配列の xFactor, yFactor, xOffset, yOffset を利用して、サブ画像のピクセルがフルサイズの画像の正しい位置に配置されるように計算が行われます。これにより、7つのパスがすべてマージされると、完全な画像が再構築されます。

reader_test.go の変更

テストファイルには、インターレースされたPNG画像 (basn3p04-31i.png) が追加され、この画像が正しくデコードされることを確認するテストが実行されます。また、BenchmarkDecodeInterlacing が追加されたことで、インターレースデコードのパフォーマンスを測定できるようになりました。ベンチマーク結果を見ると、インターレースデコードは通常のデコードよりも時間がかかることが示されていますが、これはAdam7インターレースの性質上、複数のパスを処理する必要があるため予想される挙動です。

関連リンク

参考にした情報源リンク