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

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

このコミットは、Go言語のimageパッケージにおけるYCbCr画像表現の内部構造を根本的に変更するものです。具体的には、YCbCr画像のピクセルバッファが、画像の矩形領域の最小座標(Rect.Min)から開始するように修正されました。これにより、YCbCr画像がRGBAやGrayなどの他の画像タイプと同様に、正の象限に限定されない任意の座標に配置できるようになります。また、YCbCr画像の描画コードのパフォーマンス最適化も含まれています。

コミット

commit ab2ea94c609cb2e6b6dd61ceea93a88a7a66b090
Author: Nigel Tao <nigeltao@golang.org>
Date:   Fri Jan 20 10:44:22 2012 +1100

    image: change the YCbCr image's pixel buffers to start at Rect.Min
    instead of the origin.
    
    This makes YCbCr match the other image types (e.g. RGBA, Gray) in
    that an image's bounds is not restricted to the positive quadrant.
    
    Also optimize the YCbCr draw code by hoisting some computation
    outside of the loop.
    
    benchmark              old ns/op    new ns/op    delta
    draw.BenchmarkYCbCr      2544418      2373558   -6.72%
    
    Like https://golang.org/cl/4681044/ I don't think a gofix is
    feasible. People will have to make manual changes. On the other hand,
    directly manipulating YCbCr images is relatively rare, compared to
    RGBA images, and if other code just uses the jpeg and draw packages
    instead of messing directly with a YCbCr's []byte representations,
    then things should just continue to work.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/5558048

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

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

元コミット内容

imageパッケージにおいて、YCbCr画像のピクセルバッファが原点からではなく、Rect.Minから開始するように変更されました。これにより、YCbCr画像が他の画像タイプ(例: RGBA, Gray)と同様に、境界が正の象限に限定されないようになります。また、YCbCr描画コードの最適化として、ループ外への計算の巻き上げが行われました。この変更により、draw.BenchmarkYCbCrベンチマークで約6.72%のパフォーマンス向上が見られました。この変更はgofixツールでの自動修正が困難であるため、手動でのコード修正が必要となる可能性がありますが、YCbCr画像を直接操作するケースは比較的稀であるため、jpegdrawパッケージを介して利用している場合は影響が少ないとされています。

変更の背景

この変更の主な背景は以下の2点です。

  1. 画像表現の一貫性の向上: 従来のGoのimageパッケージでは、RGBAGrayといった他の画像タイプは、そのピクセルデータが画像の矩形領域(Rect)の最小座標(Rect.Min)を基準としてオフセットされていました。しかし、YCbCr画像は、そのピクセルバッファが常に原点(0,0)を基準としていました。この不整合は、画像処理を行う際にYCbCr画像を他の画像タイプと組み合わせる場合や、画像のサブイメージを扱う場合に混乱や不便を生じさせていました。このコミットは、YCbCr画像の内部表現を他の画像タイプと統一し、Rect.Minを基準とするようにすることで、APIの一貫性と使いやすさを向上させることを目的としています。これにより、YCbCr画像も負の座標や任意のオフセットを持つことができるようになります。

  2. パフォーマンスの最適化: YCbCr画像をRGBA画像に描画する際のパフォーマンス改善も目的の一つです。特に、ピクセルごとの色変換(YCbCrからRGBへの変換)を行うループ内で繰り返し計算されていたオフセット計算をループの外に移動(hoisting)することで、計算コストを削減し、描画処理の高速化を図っています。ベンチマーク結果が示すように、この最適化により顕著なパフォーマンス向上が達成されています。

これらの変更は、Go言語の画像処理ライブラリの堅牢性と効率性を高めるための重要なステップと言えます。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1. YCbCr色空間

YCbCrは、デジタルビデオや画像圧縮で広く使用される色空間です。人間の視覚が輝度(明るさ)の変化に敏感で、色差(色の違い)の変化には比較的鈍感であるという特性を利用しています。

  • Y (Luma): 輝度成分。画像の明るさを表します。
  • Cb (Chroma Blue): 青色差成分。青と黄色の間の色差を表します。
  • Cr (Chroma Red): 赤色差成分。赤とシアンの間の色差を表します。

