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

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

このコミットは、Go言語の image/draw パッケージにおいて、YCbCr形式の画像データ、特に4:4:0クロマサブサンプリングされたソース画像に対する描画処理のパフォーマンスを向上させるための高速パスを追加するものです。これにより、特定のYCbCr画像形式の描画がより効率的に行われるようになります。

コミット

  • コミットハッシュ: 5abf4bdc2775e4514f9cf8e2bf842f685eba2fd8
  • Author: Nigel Tao nigeltao@golang.org
  • Date: Fri Oct 19 10:55:41 2012 +1100
  • Message: image/draw: fast-path for 4:4:0 chroma subsampled sources.

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

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

元コミット内容

image/draw: fast-path for 4:4:0 chroma subsampled sources.

R=r
CC=golang-dev
https://golang.org/cl/6699049

変更の背景

Go言語の image パッケージは、様々な画像フォーマットを扱うための機能を提供しています。image/draw パッケージは、画像間の描画操作(例えば、ある画像を別の画像にコピーする)を処理します。JPEGなどの画像フォーマットでは、色情報を効率的に表現するために「クロマサブサンプリング」という技術が用いられます。

YCbCr形式の画像は、輝度(Y)と2つの色差(Cb, Cr)の成分で構成されます。人間の目は輝度情報に比べて色差情報に対する感度が低いため、色差情報を間引く(サブサンプリングする)ことで、画質への影響を最小限に抑えつつファイルサイズを削減できます。

このコミット以前は、image/draw パッケージの drawYCbCr 関数は、YCbCr画像の描画において、特定のクロマサブサンプリング比率(例えば4:4:4、4:2:2、4:2:0)に対して最適化された処理を持っていましたが、4:4:0という特定のサブサンプリング比率に対しては、汎用的な処理パスが使用されていました。汎用的な処理は、特定のケースに最適化された処理に比べてパフォーマンスが劣る可能性があります。

この変更の背景には、4:4:0クロマサブサンプリングされた画像ソースの描画性能を向上させるという目的があります。特に、image.YCbCrSubsampleRatio440 のケースを明示的に処理することで、より効率的なピクセル変換とメモリアクセスを実現し、描画速度の改善を図っています。

前提知識の解説

image/draw パッケージ

image/draw パッケージは、Go言語の image パッケージと連携して、画像間の描画操作を提供します。例えば、Draw 関数や DrawMask 関数を使って、ある image.Image を別の image.Image に描画することができます。このパッケージは、異なる画像フォーマット間での変換や、アルファブレンドなどの複雑な描画モードもサポートしています。

image.YCbCr

image.YCbCr は、Go言語の image パッケージで定義されているYCbCrカラーモデルの画像を表す型です。この型は、輝度(Y)成分と2つの色差(Cb、Cr)成分を別々のバイトスライスとして保持します。

image.YCbCr 型には SubsampleRatio というフィールドがあり、これはYCbCr画像のクロマサブサンプリング比率を示します。

クロマサブサンプリング

クロマサブサンプリングは、画像の色情報を圧縮するための技術です。人間の視覚システムは輝度(明るさ)の変化には敏感ですが、色の変化には比較的鈍感であるという特性を利用します。これにより、色情報を間引いても、知覚される画質の劣化を最小限に抑えることができます。

一般的なクロマサブサンプリングの比率には以下のものがあります。

  • 4:4:4: サブサンプリングなし。Y、Cb、Crの各成分が同じ解像度を持ちます。最高品質ですが、データ量も最大です。
  • 4:2:2: 水平方向に色差成分を半分に間引きます。輝度情報が2ピクセルごとに1つの色差情報が対応します。
  • 4:2:0: 水平方向と垂直方向の両方で色差成分を半分に間引きます。輝度情報が4ピクセルごとに1つの色差情報が対応します。最も一般的なサブサンプリング比率で、JPEGやMPEGなどで広く使われています。
  • 4:4:0: このコミットで扱われる比率です。水平方向には色差成分を間引かず、垂直方向に半分に間引きます。つまり、各水平ラインで輝度情報と同じ数の色差情報がありますが、垂直方向には2ラインごとに1つの色差情報が対応します。

color.YCbCrToRGB 関数

color.YCbCrToRGB 関数は、YCbCrカラーモデルのY、Cb、Crの各成分を受け取り、それらをRGBカラーモデルのR、G、B成分に変換するGo言語の標準ライブラリ関数です。画像描画において、YCbCr形式のデータを画面表示に適したRGB形式に変換する際に使用されます。

技術的詳細

このコミットの主要な変更は、src/pkg/image/draw/draw.go ファイル内の drawYCbCr 関数にあります。

drawYCbCr 関数は、image.YCbCr 型のソース画像を image.RGBA 型のデスティネーション画像に描画する役割を担っています。この関数は、ソース画像の SubsampleRatio に応じて異なる描画ロジックを適用します。

