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

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

このコミットは、Go言語のimage/jpegパッケージにおいて、JPEG画像の量子化テーブルの扱いに関する重要な修正を行っています。具体的には、量子化テーブルがJPEG標準で規定されている「ジグザグ順序(zig-zag order)」ではなく、「自然順序(natural order)」で処理されていた問題を修正し、エンコーダとデコーダの両方でジグザグ順序に準拠するように変更しました。これにより、生成されるJPEG画像がより標準に則ったものとなります。

コミット

commit 4bd8a3864180e88b51939f9880ac52b575bf21fa
Author: Nigel Tao <nigeltao@golang.org>
Date:   Thu Sep 6 11:10:47 2012 +1000

    image/jpeg: fix quantization tables to be in zig-zag order, not natural
    order.
    
    JPEG images generated prior to this CL are still valid JPEGs, as the
    quantization tables used are encoded in the wire format. Such JPEGs just
    don't use the recommended quantization tables.
    
    R=r, dsymonds, raph, adg
    CC=golang-dev, tuom.larsen
    https://golang.org/cl/6497083

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

https://github.com/golang/go/commit/4bd8a3864180e88b51939f9880ac52b575bf21fa

元コミット内容

image/jpeg: 量子化テーブルを自然順序ではなく、ジグザグ順序に修正。

このCL以前に生成されたJPEG画像は、使用された量子化テーブルがワイヤーフォーマットでエンコードされているため、依然として有効なJPEGです。そのようなJPEGは、推奨される量子化テーブルを使用していないだけです。

変更の背景

JPEG圧縮の国際標準(ISO/IEC 10918-1 | ITU-T Recommendation T.81)では、量子化テーブルは特定の順序、すなわち「ジグザグ順序」で定義され、ファイル内に格納されることが規定されています。これは、離散コサイン変換(DCT)によって得られた周波数係数を効率的に符号化するために重要な要素です。

Go言語のimage/jpegパッケージの以前の実装では、この量子化テーブルが標準で定められたジグザグ順序ではなく、内部的に「自然順序」で扱われていました。この不一致は、生成されるJPEG画像が技術的には有効であるものの、標準で推奨される量子化テーブルの配置とは異なり、場合によっては互換性や最適化の点で問題を引き起こす可能性がありました。

このコミットは、この標準からの逸脱を修正し、image/jpegパッケージがJPEG標準に完全に準拠するようにすることを目的としています。これにより、より堅牢で互換性の高いJPEGエンコーダおよびデコーダが提供されます。コミットメッセージにあるように、この修正以前に生成されたJPEGファイルは、量子化テーブル自体がファイル内にエンコードされているため、引き続き有効ですが、推奨されるテーブルの配置を使用していなかったという点が改善されます。

前提知識の解説

JPEG圧縮の基本

JPEG(Joint Photographic Experts Group)は、主に写真などの連続階調画像を効率的に圧縮するための標準です。その圧縮プロセスは、主に以下のステップで構成されます。

  1. 色空間変換: RGB画像をY'CbCr色空間に変換します。Y'は輝度(明るさ)、CbとCrは色差(色合い)を表します。人間の目は輝度変化に敏感で、色差変化には比較的鈍感であるため、色差成分の情報を間引くことで高い圧縮率を実現します(クロマサブサンプリング)。
  2. DCT(離散コサイン変換): 画像を8x8ピクセルのブロックに分割し、各ブロックに対してDCTを適用します。DCTは、空間領域のピクセルデータを周波数領域の係数に変換します。これにより、画像内の低周波成分(滑らかな変化)と高周波成分(細かいディテール)が分離されます。低周波成分は画像の主要な情報を持ち、高周波成分は細かいディテールやノイズを表します。
  3. 量子化: DCTによって得られた周波数係数を量子化します。これは、各係数を量子化テーブルの対応する値で割り、結果を丸めるプロセスです。このステップがJPEG圧縮の「非可逆性」の主要な原因であり、人間の視覚が感知しにくい高周波成分の情報を大胆に破棄することで、高い圧縮率を実現します。量子化テーブルの値が大きいほど、より多くの情報が破棄され、圧縮率が高まりますが、画質は低下します。
  4. エントロピー符号化: 量子化された係数をさらに圧縮します。DC係数(最も左上の低周波成分)は差分符号化され、AC係数(その他の高周波成分)はジグザグ走査によって一次元化された後、ゼロランレングス符号化とハフマン符号化(または算術符号化)が適用されます。