YCbCrはRGB(赤、緑、青)色空間から変換され、通常、Y成分はフル解像度で保持される一方で、CbとCr成分は人間の視覚特性に合わせてサブサンプリング(間引き)されることが多いです。

2. クロマサブサンプリング (Chroma Subsampling)

YCbCr色空間の大きな利点の一つは、色差成分(Cb, Cr)を間引くことでデータ量を削減できることです。これは、人間の目が輝度の変化に比べて色の変化に鈍感であるという事実に基づいています。一般的なサブサンプリング方式には以下のものがあります。

  • 4:4:4: サブサンプリングなし。Y, Cb, Cr成分がすべて同じ解像度を持ちます。
  • 4:2:2: 水平方向に色差成分を半分に間引きます。つまり、2つのYピクセルに対して1つのCbと1つのCrピクセルが対応します。
  • 4:2:0: 水平方向と垂直方向の両方で色差成分を半分に間引きます。つまり、4つのYピクセル(2x2のブロック)に対して1つのCbと1つのCrピクセルが対応します。

Goのimage.YCbCr構造体は、このサブサンプリング比率をSubsampleRatioフィールドで管理しています。

3. Go言語のimageパッケージにおける画像表現

Go言語の標準ライブラリであるimageパッケージは、様々な画像フォーマットを扱うための基本的な型と関数を提供します。

  • image.Imageインターフェース: すべての画像タイプが実装するインターフェースで、Bounds() RectangleColorModel() color.ModelAt(x, y int) color.Colorなどのメソッドを定義します。
  • image.Rectangle構造体: 画像の矩形領域を表します。Min(左上隅の座標)とMax(右下隅の座標)のimage.Pointを含みます。Min.X, Min.Y, Max.X, Max.Yで構成され、Dx()(幅)とDy()(高さ)メソッドを持ちます。
  • image.YCbCr構造体: YCbCr画像を表現するための具体的な型です。
    • Y, Cb, Cr: それぞれ輝度、青色差、赤色差のピクセルデータを格納する[]uint8スライス。
    • YStride, CStride: それぞれY、Cb/Crスライスにおける行ごとのバイト数(ピクセル数)を表します。これにより、メモリ上の連続したデータから特定の行のピクセルにアクセスできます。
    • SubsampleRatio: 使用されているクロマサブサンプリング比率。
    • Rect: 画像の論理的な境界を表すimage.Rectangle

4. ピクセルバッファのオフセット

Goのimageパッケージでは、画像のピクセルデータは通常、一次元バイトスライス([]uint8)としてメモリに格納されます。特定の座標(x, y)のピクセルデータにアクセスするためには、この一次元スライス内でのオフセットを計算する必要があります。

従来のimage.YCbCrでは、Y, Cb, Crスライスは常に論理的な画像の原点(0,0)から始まるものとして扱われていました。しかし、image.RGBAなどの他の画像タイプでは、Rect.Minが画像の論理的な左上隅を表し、ピクセルデータへのアクセスはRect.Minからの相対座標で計算されます。このコミットは、YCbCrRect.Minを基準とするように変更し、画像表現の一貫性を高めています。

技術的詳細

このコミットの技術的な変更点は多岐にわたりますが、主に以下の3つの側面があります。

1. YCbCr画像のピクセルバッファの基準点変更

最も重要な変更は、image.YCbCr構造体のY, Cb, Crスライスが、画像の論理的な境界であるRect.Minを基準としてピクセルデータを格納するように変更された点です。

変更前: YCbCr画像のピクセルデータは、常に(0,0)を原点としてメモリに配置されていると仮定されていました。したがって、YOffsetCOffsetのようなメソッドは、単にy*YStride + xのような計算でオフセットを求めていました。これは、Rect.Minが常に(0,0)であるか、またはピクセルデータへのアクセス時にRect.Minが考慮されない場合にのみ正しく機能します。