変更前は、image.YCbCrSubsampleRatio444 のケースが default ブロックで処理されており、これは実質的に4:4:4のサブサンプリングを想定したものでした。しかし、4:4:0のサブサンプリングは4:4:4とは異なる色差成分の配置を持つため、この汎用的な処理では最適化の余地がありました。

このコミットでは、以下の点が変更されています。

  1. drawYCbCr 関数の戻り値の変更: 変更前は void でしたが、変更後は (ok bool) を返すようになりました。これは、特定のサブサンプリング比率(4:4:4、4:2:2、4:2:0、4:4:0)に対して高速パスが適用された場合に true を返し、それ以外の場合(未知のサブサンプリング比率など)に false を返すことで、呼び出し元がフォールバック処理を行うかどうかを判断できるようにするためです。

  2. image.YCbCrSubsampleRatio444 の高速パスの追加: switch src.SubsampleRatio ステートメントに case image.YCbCrSubsampleRatio444: が明示的に追加されました。これにより、4:4:4の画像に対して専用のループが実行され、Y、Cb、Crの各成分がピクセルごとに直接対応するため、最も単純な変換ロジックが適用されます。

  3. image.YCbCrSubsampleRatio440 の高速パスの追加と最適化: 変更前は default で処理されていた4:4:0のケースが、case image.YCbCrSubsampleRatio440: として明示的に追加されました。 このケースでは、水平方向には色差成分が間引かれていないため ci の計算は (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X) となりますが、垂直方向には半分に間引かれているため、ci の計算において sysy/2 で扱う必要があります。具体的には、ci := (sy/2-src.Rect.Min.Y/2)*src.CStride + (sp.X - src.Rect.Min.X) のように、垂直方向のインデックス計算に sy/2 を使用することで、4:4:0の特性に合わせた正しい色差成分の参照が行われます。これにより、4:4:0形式の画像描画がより効率的になります。

  4. default ケースの変更: switch ステートメントの default ケースは、return false となりました。これは、上記で明示的に処理されたサブサンプリング比率以外の未知の比率が来た場合に、高速パスが適用できないことを呼び出し元に通知するためです。

これらの変更により、drawYCbCr 関数は、一般的なYCbCrサブサンプリング比率に対してより効率的な描画パスを提供し、特に4:4:0形式の画像処理性能が向上しました。

また、テストデータとして src/pkg/image/testdata/video-001.q50.440.jpegsrc/pkg/image/testdata/video-001.q50.440.progressive.jpeg が追加され、src/pkg/image/jpeg/reader_test.go にもこれらの新しいテストケースが追加されています。これにより、4:4:0サブサンプリングのJPEG画像が正しくデコードされ、描画されることが保証されます。

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

src/pkg/image/draw/draw.godrawYCbCr 関数が主な変更点です。

--- a/src/pkg/image/draw/draw.go
+++ b/src/pkg/image/draw/draw.go
@@ -81,8 +81,9 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
 				case *image.YCbCr:
-					drawYCbCr(dst0, r, src0, sp)
-					return
+					if drawYCbCr(dst0, r, src0, sp) {
+						return
+					}
 				}
 			} else if mask0, ok := mask.(*image.Alpha); ok {
 				switch src0 := src.(type) {
@@ -104,8 +105,9 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
 				case *image.YCbCr:
-					drawYCbCr(dst0, r, src0, sp)
-					return
+					if drawYCbCr(dst0, r, src0, sp) {
+						return
+					}
 				}
 			}
 		}
@@ -345,7 +347,7 @@ func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image
 	}
 }
 
-func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) {
+func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) (ok bool) {
 	// An image.YCbCr is always fully opaque, and so if the mask is implicitly nil
 	// (i.e. fully opaque) then the op is effectively always Src.
 	x0 := (r.Min.X - dst.Rect.Min.X) * 4
@@ -353,6 +355,19 @@ func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) {
 	y0 := r.Min.Y - dst.Rect.Min.Y
 	y1 := r.Max.Y - dst.Min.Y
 	switch src.SubsampleRatio {
+	case image.YCbCrSubsampleRatio444:
+		for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+			dpix := dst.Pix[y*dst.Stride:]
+			yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+			ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
+			for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
+				rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
+				dpix[x+0] = rr
+				dpix[x+1] = gg
+				dpix[x+2] = bb
+				dpix[x+3] = 255
+			}
+		}
 	case image.YCbCrSubsampleRatio422:
 		for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
 			dpix := dst.Pix[y*dst.Stride:]
@@ -381,12 +396,11 @@ func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po
 			dpix[x+3] = 255
 		}
 	}
-	default:
-		// Default to 4:4:4 subsampling.
+	case image.YCbCrSubsampleRatio440:
 		for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
 			dpix := dst.Pix[y*dst.Stride:]
 			yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
