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

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

このコミットは、Go言語の標準ライブラリである image/jpeg パッケージに、JPEG画像の4:4:0クロマサブサンプリング形式の読み込みサポートを追加するものです。これにより、GoのJPEGデコーダが対応するJPEGフォーマットの範囲が拡張され、より多様なJPEG画像を正確に処理できるようになります。

コミット

commit b6cc39d0dd8f5a778c5f7643403355685a80d193
Author: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
Date:   Wed Jun 27 14:37:17 2012 -0700

    image/jpeg: support for reading 4:4:0 subsampling.
    Updates #2362.

    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6326057

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

https://github.com/golang/go/commit/b6cc39d0dd8f5f7643403355685a80d193

元コミット内容

image/jpeg: support for reading 4:4:0 subsampling. Updates #2362.

このコミットは、JPEG画像の4:4:0サブサンプリング形式の読み込みに対応するための変更です。これは、Issue #2362の解決に関連しています。

変更の背景

JPEG画像は、色情報を効率的に圧縮するためにクロマサブサンプリングという技術を使用します。これは、人間の目が輝度(明るさ)の変化には敏感である一方で、色相や彩度(色情報)の変化には比較的鈍感であるという特性を利用したものです。そのため、色情報を間引いて保存することで、画質への影響を最小限に抑えつつファイルサイズを削減できます。

Goの image/jpeg パッケージは、これまで4:4:4(サブサンプリングなし)、4:2:2、4:2:0といった一般的なクロマサブサンプリング形式に対応していました。しかし、特定のアプリケーションやカメラで生成されるJPEG画像の中には、4:4:0という形式を使用するものも存在しました。この形式に対応していない場合、Goのライブラリではこれらの画像を正しくデコードできず、エラーが発生するか、あるいは誤った色情報で画像が生成される可能性がありました。

このコミットは、このような互換性の問題を解決し、GoのJPEGデコーダがより広範なJPEG画像を処理できるようにするために導入されました。具体的には、Issue #2362で報告された問題に対応しています。

前提知識の解説

JPEGとクロマサブサンプリング

JPEG (Joint Photographic Experts Group) は、静止画像を圧縮するための一般的な標準です。JPEG圧縮は、主に以下のステップで行われます。

  1. 色空間変換: RGB(赤、緑、青)色空間からY'CbCr色空間への変換が行われます。

    • Y' (Luma): 輝度成分。画像の明るさを表します。
    • Cb (Chroma Blue): 青色差成分。青と黄色の間の色情報を表します。
    • Cr (Chroma Red): 赤色差成分。赤とシアンの間の色情報を表します。 人間の目は輝度情報に最も敏感であるため、Y'成分は通常、CbおよびCr成分よりも詳細に保持されます。
  2. クロマサブサンプリング: CbとCr成分の解像度を意図的に下げて、データ量を削減します。これが「サブサンプリング」です。一般的なサブサンプリング形式には以下があります。

    • 4:4:4 (No Subsampling): Y', Cb, Crの各成分が同じ解像度で保持されます。最も高品質ですが、ファイルサイズは大きくなります。
    • 4:2:2: 水平方向にCbとCrのサンプル数が半分になります。つまり、2つの輝度サンプルに対して1つの色差サンプルが対応します。垂直方向は同じ解像度です。
    • 4:2:0: 水平方向と垂直方向の両方でCbとCrのサンプル数が半分になります。つまり、4つの輝度サンプル(2x2のブロック)に対して1つの色差サンプルが対応します。最も一般的な形式で、ファイルサイズが小さくなります。
    • 4:4:0: このコミットで追加された形式です。水平方向はY'と同じ解像度ですが、垂直方向はCbとCrのサンプル数が半分になります。つまり、2つの輝度サンプル(1x2のブロック)に対して1つの色差サンプルが対応します。これは、垂直方向の色の変化が少ない画像や、特定のシステムで効率的な処理を行うために使用されることがあります。

JPEGデコーダの動作原理

JPEGデコーダは、圧縮されたJPEGデータストリームを読み込み、以下の主要なステップで画像を再構築します。

  1. ヘッダ解析: JPEGファイルのヘッダを読み込み、画像サイズ、色空間、サブサンプリング形式、量子化テーブル、ハフマンテーブルなどのメタデータを抽出します。
  2. データデコード: 圧縮された輝度および色差データをデコードします。これには、ハフマン復号、逆量子化、逆DCT (Discrete Cosine Transform) などの処理が含まれます。
  3. アップサンプリング: サブサンプリングされたCbとCr成分を、Y'成分と同じ解像度に戻します。このプロセスは、通常、線形補間などのアルゴリズムを使用して行われます。
  4. 色空間変換: Y'CbCr色空間からRGB色空間に変換し、最終的な画像データを生成します。