変更後: YCbCr構造体のY, Cb, Crスライスは、Rect.Minで定義される画像の論理的な左上隅に対応するピクセルから開始するように変更されました。これにより、YOffsetおよびCOffsetメソッドの計算式が以下のように変更されました。

  • YOffset(x, y int) int: 変更前: return y*p.YStride + x 変更後: return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X) この変更により、Yスライス内のインデックスは、Rect.Minを基準とした相対座標(x - p.Rect.Min.X, y - p.Rect.Min.Y)に基づいて計算されるようになりました。

  • COffset(x, y int) int: 変更前: return y*p.CStride + (x / 2) (4:2:2の場合) 変更後: return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2) (4:2:2の場合) 同様に、CbおよびCrスライス内のインデックスもRect.Minを基準とした相対座標に基づいて計算されます。サブサンプリング比率に応じて、xyが2で割られる点も考慮されています。

この変更により、YCbCr画像もRect.Minが(0,0)以外の値を持つことが可能になり、画像が負の座標空間に存在したり、サブイメージとして切り出されたりした場合でも、ピクセルデータへのアクセスが正しく行われるようになります。

2. SubImageメソッドの実装変更

image.YCbCrSubImageメソッドは、元の画像の一部を新しいYCbCr画像として返す機能を提供します。

変更前: SubImageは、元のYCbCr構造体をシャローコピーし、Rectフィールドのみを更新していました。これは、ピクセルデータスライス(Y, Cb, Cr)が元の画像と共有されるため、新しいRectが元の画像のピクセルバッファの範囲外を指す可能性があり、Atメソッドなどでパニックを引き起こす可能性がありました。

変更後: SubImageメソッドは、新しいYCbCr構造体を生成し、そのY, Cb, Crスライスを元の画像の対応するスライスのサブスライスとして設定します。この際、新しい矩形領域rMin.XMin.Yに対応するオフセットを計算し、そのオフセットから始まるサブスライスを作成します。

// 変更後のSubImageの抜粋
func (p *YCbCr) SubImage(r Rectangle) Image {
    r = r.Intersect(p.Rect)
    if r.Empty() {
        return &YCbCr{SubsampleRatio: p.SubsampleRatio}
    }
    yi := p.YOffset(r.Min.X, r.Min.Y)
    ci := p.COffset(r.Min.X, r.Min.Y)
    return &YCbCr{
        Y:              p.Y[yi:],
        Cb:             p.Cb[ci:],
        Cr:             p.Cr[ci:],
        SubsampleRatio: p.SubsampleRatio,
        YStride:        p.YStride,
        CStride:        p.CStride,
        Rect:           r,
    }
}

この変更により、SubImageによって返される画像は、そのRectが示す範囲内のピクセルデータのみを参照するようになり、より安全で直感的な動作が実現されます。

3. NewYCbCrコンストラクタの追加

image.YCbCr型を適切に初期化するための新しいコンストラクタ関数NewYCbCrが追加されました。

追加前: YCbCr画像を生成するには、手動でY, Cb, Crスライスを確保し、YStride, CStride, SubsampleRatio, Rectを設定する必要がありました。特に、サブサンプリング比率に応じてCbCrのバッファサイズやストライドを正しく計算するのは複雑でした。

追加後: NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr関数が追加されました。この関数は、指定された矩形領域rとサブサンプリング比率に基づいて、必要なサイズのY, Cb, Crバッファを自動的に確保し、YCbCr構造体を適切に初期化します。これにより、YCbCr画像の生成がより簡単かつ安全になりました。

// NewYCbCr関数の抜粋
func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
    w, h, cw, ch := r.Dx(), r.Dy(), 0, 0
    switch subsampleRatio {
    case YCbCrSubsampleRatio422:
        cw = (r.Max.X+1)/2 - r.Min.X/2
        ch = h
    case YCbCrSubsampleRatio420:
        cw = (r.Max.X+1)/2 - r.Min.X/2
        ch = (r.Max.Y+1)/2 - r.Min.Y/2
    default: // 4:4:4
        cw = w
        ch = h
    }
    b := make([]byte, w*h+2*cw*ch) // Y, Cb, Crのバッファをまとめて確保
    return &YCbCr{
        Y:              b[:w*h],
        Cb:             b[w*h+0*cw*ch : w*h+1*cw*ch],
        Cr:             b[w*h+1*cw*ch : w*h+2*cw*ch],
        SubsampleRatio: subsampleRatio,
        YStride:        w,
        CStride:        cw,
        Rect:           r,
    }
}

