イメージ ストライド
ビデオ画像がメモリに格納されるとき、メモリ バッファーの各ピクセル行の後に、余分なパディング バイトが含まれる場合があります。 パディング バイトは画像をメモリに格納する方法に影響しますが、画像の表示方法には影響しません。
"ストライド" は、メモリ内のあるピクセル行から、メモリ内の次のピクセル行までのバイト数です。 ストライドは、"ピッチ" と呼ばれこともあります。 次の図に示すように、パディング バイトが存在する場合、ストライドは画像の幅より広くなります。
2 つのバッファーに同じサイズのビデオ フレームが格納されていても、ストライドは異なる可能性があります。 ビデオ画像を処理する場合、ストライドを考慮する必要があります。
また、メモリ内に画像を配置する方法は 2 つあります。 "トップダウン" 画像では、画像の先頭のピクセル行がメモリに最初に配置されます。 "ボトムアップ" 画像では、最後のピクセル行がメモリに最初に配置されます。 次の図は、トップダウン画像とボトムアップ画像の違いを示したものです。
ストライドは、表示される画像を基準にして、ピクセル行を下に移動するために必要なバイト数と定義されるため、ボトムアップ画像ではストライドは負になります。 YUV 画像は常にトップダウンである必要があり、Direct3D サーフェイスに含まれる画像はトップダウンである必要があります。 通常、システム メモリ内の RGB 画像はボトムアップです。
特にビデオ変換では、入力バッファーと出力バッファーが一致していない可能性があるため、ストライドが異なるバッファーを処理する必要があります。 たとえば、ソース画像を変換し、結果をターゲット画像に書き込む場合について考えます。 どちらの画像も幅と高さは同じですが、ピクセル形式または画像ストライドは異なる可能性があるとします。
次に示すのは、この種の関数を記述する際の一般的なアプローチのコード例です。 この例は、多くの具体的な細部が抽象化されているため、完全には動作しません。
void ProcessVideoImage(
BYTE* pDestScanLine0,
LONG lDestStride,
const BYTE* pSrcScanLine0,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
for (DWORD y = 0; y < dwHeightInPixels; y++)
{
SOURCE_PIXEL_TYPE *pSrcPixel = (SOURCE_PIXEL_TYPE*)pSrcScanLine0;
DEST_PIXEL_TYPE *pDestPixel = (DEST_PIXEL_TYPE*)pDestScanLine0;
for (DWORD x = 0; x < dwWidthInPixels; x +=2)
{
pDestPixel[x] = TransformPixelValue(pSrcPixel[x]);
}
pDestScanLine0 += lDestStride;
pSrcScanLine0 += lSrcStride;
}
}
この関数は、6 つのパラメーターを受け取ります。
- ターゲット画像内のスキャン行 0 の開始位置へのポインター。
- ターゲット画像のストライド。
- ソース画像内のスキャン行 0 の開始位置へのポインター。
- ソース画像のストライド。
- 画像の幅 (ピクセル単位)。
- 画像の高さ (ピクセル単位)。
一般的な考え方は、一度に 1 行ずつ処理し、行内の各ピクセルを反復処理することです。 SOURCE_PIXEL_TYPE と DEST_PIXEL_TYPE はそれぞれ、ソースとターゲットの画像のピクセル レイアウトを表す構造体であるとします。 (たとえば、32 ビット RGB では、RGBQUAD 構造体が使われます。すべてのピクセル形式に定義済みの構造体があるわけではありません。) 配列ポインターを構造体型にキャストすると、各ピクセルの RGB または YUV 成分にアクセスできます。 各行の処理の開始時に、関数は行へのポインターを格納します。 行の処理の終了時には、画像ストライドの幅だけポインターをインクリメントして、ポインターを次の行に進めます。
この例では、ピクセルごとに TransformPixelValue という名前の仮想の関数を呼び出しています。 これは、ソース ピクセルからターゲット ピクセルを計算する、どのような関数でもかまいません。 もちろん、正確な詳細は特定のタスクによって異なります。 たとえば、平面 YUV 形式の場合は、彩度平面と輝度平面に個別にアクセスする必要があります。インターレース ビデオでは、フィールドを個別に処理することが必要な場合があります。
具体的な例として、次のコードでは 32 ビット RGB 画像を AYUV 画像に変換しています。 RGB のピクセルには RGBQUAD 構造体を使ってアクセスし、AYUV のピクセルには DXVA2_AYUVSample8 構造体を使ってアクセスします。
//-------------------------------------------------------------------
// Name: RGB32_To_AYUV
// Description: Converts an image from RGB32 to AYUV
//-------------------------------------------------------------------
void RGB32_To_AYUV(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
for (DWORD y = 0; y < dwHeightInPixels; y++)
{
RGBQUAD *pSrcPixel = (RGBQUAD*)pSrc;
DXVA2_AYUVSample8 *pDestPixel = (DXVA2_AYUVSample8*)pDest;
for (DWORD x = 0; x < dwWidthInPixels; x++)
{
pDestPixel[x].Alpha = 0x80;
pDestPixel[x].Y = RGBtoY(pSrcPixel[x]);
pDestPixel[x].Cb = RGBtoU(pSrcPixel[x]);
pDestPixel[x].Cr = RGBtoV(pSrcPixel[x]);
}
pDest += lDestStride;
pSrc += lSrcStride;
}
}
次の例では、32 ビット RGB 画像を YV12 画像に変換しています。 この例では、平面 YUV 形式を処理する方法を示します。 (YV12 は平面 4:2:0 形式です。) この例の関数は、ターゲット画像の 3 つの平面に対して 3 つの個別のポインターを保持します。 ただし、基本的なアプローチは前の例と同じです。
void RGB32_To_YV12(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
assert(dwWidthInPixels % 2 == 0);
assert(dwHeightInPixels % 2 == 0);
const BYTE *pSrcRow = pSrc;
BYTE *pDestY = pDest;
// Calculate the offsets for the V and U planes.
// In YV12, each chroma plane has half the stride and half the height
// as the Y plane.
BYTE *pDestV = pDest + (lDestStride * dwHeightInPixels);
BYTE *pDestU = pDest +
(lDestStride * dwHeightInPixels) +
((lDestStride * dwHeightInPixels) / 4);
// Convert the Y plane.
for (DWORD y = 0; y < dwHeightInPixels; y++)
{
RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
for (DWORD x = 0; x < dwWidthInPixels; x++)
{
pDestY[x] = RGBtoY(pSrcPixel[x]); // Y0
}
pDestY += lDestStride;
pSrcRow += lSrcStride;
}
// Convert the V and U planes.
// YV12 is a 4:2:0 format, so each chroma sample is derived from four
// RGB pixels.
pSrcRow = pSrc;
for (DWORD y = 0; y < dwHeightInPixels; y += 2)
{
RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
RGBQUAD *pNextSrcRow = (RGBQUAD*)(pSrcRow + lSrcStride);
BYTE *pbV = pDestV;
BYTE *pbU = pDestU;
for (DWORD x = 0; x < dwWidthInPixels; x += 2)
{
// Use a simple average to downsample the chroma.
*pbV++ = ( RGBtoV(pSrcPixel[x]) +
RGBtoV(pSrcPixel[x + 1]) +
RGBtoV(pNextSrcRow[x]) +
RGBtoV(pNextSrcRow[x + 1]) ) / 4;
*pbU++ = ( RGBtoU(pSrcPixel[x]) +
RGBtoU(pSrcPixel[x + 1]) +
RGBtoU(pNextSrcRow[x]) +
RGBtoU(pNextSrcRow[x + 1]) ) / 4;
}
pDestV += lDestStride / 2;
pDestU += lDestStride / 2;
// Skip two lines on the source image.
pSrcRow += (lSrcStride * 2);
}
}
これらのすべての例では、画像のストライドはアプリケーションによって既に決定されているとします。 メディア バッファーからこの情報を取得できる場合があります。 それ以外の場合は、ビデオ形式に基づいて計算する必要があります。 画像のストライドの計算とビデオのメディア バッファーの操作について詳しくは、「圧縮されていないビデオ バッファー」をご覧ください。
関連トピック