このコミットの変更は、特にヘッダ解析時のサブサンプリング形式の識別と、その後のアップサンプリング処理に影響を与えます。

技術的詳細

このコミットの主要な変更点は、Goの image/jpeg パッケージが4:4:0クロマサブサンプリング形式を認識し、それに基づいてY'CbCr画像を正しく構築できるようにすることです。

JPEGのSOF (Start Of Frame) マーカーには、各コンポーネント(Y, Cb, Cr)の水平および垂直サブサンプリング係数 (h, v) が含まれています。

  • 4:4:4の場合、Y, Cb, Crすべてが (1,1) です。
  • 4:2:2の場合、Yが (2,1)、Cb, Crが (1,1) です。
  • 4:2:0の場合、Yが (2,2)、Cb, Crが (1,1) です。
  • 4:4:0の場合、Yが (1,2)、Cb, Crが (1,1) です。

このコミットでは、主に以下の変更が行われています。

  1. image/jpeg/reader.go の変更:

    • processSOF 関数において、Yコンポーネントのサブサンプリング比率のチェックに 0x12 (h=1, v=2) が追加されました。これにより、デコーダは4:4:0形式のJPEGファイルを「サポートされていない」と判断せずに処理を進めることができます。
    • makeImg 関数において、h0 (Yの水平サブサンプリング係数) と v0 (Yの垂直サブサンプリング係数) の値に基づいて YCbCrSubsampleRatio を決定するロジックが更新されました。具体的には、h0 == 1 && v0 == 2 の場合に image.YCbCrSubsampleRatio440 が割り当てられるようになりました。
    • processSOS 関数において、DCTブロックのオフセット計算ロジックが修正されました。特に、Yコンポーネントのブロック位置 (mx0, my0) の計算が、4:4:0形式(h0 == 1の場合)に対応するように調整されています。これにより、垂直方向にサブサンプリングされているY成分のブロックが正しく配置されるようになります。
  2. image/ycbcr.go の変更:

    • YCbCrSubsampleRatio 型に YCbCrSubsampleRatio440 という新しい定数が追加されました。これは、4:4:0サブサンプリング形式を識別するための列挙値です。
    • String() メソッドが更新され、YCbCrSubsampleRatio440 の文字列表現が追加されました。
    • YCbCr 構造体のドキュメントコメントに、4:4:0形式における CStridelen(Cb)/len(Cr) の関係が追記されました。
    • COffset 関数が更新され、YCbCrSubsampleRatio440 の場合のCb/Cr成分のオフセット計算ロジックが追加されました。4:4:0では、Cb/Crは垂直方向に半分の解像度であるため、y/2 を使用しつつ、水平方向はYと同じ解像度であるため x をそのまま使用します。
    • NewYCbCr 関数が更新され、YCbCrSubsampleRatio440 の場合のCb/Cr成分の幅 (cw) と高さ (ch) の計算ロジックが追加されました。幅はYと同じ (w)、高さはYの半分 ((r.Max.Y+1)/2 - r.Min.Y/2) となります。
  3. image/ycbcr_test.go の変更:

    • TestYCbCr 関数内のテスト対象の YCbCrSubsampleRatio のリストに YCbCrSubsampleRatio440 が追加されました。これにより、新しいサブサンプリング形式が既存のテストフレームワークで検証されるようになります。

これらの変更により、Goの image/jpeg パッケージは、4:4:0サブサンプリング形式のJPEG画像を正しくデコードし、Goの image.YCbCr 型として表現できるようになります。

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

src/pkg/image/jpeg/reader.go