この関数は、jpegパッケージのリーダーなど、YCbCr画像を生成する場所で利用されるようになります。

4. draw.YCbCr描画コードの最適化

src/pkg/image/draw/draw.go内のdrawYCbCr関数において、YCbCr画像をRGBA画像に描画する際のループ内の計算が最適化されました。

変更前: ループ内でyi (Yオフセット) やci (Cb/Crオフセット) がピクセルごとに再計算されていました。

変更後: yiciBase(Cb/Crのベースオフセット)の計算がループの外に巻き上げられました。ループ内では、これらのベースオフセットにsx(ソースX座標)やsx/2を加算するだけで、現在のピクセルのオフセットを効率的に計算できるようになりました。

// 4:2:2 サブサンプリングの場合の変更例
// 変更前:
// for x, sx := x0, sp.X; x != x1; x, sx = x+4, sx+1 {
//     i := sx / 2
//     yy = src.Y[sy*src.YStride+sx]
//     cb = src.Cb[sy*src.CStride+i]
//     cr = src.Cr[sy*src.CStride+i]
//     rr, gg, bb := color.YCbCrToRGB(yy, cb, cr)
// }

// 変更後:
// yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
// ciBase := (sy-src.Rect.Min.Y)*src.CStride - src.Rect.Min.X/2
// for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
//     ci := ciBase + sx/2
//     rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
// }

この最適化により、ループごとの計算量が減少し、描画パフォーマンスが向上しました。ベンチマーク結果(約6.72%の改善)がこの効果を裏付けています。

5. image.jpegパッケージの更新

src/pkg/image/jpeg/reader.go内のJPEGデコーダが、新しいNewYCbCrコンストラクタを使用するように変更されました。これにより、JPEG画像をデコードしてYCbCr画像を生成するプロセスが、新しいYCbCr表現と整合性が取れるようになりました。

6. テストケースの追加

src/pkg/image/ycbcr_test.goという新しいテストファイルが追加され、YCbCr構造体の変更が正しく機能することを確認するための広範なテストケースが記述されました。特に、様々な矩形領域、サブサンプリング比率、およびオフセットを持つYCbCr画像の生成、ピクセルアクセス(YOffset, COffset)、およびサブイメージの動作が検証されています。これにより、変更の正確性と堅牢性が保証されます。

これらの技術的変更は、Goの画像処理ライブラリの内部的な整合性を高め、パフォーマンスを改善し、将来的な拡張性を向上させるための重要なステップです。

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

このコミットで変更された主要なファイルと、その中のコアとなる変更箇所は以下の通りです。

  1. src/pkg/image/draw/draw.go:

    • drawYCbCr関数内のループ最適化。yy, cb, cr変数の削除と、yi, ciBaseの計算をループ外に巻き上げ、ループ内のオフセット計算を簡素化。
  2. src/pkg/image/image.go:

    • NewRGBA, NewRGBA64, NewNRGBA, NewNRGBA64, NewAlpha, NewAlpha16, NewGray, NewGray16関数のコメントが「width and height」から「bounds」に変更され、Rectangle引数が単なる幅と高さだけでなく、オフセットも含むことを明確化。
  3. src/pkg/image/jpeg/reader.go:

    • decoder.makeImg関数内で、image.YCbCrの生成方法が手動でのバイトスライス割り当てから、新しく追加されたimage.NewYCbCr関数を使用するように変更。
  4. src/pkg/image/ycbcr.go:

    • YCbCrSubsampleRatioString()メソッドが追加され、サブサンプリング比率の文字列表現を提供。
    • YCbCr構造体からY, Cb, Crフィールドの個別の宣言が削除され、Y, Cb, Cr []uint8としてまとめて宣言。これは機能的な変更ではなく、コードの簡潔化。
    • YOffsetメソッドのオフセット計算ロジックが、Rect.Minを考慮するように変更。
      • return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X)
    • COffsetメソッドのオフセット計算ロジックが、Rect.Minを考慮するように変更。サブサンプリング比率に応じてxyが2で割られる点も考慮。
      • return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2) (4:2:2の場合)
      • return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2) (4:2:0の場合)
      • return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X) (4:4:4の場合)
    • SubImageメソッドの実装が大幅に変更され、新しい矩形領域に対応するサブスライスを返すように修正。
    • NewYCbCr関数が新しく追加され、指定された矩形とサブサンプリング比率に基づいてYCbCr画像を初期化する機能を提供。
  5. src/pkg/image/ycbcr_test.go:

    • 新しいテストファイルとして追加TestYCbCr関数とtestYCbCrヘルパー関数が含まれ、様々なRectangleYCbCrSubsampleRatioPointの組み合わせでNewYCbCrYOffsetCOffsetSubImageAtメソッドの動作を検証。特に、Rect.Minが非ゼロの場合の動作が重点的にテストされています。