量子化テーブル

量子化テーブルは、JPEG圧縮において画質とファイルサイズを決定する非常に重要な要素です。各8x8ブロックのDCT係数に対応する64個の量子化ステップサイズが格納されています。これらの値は、DCT係数をどれだけ「丸めるか」を指示します。

  • 値が小さい: 係数の丸めが少なく、情報がより多く保持されるため、高画質になりますがファイルサイズは大きくなります。
  • 値が大きい: 係数の丸めが多く、情報がより多く破棄されるため、低画質になりますがファイルサイズは小さくなります。

JPEG標準では、推奨される標準量子化テーブルが定義されており、通常はこれらがベースとなります。エンコーダは、ユーザーが指定する画質設定(例: クオリティ90%)に基づいて、これらの基本テーブルをスケーリングして使用します。

ジグザグ走査 (Zig-zag Scan)

DCTによって得られた8x8の周波数係数ブロックは、通常、左上から右下に向かって低周波成分から高周波成分へと並んでいます。JPEGでは、これらの2次元の係数を1次元のシーケンスに変換するために「ジグザグ走査」という特殊な順序を使用します。

ジグザグ走査は、低周波成分(ブロックの左上隅)から始まり、対角線に沿って高周波成分(ブロックの右下隅)へと移動します。この順序で係数を並べることで、多くの高周波係数がゼロになる傾向があるため、連続するゼロのラン(run)が長くなり、ランレングス符号化とハフマン符号化の効率が向上します。

ナチュラルオーダー (Natural Order) vs. ジグザグオーダー (Zig-zag Order)

  • ナチュラルオーダー(Natural Order): 単純に左から右、上から下へと順に並べた順序です。8x8ブロックの場合、インデックス0から63までが単純に0, 1, 2, ..., 63と並びます。
  • ジグザグオーダー(Zig-zag Order): 前述のジグザグ走査によって得られる順序です。例えば、8x8ブロックの最初の数要素は、ナチュラルオーダーで(0,0), (0,1), (1,0), (2,0), (1,1), (0,2), ... となりますが、ジグザグオーダーではインデックス0, 1, 2, 3, 4, 5, ... に対応する要素がこの順序で並びます。

JPEG標準では、量子化テーブル自体も、ファイルフォーマット内ではジグザグ順序で格納されることが期待されます。このコミットの目的は、Goのimage/jpegパッケージがこの標準の期待に沿うように、内部的な量子化テーブルの表現と処理をジグザグ順序に合わせることでした。

技術的詳細

このコミットは、Go言語のimage/jpegパッケージ内のreader.gowriter.gowriter_test.goの3つのファイルにわたって変更を加えています。主な変更点は、量子化テーブルのデータ構造と、DCT係数の処理におけるジグザグ順序の適用です。

src/pkg/image/jpeg/reader.go

  • unzig配列のコメント修正:
    • unzig配列は、ジグザグ順序のインデックスをナチュラル順序のインデックスにマッピングするための配列です。以前のコメントは「ジグザグ順序から自然順序へのマッピング」とだけ書かれていましたが、より詳細な説明が追加されました。
    • unzig[3]の例が追加され、その値が16であること、そしてそれがナチュラル順序での列と行(16%8 == 0で1列目、16/8 == 2で3行目)をどのように表すかが明確にされました。これは、unzig配列がジグザグ順序のインデックスを受け取り、対応するナチュラル順序の1次元インデックスを返すことを示しています。
  • decoder構造体のquantフィールドのコメント修正:
    • quantフィールドは量子化テーブルを保持しますが、そのコメントに「// Quantization tables, in zig-zag order.」と追記され、このテーブルがジグザグ順序で格納されていることが明示されました。
  • processSOS関数内のAC係数デコードループの修正:
    • AC係数をデコードするループ変数がkからzigに変更されました。これは、ループがジグザグ順序のインデックスをたどっていることをより明確にするためです。
    • b[unzig[k]] = ac * qt[k]という行がb[unzig[zig]] = ac * qt[zig]に変更されました。これは、デコードされたAC係数をナチュラル順序のブロックbに格納する際に、ジグザグ順序のインデックスzigunzig配列で変換してナチュラル順序のインデックスを得ていることを示しています。また、量子化テーブルqtもジグザグ順序でアクセスされるようになりました。
    • k += int(val0)k += 0x0fといったインデックスのスキップ処理も、zig += int(val0)zig += 0x0fに変更され、ジグザグ順序でのインデックス操作に統一されました。

