[インデックス 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とは異なる色差成分の配置を持つため、この汎用的な処理では最適化の余地がありました。
このコミットでは、以下の点が変更されています。
-
drawYCbCr
関数の戻り値の変更: 変更前はvoid
でしたが、変更後は(ok bool)
を返すようになりました。これは、特定のサブサンプリング比率(4:4:4、4:2:2、4:2:0、4:4:0)に対して高速パスが適用された場合にtrue
を返し、それ以外の場合(未知のサブサンプリング比率など)にfalse
を返すことで、呼び出し元がフォールバック処理を行うかどうかを判断できるようにするためです。 -
image.YCbCrSubsampleRatio444
の高速パスの追加:switch src.SubsampleRatio
ステートメントにcase image.YCbCrSubsampleRatio444:
が明示的に追加されました。これにより、4:4:4の画像に対して専用のループが実行され、Y、Cb、Crの各成分がピクセルごとに直接対応するため、最も単純な変換ロジックが適用されます。 -
image.YCbCrSubsampleRatio440
の高速パスの追加と最適化: 変更前はdefault
で処理されていた4:4:0のケースが、case image.YCbCrSubsampleRatio440:
として明示的に追加されました。 このケースでは、水平方向には色差成分が間引かれていないためci
の計算は(sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
となりますが、垂直方向には半分に間引かれているため、ci
の計算においてsy
をsy/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形式の画像描画がより効率的になります。 -
default
ケースの変更:switch
ステートメントのdefault
ケースは、return false
となりました。これは、上記で明示的に処理されたサブサンプリング比率以外の未知の比率が来た場合に、高速パスが適用できないことを呼び出し元に通知するためです。
これらの変更により、drawYCbCr
関数は、一般的なYCbCrサブサンプリング比率に対してより効率的な描画パスを提供し、特に4:4:0形式の画像処理性能が向上しました。
また、テストデータとして src/pkg/image/testdata/video-001.q50.440.jpeg
と src/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.go
の drawYCbCr
関数が主な変更点です。
--- 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
の変更
-
DrawMask
関数内のdrawYCbCr
呼び出しの変更:- drawYCbCr(dst0, r, src0, sp) - return + if drawYCbCr(dst0, r, src0, sp) { + return + }
drawYCbCr
がbool
を返すようになったため、その戻り値を確認し、true
の場合にのみreturn
するように変更されました。これは、drawYCbCr
が高速パスを適用できたかどうかを呼び出し元に伝えるための変更です。もしfalse
が返された場合、呼び出し元は別の汎用的な描画ロジックにフォールバックする可能性があります。 -
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)
を返すように変更されました。これにより、関数が特定のサブサンプリング比率に対して最適化された処理を実行できたかどうかを示すことができます。 -
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
変換が行われます。 -
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形式の画像描画がより正確かつ効率的に行われます。 -
default
ケースの変更:+ default: + return false } + return true
switch
ステートメントのdefault
ケースは、明示的に処理されていないSubsampleRatio
の場合にfalse
を返すように変更されました。これは、この関数が高速パスを提供できないことを示し、呼び出し元がフォールバック処理を行うべきであることを示唆します。 そして、いずれかのcase
にマッチして処理が完了した場合はreturn true
が実行され、高速パスが適用されたことを示します。
テストファイルの変更
-
src/pkg/image/jpeg/reader_test.go
: 新しいテストデータファイル../testdata/video-001.q50.440
がtestCases
スライスに追加されました。これにより、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言語の
image
パッケージドキュメント: https://pkg.go.dev/image - Go言語の
image/draw
パッケージドキュメント: https://pkg.go.dev/image/draw - Go言語の
image/color
パッケージドキュメント: https://pkg.go.dev/image/color - クロマサブサンプリングに関するWikipedia記事: https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%9E%E3%82%B5%E3%83%96%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B0
参考にした情報源リンク
- Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/6699049 (コミットメッセージに記載されているChange-ID)
- Go言語のドキュメント (pkg.go.dev)
- クロマサブサンプリングに関する一般的な知識