これらの変更は、YCbCr画像の内部表現と操作方法に大きな影響を与え、Goの画像処理ライブラリ全体の一貫性と効率性を向上させています。

コアとなるコードの解説

このコミットの核心は、image.YCbCr型のピクセルデータへのアクセス方法と、その生成・サブイメージ化のロジックの変更にあります。

src/pkg/image/ycbcr.go における変更

YOffset および COffset メソッドの変更

これらのメソッドは、特定の座標 (x, y) に対応するY、Cb、またはCr成分が格納されているバイトスライス内のインデックスを計算します。

変更前:

func (p *YCbCr) YOffset(x, y int) int {
	return y*p.YStride + x
}
func (p *YCbCr) COffset(x, y int) int {
	switch p.SubsampleRatio {
	case YCbCrSubsampleRatio422:
		return y*p.CStride + (x / 2)
	case YCbCrSubsampleRatio420:
		return (y/2)*p.CStride + (x / 2)
	}
	return y*p.CStride + x // Default to 4:4:4 subsampling.
}

この古い実装では、ピクセルデータが常に論理的な原点 (0,0) から始まるものと仮定していました。つまり、Rect.Min の値はオフセット計算に考慮されていませんでした。

変更後:

func (p *YCbCr) YOffset(x, y int) int {
	return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X)
}
func (p *YCbCr) COffset(x, y int) int {
	switch p.SubsampleRatio {
	case YCbCrSubsampleRatio422:
		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)
	}
	return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X) // Default to 4:4:4 subsampling.
}

新しい実装では、xy の座標から p.Rect.Min.Xp.Rect.Min.Y をそれぞれ減算しています。これにより、計算されるオフセットは、YCbCr画像の Rect.Min を基準とした相対的な位置を示すようになります。この変更により、YCbCr画像が負の座標空間に存在したり、サブイメージとして切り出されたりした場合でも、ピクセルデータへのアクセスが正しく行われるようになります。これは、他の image 型(例: RGBA)との一貫性を保つ上で非常に重要です。

SubImage メソッドの変更

SubImage メソッドは、既存の画像から指定された矩形領域に対応する新しい画像(サブイメージ)を生成します。

変更前:

func (p *YCbCr) SubImage(r Rectangle) Image {
	q := new(YCbCr)
	*q = *p
	q.Rect = q.Rect.Intersect(r)
	return q
}

この実装は、元の YCbCr 構造体をシャローコピーし、Rect フィールドのみを更新していました。これは、Y, Cb, Cr スライスが元の画像と共有されるため、新しい Rect が元の画像のピクセルバッファの範囲外を指す可能性があり、At メソッドなどでパニックを引き起こす可能性がありました。

変更後:

func (p *YCbCr) SubImage(r Rectangle) Image {
	r = r.Intersect(p.Rect)
	// If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
	// either r1 or r2 if the intersection is empty. Without explicitly checking for
	// this, the Pix[i:] expression below can panic.
	if r.Empty() {
		return &YCbCr{
			SubsampleRatio: p.SubsampleRatio,
		}
	}
	yi := p.YOffset(r.Min.X, r.Min.Y)
	ci := p.COffset(r.Min.X, r.Min.Y)
	return &YCbCr{
		Y:              p.Y[yi:],
		Cb:             p.Cb[ci:],
		Cr:             p.Cr[ci:],
		SubsampleRatio: p.SubsampleRatio,
		YStride:        p.YStride,
		CStride:        p.CStride,
		Rect:           r,
	}
}