--- a/src/pkg/image/jpeg/reader.go
+++ b/src/pkg/image/jpeg/reader.go
@@ -51,7 +51,7 @@ const (
 	// A color JPEG image has Y, Cb and Cr components.
 	nColorComponent = 3
 
-	// We only support 4:4:4, 4:2:2 and 4:2:0 downsampling, and therefore the
+	// We only support 4:4:4, 4:4:0, 4:2:2 and 4:2:0 downsampling, and therefore the
 	// number of luma samples per chroma sample is at most 2 in the horizontal
 	// and 2 in the vertical direction.
 	maxH = 2
@@ -154,12 +154,12 @@ func (d *decoder) processSOF(n int) error {
 		if d.nComp == nGrayComponent {
 			continue
 		}
-		// For color images, we only support 4:4:4, 4:2:2 or 4:2:0 chroma
+		// For color images, we only support 4:4:4, 4:4:0, 4:2:2 or 4:2:0 chroma
 		// downsampling ratios. This implies that the (h, v) values for the Y
-		// component are either (1, 1), (2, 1) or (2, 2), and the (h, v)
+		// component are either (1, 1), (1, 2), (2, 1) or (2, 2), and the (h, v)
 		// values for the Cr and Cb components must be (1, 1).
 		if i == 0 {
-			if hv != 0x11 && hv != 0x21 && hv != 0x22 {
+			if hv != 0x11 && hv != 0x21 && hv != 0x22 && hv != 0x12 {
 				return UnsupportedError("luma downsample ratio")
 			}
 		} else if hv != 0x11 {
@@ -203,12 +203,14 @@ func (d *decoder) makeImg(h0, v0, mxx, myy int) {
 		return
 	}
 	var subsampleRatio image.YCbCrSubsampleRatio
-	switch h0 * v0 {
-	case 1:
+	switch {
+	case h0 == 1 && v0 == 1:
 		subsampleRatio = image.YCbCrSubsampleRatio444
-	case 2:
+	case h0 == 1 && v0 == 2:
+		subsampleRatio = image.YCbCrSubsampleRatio440
+	case h0 == 2 && v0 == 1:
 		subsampleRatio = image.YCbCrSubsampleRatio422
-	case 4:
+	case h0 == 2 && v0 == 2:
 		subsampleRatio = image.YCbCrSubsampleRatio420
 	default:
 		panic("unreachable")
@@ -313,8 +315,13 @@ func (d *decoder) processSOS(n int) error {
 					} else {
 						switch i {
 						case 0:
-							mx0 := h0*mx + (j % 2)
-							my0 := v0*my + (j / 2)
+							mx0, my0 := h0*mx, v0*my
+							if h0 == 1 {
+								my0 += j
+							} else {
+								mx0 += j % 2
+								my0 += j / 2
+							}
 							idct(d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride, &b)
 						case 1:
 							idct(d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b)

src/pkg/image/ycbcr.go

--- a/src/pkg/image/ycbcr.go
+++ b/src/pkg/image/ycbcr.go
@@ -15,6 +15,7 @@ const (
 	YCbCrSubsampleRatio444 YCbCrSubsampleRatio = iota
 	YCbCrSubsampleRatio422
 	YCbCrSubsampleRatio420
+	YCbCrSubsampleRatio440
 )
 
 func (s YCbCrSubsampleRatio) String() string {
@@ -25,6 +26,8 @@ func (s YCbCrSubsampleRatio) String() string {
 		return "YCbCrSubsampleRatio422"
 	case YCbCrSubsampleRatio420:
 		return "YCbCrSubsampleRatio420"
+	case YCbCrSubsampleRatio440:
+		return "YCbCrSubsampleRatio440"
 	}\n\treturn "YCbCrSubsampleRatioUnknown"
 }
 
@@ -39,6 +42,7 @@ func (s YCbCrSubsampleRatio) String() string {
 //	For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1.
 //	For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2.
 //	For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4.
+//	For 4:4:0, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/2.
 type YCbCr struct {
 	Y, Cb, Cr      []uint8
 	YStride        int
@@ -82,6 +86,8 @@ func (p *YCbCr) COffset(x, y int) int {
 		return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2)
 	case YCbCrSubsampleRatio420:
 		return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2)
+	case YCbCrSubsampleRatio440:
+		return (y/2-p.Rect.Min.Y/2)*p.CStride + (x - p.Rect.Min.X)
 	}
 	// Default to 4:4:4 subsampling.
 	return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X)
@@ -126,6 +132,9 @@ func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
 	case YCbCrSubsampleRatio420:
 		cw = (r.Max.X+1)/2 - r.Min.X/2
 		ch = (r.Max.Y+1)/2 - r.Min.Y/2
+	case YCbCrSubsampleRatio440:
+		cw = w
+		ch = (r.Max.Y+1)/2 - r.Min.Y/2
 	default:
 		// Default to 4:4:4 subsampling.
 		cw = w

src/pkg/image/ycbcr_test.go

--- a/src/pkg/image/ycbcr_test.go
+++ b/src/pkg/image/ycbcr_test.go
@@ -36,6 +36,7 @@ func TestYCbCr(t *testing.T) {
 		YCbCrSubsampleRatio444,
 		YCbCrSubsampleRatio422,
 		YCbCrSubsampleRatio420,
+		YCbCrSubsampleRatio440,
 	}
 	deltas := []Point{
 		Pt(0, 0),

コアとなるコードの解説

src/pkg/image/jpeg/reader.go

  • processSOF 関数: JPEGファイルのSOF (Start Of Frame) マーカーを処理し、画像の基本情報を読み込む部分です。ここで、Yコンポーネントの水平・垂直サブサンプリング係数 (hv) が検証されます。hv != 0x11 && hv != 0x21 && hv != 0x22 という既存のチェックに && hv != 0x12 が追加されました。0x12 は、水平1、垂直2のサブサンプリング比率(つまり4:4:0)を意味します。これにより、4:4:0形式のJPEGが「サポートされていない」と誤って判断されるのを防ぎます。
  • makeImg 関数: デコードされた画像データを格納するための image.YCbCr 構造体を初期化する部分です。h0 (Yの水平係数) と v0 (Yの垂直係数) の組み合わせに基づいて、適切な YCbCrSubsampleRatio を決定します。switch 文が h0 * v0 から switch { ... } に変更され、h0 == 1 && v0 == 2 のケースが追加され、YCbCrSubsampleRatio440 が割り当てられるようになりました。これは、4:4:0形式の画像を正しく識別し、対応する内部表現にマッピングするために不可欠です。
  • processSOS 関数: JPEGのSOS (Start Of Scan) マーカーを処理し、実際の画像データ(DCT係数)をデコードする部分です。特に、YコンポーネントのDCTブロックのオフセット (mx0, my0) の計算ロジックが修正されました。4:4:0形式では、Yコンポーネントは垂直方向にサブサンプリングされているため、h0 == 1 の場合に my0 += j と計算することで、垂直方向のブロック位置が正しく決定されるようになります。これにより、Y成分のデータが正しく配置され、最終的な画像が歪むことなく再構築されます。

src/pkg/image/ycbcr.go

  • YCbCrSubsampleRatio: Goの image パッケージで定義されている、YCbCr色空間のサブサンプリング比率を表す列挙型です。ここに YCbCrSubsampleRatio440 が追加されました。これにより、Goの画像処理ライブラリ全体で4:4:0形式を型安全に扱うことが可能になります。
  • String() メソッド: YCbCrSubsampleRatio 型の文字列表現を返すメソッドです。デバッグやログ出力の際に、サブサンプリング形式を人間が読める形式で表示するために、YCbCrSubsampleRatio440 のケースが追加されました。
  • YCbCr 構造体のコメント: YCbCr 構造体のドキュメントに、4:4:0形式における CStridelen(Cb)/len(Cr) の関係が追記されました。これは、この構造体を使用する開発者にとって、各サブサンプリング形式でのメモリレイアウトとデータアクセス方法を理解するための重要な情報となります。
  • COffset 関数: YCbCr 画像内の指定された座標 (x, y) におけるCbまたはCr成分のオフセット(配列インデックス)を計算する関数です。YCbCrSubsampleRatio440 のケースが追加され、y/2 を使用して垂直方向のサブサンプリングを考慮しつつ、水平方向は x をそのまま使用することで、正しいオフセットが計算されるようになりました。
  • NewYCbCr 関数: 新しい YCbCr 構造体を指定された矩形とサブサンプリング比率で初期化する関数です。YCbCrSubsampleRatio440 のケースが追加され、Cb/Cr成分の幅 (cw) と高さ (ch) が、4:4:0の特性(幅はYと同じ、高さはYの半分)に基づいて正しく計算されるようになりました。

src/pkg/image/ycbcr_test.go

  • TestYCbCr 関数: YCbCr 型の基本的な動作を検証するテスト関数です。テスト対象の subsampleRatios スライスに YCbCrSubsampleRatio440 が追加されました。これにより、4:4:0形式の YCbCr 画像が正しく作成され、そのオフセット計算などが期待通りに機能するかどうかが自動的にテストされるようになります。これは、新しい機能が既存のコードベースに悪影響を与えず、意図した通りに動作することを保証するための重要なステップです。

これらの変更は、Goの画像処理ライブラリがJPEGの4:4:0サブサンプリング形式を完全にサポートするための基盤を構築しています。

関連リンク

参考にした情報源リンク