-			ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
+			ci := (sy/2-src.Rect.Min.Y/2)*src.CStride + (sp.X - src.Rect.Min.X)
 			for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
 				rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
 				dpix[x+0] = rr
@@ -395,7 +409,10 @@ func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po
 				dpix[x+3] = 255
 			}
 		}
+	default:
+		return false
 	}
+	return true
 }
 
 func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.Uniform, mask *image.Alpha, mp image.Point) {

また、テスト関連のファイルも変更されています。

  • src/pkg/image/jpeg/reader_test.go
  • src/pkg/image/testdata/video-001.q50.440.jpeg (新規追加)
  • src/pkg/image/testdata/video-001.q50.440.progressive.jpeg (新規追加)

コアとなるコードの解説

src/pkg/image/draw/draw.go の変更

  1. DrawMask 関数内の drawYCbCr 呼び出しの変更:

    -					drawYCbCr(dst0, r, src0, sp)
    -					return
    +					if drawYCbCr(dst0, r, src0, sp) {
    +						return
    +					}
    

    drawYCbCrbool を返すようになったため、その戻り値を確認し、true の場合にのみ return するように変更されました。これは、drawYCbCr が高速パスを適用できたかどうかを呼び出し元に伝えるための変更です。もし false が返された場合、呼び出し元は別の汎用的な描画ロジックにフォールバックする可能性があります。

  2. drawYCbCr 関数のシグネチャ変更:

    -func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) {
    +func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) (ok bool) {
    

    関数が (ok bool) を返すように変更されました。これにより、関数が特定のサブサンプリング比率に対して最適化された処理を実行できたかどうかを示すことができます。

  3. image.YCbCrSubsampleRatio444 の高速パスの追加:

    +	case image.YCbCrSubsampleRatio444:
    +		for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
    +			dpix := dst.Pix[y*dst.Stride:]
    +			yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
    +			ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
    +			for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
    +				rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
    +				dpix[x+0] = rr
    +				dpix[x+1] = gg
    +				dpix[x+2] = bb
    +				dpix[x+3] = 255
    +			}
    +		}
    

    4:4:4サブサンプリングは、Y、Cb、Crの各成分がピクセルごとに対応するため、最も単純な変換ロジックが適用されます。yi (Y成分のインデックス) と ci (Cb/Cr成分のインデックス) は同じように計算され、各ピクセルで YCbCrToRGB 変換が行われます。

  4. image.YCbCrSubsampleRatio440 の高速パスの追加と ci の計算の修正:

    -	default:
    -		// Default to 4:4:4 subsampling.
    +	case image.YCbCrSubsampleRatio440:
     		for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
     			dpix := dst.Pix[y*dst.Stride:]
     			yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
    -			ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
    +			ci := (sy/2-src.Rect.Min.Y/2)*src.CStride + (sp.X - src.Rect.Min.X)
     			for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
     				rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
     				dpix[x+0] = rr
     				dpix[x+1] = gg
     				dpix[x+2] = bb
     				dpix[x+3] = 255
     			}
     		}
    

    このブロックは、4:4:0サブサンプリングの特性に合わせて ci (Cb/Cr成分のインデックス) の計算を修正しています。4:4:0では垂直方向に色差成分が半分に間引かれるため、sy (ソース画像のY座標) を sy/2 で割ることで、正しい色差成分の行を参照するようにしています。これにより、4:4:0形式の画像描画がより正確かつ効率的に行われます。

  5. default ケースの変更:

    +	default:
    +		return false
     	}
    +	return true
    

    switch ステートメントの default ケースは、明示的に処理されていない SubsampleRatio の場合に false を返すように変更されました。これは、この関数が高速パスを提供できないことを示し、呼び出し元がフォールバック処理を行うべきであることを示唆します。 そして、いずれかの case にマッチして処理が完了した場合は return true が実行され、高速パスが適用されたことを示します。

テストファイルの変更

  • src/pkg/image/jpeg/reader_test.go: 新しいテストデータファイル ../testdata/video-001.q50.440testCases スライスに追加されました。これにより、JPEGデコーダが4:4:0サブサンプリングの画像を正しく読み込めるかどうかがテストされます。

  • src/pkg/image/testdata/video-001.q50.440.jpeg および src/pkg/image/testdata/video-001.q50.440.progressive.jpeg: これらのバイナリファイルは、4:4:0クロマサブサンプリングされたJPEG画像のテストデータとして追加されました。これらは、新しい高速パスが正しく機能することを確認するために使用されます。

これらの変更により、Go言語の image/draw パッケージは、4:4:0クロマサブサンプリングされたYCbCr画像の描画において、より効率的で正確な処理を提供できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
  • Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/6699049 (コミットメッセージに記載されているChange-ID)
  • Go言語のドキュメント (pkg.go.dev)
  • クロマサブサンプリングに関する一般的な知識