新しい実装では、まず rp.Rect の交差部分を計算し、結果が空の場合は空の YCbCr を返します。重要なのは、p.Y[yi:], p.Cb[ci:], p.Cr[ci:] のように、元のスライスからサブスライスを作成している点です。yici は、新しい矩形領域 rMin.XMin.Y に対応する元のスライス内の開始インデックスです。これにより、返されるサブイメージは、その Rect が示す範囲内のピクセルデータのみを参照するようになり、より安全で直感的な動作が実現されます。

NewYCbCr 関数の追加

この関数は、指定された矩形領域とサブサンプリング比率に基づいて、新しい YCbCr 画像を生成し、そのピクセルバッファを適切に初期化します。

func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
	w, h, cw, ch := r.Dx(), r.Dy(), 0, 0
	switch subsampleRatio {
	case YCbCrSubsampleRatio422:
		cw = (r.Max.X+1)/2 - r.Min.X/2
		ch = h
	case YCbCrSubsampleRatio420:
		cw = (r.Max.X+1)/2 - r.Min.X/2
		ch = (r.Max.Y+1)/2 - r.Min.Y/2
	default: // Default to 4:4:4 subsampling.
		cw = w
		ch = h
	}
	// Y, Cb, Crのバッファをまとめて確保
	b := make([]byte, w*h+2*cw*ch)
	return &YCbCr{
		Y:              b[:w*h],
		Cb:             b[w*h+0*cw*ch : w*h+1*cw*ch],
		Cr:             b[w*h+1*cw*ch : w*h+2*cw*ch],
		SubsampleRatio: subsampleRatio,
		YStride:        w,
		CStride:        cw,
		Rect:           r,
	}
}

この関数は、r.Dx()r.Dy() を使用して画像の幅と高さを計算し、サブサンプリング比率に基づいて CbCr のコンポーネントの幅 (cw) と高さ (ch) を決定します。その後、必要なすべてのピクセルデータを格納するための単一のバイトスライス b を割り当て、そのスライスを Y, Cb, Cr の各フィールドに適切に分割して割り当てます。これにより、YCbCr 画像の生成がより簡単かつ安全になり、特に jpeg デコーダのような場所で利用されます。

src/pkg/image/draw/draw.go における変更

drawYCbCr 関数の最適化

この関数は、YCbCr 画像を RGBA 画像に描画する際に使用されます。

変更前:

// 4:2:2 サブサンプリングの場合の抜粋
for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
	dpix := dst.Pix[y*dst.Stride:]
	for x, sx := x0, sp.X; x != x1; x, sx = x+4, sx+1 {
		i := sx / 2
		yy = src.Y[sy*src.YStride+sx]
		cb = src.Cb[sy*src.CStride+i]
		cr = src.Cr[sy*src.CStride+i]
		rr, gg, bb := color.YCbCrToRGB(yy, cb, cr)
		// ... RGBAピクセル設定 ...
	}
}

このコードでは、内側のループ(xsx)の各イテレーションで yy, cb, cr のインデックス計算が繰り返し行われていました。

変更後:

// 4:2:2 サブサンプリングの場合の抜粋
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)
	ciBase := (sy-src.Rect.Min.Y)*src.CStride - src.Rect.Min.X/2
	for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
		ci := ciBase + sx/2
		rr, gg, bb := color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
		// ... RGBAピクセル設定 ...
	}
}

変更後では、yiciBase の計算が外側のループ(ysy)の各イテレーションで一度だけ行われるように巻き上げられています。内側のループでは、yiyi+1 でインクリメントされ、ciciBase + sx/2 で計算されます。これにより、ループ内の計算量が減少し、描画処理のパフォーマンスが向上します。これは、コミットメッセージに記載されているベンチマーク結果(約6.72%の改善)に貢献しています。

これらの変更は、Goの画像処理ライブラリにおける YCbCr 画像の扱いをより堅牢で効率的、かつ他の画像タイプと一貫性のあるものにするための重要なステップです。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分
  • Go言語のimageパッケージのドキュメント (Go標準ライブラリ)
  • YCbCr色空間とクロマサブサンプリングに関する一般的な知識