src/pkg/image/jpeg/writer.go

  • unscaledQuant配列の修正とコメント追加:
    • unscaledQuantは、スケーリング前の標準量子化テーブルを保持する配列です。この配列の具体的な値が大幅に変更されました。これは、以前のテーブルがナチュラル順序で定義されていたのに対し、新しいテーブルはJPEG標準のセクションK.1から導出されたジグザグ順序の値に変換されたためです。
    • コメントも「// unscaledQuant are the unscaled quantization tables in zig-zag order.」と変更され、このテーブルがジグザグ順序であることを明示しています。さらに、「The values are derived from section K.1 after converting from natural to zig-zag order.」と、値の由来と変換プロセスが説明されています。
  • encoder構造体のquantフィールドのコメント修正:
    • quantフィールドはスケーリング後の量子化テーブルを保持しますが、そのコメントに「// quant is the scaled quantization tables, in zig-zag order.」と追記され、このテーブルもジグザグ順序であることが明示されました。
  • writeDQTおよびwriteSOF0関数のmarkerlen変数の変更:
    • markerlen変数がconstキーワードで宣言されるようになりました。これは、これらの値がコンパイル時に決定される定数であることを示し、コードの明確性と潜在的な最適化に寄与します。
  • writeBlock関数内のAC係数エンコードループの修正:
    • AC係数をエンコードするループ変数がkからzigに変更されました。
    • ac := div(b[unzig[k]], (8 * int(e.quant[q][k])))という行がac := div(b[unzig[zig]], (8 * int(e.quant[q][zig])))に変更されました。これは、ナチュラル順序のブロックbから係数を読み出す際にunzigを使ってジグザグ順序に変換し、量子化テーブルe.quant[q]もジグザグ順序でアクセスしていることを示しています。

src/pkg/image/jpeg/writer_test.go

  • zigzag配列の追加:
    • zigzag配列は、ナチュラル順序のインデックスをジグザグ順序のインデックスにマッピングするための新しい配列です。これはunzigの逆マッピングを提供します。
  • TestZigUnzigテストの追加:
    • unzigzigzag配列が互いに逆関数として正しく機能するかを検証するテストです。unzig[zigzag[i]] == izigzag[unzig[i]] == iがすべてのインデックスiで成り立つことを確認します。これにより、ジグザグとナチュラル順序間の変換ロジックの正確性が保証されます。
  • unscaledQuantInNaturalOrder配列の追加:
    • JPEG標準のセクションK.1で定義されている、ナチュラル順序のままの標準量子化テーブルを保持する新しい配列です。これは、unscaledQuantが正しくジグザグ順序に変換されたかを検証するための参照として使用されます。
  • TestUnscaledQuantテストの追加:
    • unscaledQuant配列(ジグザグ順序)が、unscaledQuantInNaturalOrder配列(ナチュラル順序)から正しく変換されているかを検証するテストです。
    • got := unscaledQuant[i][zig]want := unscaledQuantInNaturalOrder[i][unzig[zig]]を比較することで、unscaledQuantzig番目の要素が、ナチュラル順序のテーブルのunzig[zig]番目の要素と一致するかを確認します。これにより、unscaledQuantが実際にジグザグ順序で正しく格納されていることが保証されます。
    • テストが失敗した場合、期待されるunscaledQuantの値(ジグザグ順序に変換されたもの)をログに出力する機能も含まれており、デバッグに役立ちます。

これらの変更により、GoのJPEGエンコーダとデコーダは、量子化テーブルの処理においてJPEG標準のジグザグ順序に完全に準拠するようになりました。

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

src/pkg/image/jpeg/reader.go

--- a/src/pkg/image/jpeg/reader.go
+++ b/src/pkg/image/jpeg/reader.go
@@ -74,7 +74,9 @@ const (
 	comMarker   = 0xfe // COMment.
 )
 
