[インデックス 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)は、主に写真などの連続階調画像を効率的に圧縮するための標準です。その圧縮プロセスは、主に以下のステップで構成されます。
- 色空間変換: RGB画像をY'CbCr色空間に変換します。Y'は輝度(明るさ)、CbとCrは色差(色合い)を表します。人間の目は輝度変化に敏感で、色差変化には比較的鈍感であるため、色差成分の情報を間引くことで高い圧縮率を実現します(クロマサブサンプリング)。
- DCT(離散コサイン変換): 画像を8x8ピクセルのブロックに分割し、各ブロックに対してDCTを適用します。DCTは、空間領域のピクセルデータを周波数領域の係数に変換します。これにより、画像内の低周波成分(滑らかな変化)と高周波成分(細かいディテール)が分離されます。低周波成分は画像の主要な情報を持ち、高周波成分は細かいディテールやノイズを表します。
- 量子化: DCTによって得られた周波数係数を量子化します。これは、各係数を量子化テーブルの対応する値で割り、結果を丸めるプロセスです。このステップがJPEG圧縮の「非可逆性」の主要な原因であり、人間の視覚が感知しにくい高周波成分の情報を大胆に破棄することで、高い圧縮率を実現します。量子化テーブルの値が大きいほど、より多くの情報が破棄され、圧縮率が高まりますが、画質は低下します。
- エントロピー符号化: 量子化された係数をさらに圧縮します。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.go
、writer.go
、writer_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
に格納する際に、ジグザグ順序のインデックスzig
をunzig
配列で変換してナチュラル順序のインデックスを得ていることを示しています。また、量子化テーブルqt
もジグザグ順序でアクセスされるようになりました。k += int(val0)
やk += 0x0f
といったインデックスのスキップ処理も、zig += int(val0)
やzig += 0x0f
に変更され、ジグザグ順序でのインデックス操作に統一されました。
- AC係数をデコードするループ変数が
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]
もジグザグ順序でアクセスしていることを示しています。
- AC係数をエンコードするループ変数が
src/pkg/image/jpeg/writer_test.go
zigzag
配列の追加:zigzag
配列は、ナチュラル順序のインデックスをジグザグ順序のインデックスにマッピングするための新しい配列です。これはunzig
の逆マッピングを提供します。
TestZigUnzig
テストの追加:unzig
とzigzag
配列が互いに逆関数として正しく機能するかを検証するテストです。unzig[zigzag[i]] == i
とzigzag[unzig[i]] == i
がすべてのインデックスi
で成り立つことを確認します。これにより、ジグザグとナチュラル順序間の変換ロジックの正確性が保証されます。
unscaledQuantInNaturalOrder
配列の追加:- JPEG標準のセクションK.1で定義されている、ナチュラル順序のままの標準量子化テーブルを保持する新しい配列です。これは、
unscaledQuant
が正しくジグザグ順序に変換されたかを検証するための参照として使用されます。
- JPEG標準のセクションK.1で定義されている、ナチュラル順序のままの標準量子化テーブルを保持する新しい配列です。これは、
TestUnscaledQuant
テストの追加:unscaledQuant
配列(ジグザグ順序)が、unscaledQuantInNaturalOrder
配列(ナチュラル順序)から正しく変換されているかを検証するテストです。got := unscaledQuant[i][zig]
とwant := unscaledQuantInNaturalOrder[i][unzig[zig]]
を比較することで、unscaledQuant
のzig
番目の要素が、ナチュラル順序のテーブルの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
に格納する際に、ジグザグ順序のインデックスzig
をunzig
配列で変換して正しいナチュラル順序の位置に配置するためです。同時に、量子化テーブル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.
」が追加され、この変換プロセスが明示されています。
- 例えば、輝度(Luminance)テーブルの最初の数値を比較すると、以前は
encoder.quant
のコメント: エンコーダが使用するスケーリング後の量子化テーブルquant
も、ジグザグ順序で格納されていることが明示されました。writeDQT
およびwriteSOF0
のmarkerlen
: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
: このテストは、unzig
とzigzag
の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 (コミットメッセージに記載)
- ジグザグ走査に関する解説記事
- 量子化テーブルに関する解説記事