-// Maps from the zig-zag ordering to the natural ordering.
+// unzig maps from the zig-zag ordering to the natural ordering. For example,
+// unzig[3] is the column and row of the fourth element in zig-zag order. The
+// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
 var unzig = [blockSize]int{
 	0, 1, 8, 16, 9, 2, 3, 10,
 	17, 24, 32, 25, 18, 11, 4, 5,
@@ -101,7 +103,7 @@ type decoder struct {
 	nComp         int
 	comp          [nColorComponent]component
 	huff          [maxTc + 1][maxTh + 1]huffman
-	quant         [maxTq + 1]block
+	quant         [maxTq + 1]block // Quantization tables, in zig-zag order.
 	b             bits
 	tmp           [1024]byte
 }
@@ -266,6 +268,7 @@ func (d *decoder) processSOS(n int) error {
 				for j := 0; j < d.comp[i].h*d.comp[i].v; j++ {
 					// TODO(nigeltao): make this a "var b block" once the compiler's escape
 					// analysis is good enough to allocate it on the stack, not the heap.
+					// b is in natural (not zig-zag) order.
 					b = block{}
 
 					// Decode the DC coefficient, as specified in section F.2.2.1.
@@ -284,20 +287,20 @@ func (d *decoder) processSOS(n int) error {
 					b[0] = dc[i] * qt[0]
 
 					// Decode the AC coefficients, as specified in section F.2.2.2.
-					for k := 1; k < blockSize; k++ {
+					for zig := 1; zig < blockSize; zig++ {
 						value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta])
 						if err != nil {
 							return err
 						}
 						val0 := value >> 4
 						val1 := value & 0x0f
 						if val1 != 0 {
-							k += int(val0)
-							if k > blockSize {
+							zig += int(val0)
+							if zig > blockSize {
 								return FormatError("bad DCT index")
 							}
 							ac, err := d.receiveExtend(val1)
 							if err != nil {
 								return err
 							}
-							b[unzig[k]] = ac * qt[k]
+							b[unzig[zig]] = ac * qt[zig]
 						} else {
 							if val0 != 0x0f {
 								break
 							}
-							k += 0x0f
+							zig += 0x0f
 						}
 					}

src/pkg/image/jpeg/writer.go

--- a/src/pkg/image/jpeg/writer.go
+++ b/src/pkg/image/jpeg/writer.go
@@ -56,26 +56,28 @@ const (
 	nQuantIndex
 )
 
-// unscaledQuant are the unscaled quantization tables. Each encoder copies and
-// scales the tables according to its quality parameter.
+// unscaledQuant are the unscaled quantization tables in zig-zag order. Each
+// encoder copies and scales the tables according to its quality parameter.
+// The values are derived from section K.1 after converting from natural to
+// zig-zag order.
 var unscaledQuant = [nQuantIndex][blockSize]byte{
 	// Luminance.
 	{
-		16, 11, 10, 16, 24, 40, 51, 61,
-		12, 12, 14, 19, 26, 58, 60, 55,
-		14, 13, 16, 24, 40, 57, 69, 56,
-		14, 17, 22, 29, 51, 87, 80, 62,
-		18, 22, 37, 56, 68, 109, 103, 77,
-		24, 35, 55, 64, 81, 104, 113, 92,
-		49, 64, 78, 87, 103, 121, 120, 101,
-		72, 92, 95, 98, 112, 100, 103, 99,
+		16, 11, 12, 14, 12, 10, 16, 14,
+		13, 14, 18, 17, 16, 19, 24, 40,
+		26, 24, 22, 22, 24, 49, 35, 37,
+		29, 40, 58, 51, 61, 60, 57, 51,
+		56, 55, 64, 72, 92, 78, 64, 68,
+		87, 69, 55, 56, 80, 109, 81, 87,
+		95, 98, 103, 104, 103, 62, 77, 113,
+		121, 112, 100, 120, 92, 101, 103, 99,
 	},
 	// Chrominance.
 	{
-		17, 18, 24, 47, 99, 99, 99, 99,
-		18, 21, 26, 66, 99, 99, 99, 99,
-		24, 26, 56, 99, 99, 99, 99, 99,
-		47, 66, 99, 99, 99, 99, 99, 99,
+		17, 18, 18, 24, 21, 24, 47, 26,
+		26, 47, 99, 66, 56, 66, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
 		99, 99, 99, 99, 99, 99, 99, 99,
 		99, 99, 99, 99, 99, 99, 99, 99,
 		99, 99, 99, 99, 99, 99, 99, 99,
@@ -222,7 +224,7 @@ type encoder struct {
 	buf [16]byte
 	// bits and nBits are accumulated bits to write to w.
 	bits, nBits uint32
-	// quant is the scaled quantization tables.
+	// quant is the scaled quantization tables, in zig-zag order.
 	quant [nQuantIndex][blockSize]byte
 }
 
@@ -301,7 +303,7 @@ func (e *encoder) writeMarkerHeader(marker uint8, markerlen int) {
 
 // writeDQT writes the Define Quantization Table marker.
 func (e *encoder) writeDQT() {
-	markerlen := 2 + int(nQuantIndex)*(1+blockSize)
+	const markerlen = 2 + int(nQuantIndex)*(1+blockSize)
 	e.writeMarkerHeader(dqtMarker, markerlen)
 	for i := range e.quant {
 		e.writeByte(uint8(i))
@@ -311,7 +313,7 @@ func (e *encoder) writeDQT() {
 
 // writeSOF0 writes the Start Of Frame (Baseline) marker.
 func (e *encoder) writeSOF0(size image.Point) {
-	markerlen := 8 + 3*nColorComponent
+	const markerlen = 8 + 3*nColorComponent
 	e.writeMarkerHeader(sof0Marker, markerlen)
 	e.buf[0] = 8 // 8-bit color.
 	e.buf[1] = uint8(size.Y >> 8)
@@ -344,8 +346,8 @@ func (e *encoder) writeBlock(b *block, q quantIndex, prevDC int) int {
 	// Emit the DC delta.
 	e.emitHuffRLE(huffIndex(2*q+0), 0, dc-prevDC)
 	// Emit the AC components.
-	h, runLength := huffIndex(2*q+1), 0
-	for k := 1; k < blockSize; k++ {
-		ac := div(b[unzig[k]], (8 * int(e.quant[q][k])))\
+	h, runLength := huffIndex(2*q+1), 0
+	for zig := 1; zig < blockSize; zig++ {
+		ac := div(b[unzig[zig]], (8 * int(e.quant[q][zig])))\
 		if ac == 0 {
 			runLength++
 		} else {
@@ -446,6 +449,7 @@ func (e *encoder) writeSOS(m image.Image) {
 	e.write(sosHeader)
 	var (
 		// Scratch buffers to hold the YCbCr values.
+		// The blocks are in natural (not zig-zag) order.
 		yBlock  block
 		cbBlock [4]block
 		crBlock [4]block

src/pkg/image/jpeg/writer_test.go

--- a/src/pkg/image/jpeg/writer_test.go
+++ b/src/pkg/image/jpeg/writer_test.go
@@ -6,6 +6,87 @@ package jpeg
 
 import (
 	"bytes"
+	"fmt"
 	"image"
 	"image/color"
 	"image/png"
@@ -15,6 +16,87 @@ import (
 	"testing"
 )
 
+// zigzag maps from the natural ordering to the zig-zag ordering. For example,
+// zigzag[0*8 + 3] is the zig-zag sequence number of the element in the fourth
+// column and first row.
+var zigzag = [blockSize]int{
+	0, 1, 5, 6, 14, 15, 27, 28,
+	2, 4, 7, 13, 16, 26, 29, 42,
+	3, 8, 12, 17, 25, 30, 41, 43,
+	9, 11, 18, 24, 31, 40, 44, 53,
+	10, 19, 23, 32, 39, 45, 52, 54,
+	20, 22, 33, 38, 46, 51, 55, 60,
+	21, 34, 37, 47, 50, 56, 59, 61,
+	35, 36, 48, 49, 57, 58, 62, 63,
+}
+
+func TestZigUnzig(t *testing.T) {
+	for i := 0; i < blockSize; i++ {
+		if unzig[zigzag[i]] != i {
+			t.Errorf("unzig[zigzag[%d]] == %d", i, unzig[zigzag[i]])
+		}
+		if zigzag[unzig[i]] != i {
+			t.Errorf("zigzag[unzig[%d]] == %d", i, zigzag[unzig[i]])
+		}
+	}
+}
+
+// unscaledQuantInNaturalOrder are the unscaled quantization tables in
+// natural (not zig-zag) order, as specified in section K.1.
+var unscaledQuantInNaturalOrder = [nQuantIndex][blockSize]byte{
+	// Luminance.
+	{
+		16, 11, 10, 16, 24, 40, 51, 61,
+		12, 12, 14, 19, 26, 58, 60, 55,
+		14, 13, 16, 24, 40, 57, 69, 56,
+		14, 17, 22, 29, 51, 87, 80, 62,
+		18, 22, 37, 56, 68, 109, 103, 77,
+		24, 35, 55, 64, 81, 104, 113, 92,
+		49, 64, 78, 87, 103, 121, 120, 101,
+		72, 92, 95, 98, 112, 100, 103, 99,
+	},
+	// Chrominance.
+	{
+		17, 18, 24, 47, 99, 99, 99, 99,
+		18, 21, 26, 66, 99, 99, 99, 99,
+		24, 26, 56, 99, 99, 99, 99, 99,
+		47, 66, 99, 99, 99, 99, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
+		99, 99, 99, 99, 99, 99, 99, 99,
+	},
+}
+
+func TestUnscaledQuant(t *testing.T) {
+	bad := false
+	for i := quantIndex(0); i < nQuantIndex; i++ {
+		for zig := 0; zig < blockSize; zig++ {
+			got := unscaledQuant[i][zig]
+			want := unscaledQuantInNaturalOrder[i][unzig[zig]]
+			if got != want {
+				t.Errorf("i=%d, zig=%d: got %d, want %d", i, zig, got, want)
+				bad = true
+			}
+		}
+	}
+	if bad {
+		names := [nQuantIndex]string{"Luminance", "Chrominance"}
+		buf := &bytes.Buffer{}
+		for i, name := range names {
+			fmt.Fprintf(buf, "// %s.\n{\n", name)
+			for zig := 0; zig < blockSize; zig++ {
+				fmt.Fprintf(buf, "%d, ", unscaledQuantInNaturalOrder[i][unzig[zig]])
+				if zig%8 == 7 {
+					buf.WriteString("\n")
+				}
+			}
+			buf.WriteString("},\n")
+		}
+		t.Logf("expected unscaledQuant values:\n%s", buf.String())
+	}
+}
+
 var testCase = []struct {
 	filename  string
 	quality   int

コアとなるコードの解説

reader.go の変更

reader.goでは、JPEGデコード時に量子化テーブルとDCT係数を正しく扱うための修正が行われました。

  • unzig配列のコメント: unzig配列は、ジグザグ順序で並んだ64個の要素(DCT係数や量子化テーブルの要素)が、8x8のブロック内でどの位置(ナチュラル順序)に対応するかを示すマッピングです。このコメントの追加により、unzigの役割がより明確になりました。例えば、ジグザグ順序のインデックス3(4番目の要素)は、ナチュラル順序のインデックス16(3行目、1列目)に対応するという具体的な例が示されています。
  • decoder.quantのコメント: デコーダが保持する量子化テーブルquantが、ジグザグ順序で格納されていることが明示されました。これにより、デコード処理全体で一貫した順序が期待されます。
  • processSOS関数内のAC係数デコード:
    • for k := 1; k < blockSize; k++for zig := 1; zig < blockSize; zig++に変更されたのは、ループ変数がジグザグ順序のインデックスを直接指すことを明確にするためです。
    • b[unzig[k]] = ac * qt[k]b[unzig[zig]] = ac * qt[zig]に変更されたのは、デコードされたAC係数acをナチュラル順序のブロックbに格納する際に、ジグザグ順序のインデックスzigunzig配列で変換して正しいナチュラル順序の位置に配置するためです。同時に、量子化テーブルqtもジグザグ順序でアクセスされるようになりました。
    • k += int(val0)などのインデックススキップもzig += int(val0)に変更され、ジグザグ順序での処理が徹底されています。

これらの変更により、デコーダはJPEGファイルから読み込んだジグザグ順序の量子化テーブルと係数を正しく解釈し、ナチュラル順序の画像データブロックに再構築できるようになりました。

writer.go の変更

writer.goでは、JPEGエンコード時に量子化テーブルをジグザグ順序で定義し、DCT係数を正しく処理するための修正が行われました。

  • unscaledQuant配列の修正: これがこのコミットの最も重要な変更点の一つです。unscaledQuantは、JPEG標準のセクションK.1で定義されている推奨量子化テーブルの値を保持します。以前のバージョンでは、これらの値がナチュラル順序で並べられていましたが、このコミットでは、これらの値がジグザグ順序に変換されて格納されるように修正されました。
    • 例えば、輝度(Luminance)テーブルの最初の数値を比較すると、以前は16, 11, 10, 16, ...でしたが、修正後は16, 11, 12, 14, ...となっています。これは、ナチュラル順序のテーブルをジグザグ走査の順序に並べ替えた結果です。
    • 新しいコメント「The values are derived from section K.1 after converting from natural to zig-zag order.」が追加され、この変換プロセスが明示されています。
  • encoder.quantのコメント: エンコーダが使用するスケーリング後の量子化テーブルquantも、ジグザグ順序で格納されていることが明示されました。
  • writeDQTおよびwriteSOF0markerlen: markerlen変数がconstになったのは、これらの値が固定長であり、コンパイル時に決定できるためです。これはコードの可読性と効率性を向上させます。
  • writeBlock関数内のAC係数エンコード:
    • for k := 1; k < blockSize; k++for zig := 1; zig < blockSize; zig++に変更され、ループがジグザグ順序のインデックスをたどることを明確にしました。
    • ac := div(b[unzig[k]], (8 * int(e.quant[q][k])))ac := div(b[unzig[zig]], (8 * int(e.quant[q][zig])))に変更されました。これは、ナチュラル順序のブロックbから係数を読み出す際にunzig配列を使ってジグザグ順序のインデックスに変換し、そのジグザグ順序のインデックスzigを使って量子化テーブルe.quant[q]から対応する量子化ステップサイズを取得していることを示しています。

これらの変更により、エンコーダはJPEG標準に準拠したジグザグ順序の量子化テーブルを生成し、DCT係数をジグザグ順序で効率的に符号化できるようになりました。

writer_test.go の変更

テストファイルでは、ジグザグ順序への変更が正しく行われたことを検証するための新しいテストが追加されました。

  • zigzag配列: unzig配列の逆マッピングを提供するzigzag配列が追加されました。これは、ナチュラル順序のインデックスをジグザグ順序のインデックスに変換します。
  • TestZigUnzig: このテストは、unzigzigzagの2つのマッピング配列が互いに正しく逆変換を行うことを検証します。これにより、ジグザグとナチュラル順序間の変換ロジックの基本的な正確性が保証されます。
  • unscaledQuantInNaturalOrder配列: JPEG標準のセクションK.1に記載されている、ナチュラル順序のままの量子化テーブルが定義されました。これは、unscaledQuant配列が正しくジグザグ順序に変換されたかを検証するための「真の値」として機能します。
  • TestUnscaledQuant: このテストは、writer.goで定義されているunscaledQuant配列(ジグザグ順序)が、unscaledQuantInNaturalOrder配列(ナチュラル順序)から正しく変換されていることを検証します。具体的には、unscaledQuant[i][zig](ジグザグ順序のテーブルのzig番目の要素)が、unscaledQuantInNaturalOrder[i][unzig[zig]](ナチュラル順序のテーブルのunzig[zig]番目の要素)と一致するかを確認します。これにより、unscaledQuantが実際にジグザグ順序で正しく格納されていることが保証され、エンコーダが標準に準拠した量子化テーブルを使用していることが確認できます。

これらのテストの追加により、量子化テーブルのジグザグ順序への変更が機能的に正しく、かつ標準に準拠していることが保証されます。

関連リンク

  • Go言語のimage/jpegパッケージのドキュメント: https://pkg.go.dev/image/jpeg
  • JPEG標準 (ITU-T Recommendation T.81 / ISO/IEC 10918-1): この標準のセクションK.1に量子化テーブルの定義があります。

参考にした情報源リンク

  • JPEG圧縮の仕組みに関する一般的な情報源(例: Wikipedia, 技術解説ブログなど)
  • JPEG標準の具体的なセクションK.1に関する情報(通常は標準文書自体、またはそれを解説する専門的な資料)
  • Go言語のimageパッケージのソースコード(特にimage/jpeg
  • Go言語のコードレビューシステム(Gerrit)のCLページ: https://golang.org/cl/6497083 (コミットメッセージに記載)
  • ジグザグ走査に関する解説記事
  • 量子化テーブルに関する解説記事