이미지 스트라이드

비디오 이미지가 메모리에 저장되면 메모리 버퍼에 각 픽셀 행 뒤의 추가 패딩 바이트가 포함될 수 있습니다. 패딩 바이트는 이미지가 메모리에 저장되는 방식에 영향을 주지만 이미지가 표시되는 방식에는 영향을 미치지 않습니다.

보폭메모리에 있는 픽셀의 한 행에서 메모리의 다음 픽셀 행까지의 바이트 수입니다. 스트라이드를 피치라고도 합니다. 패딩 바이트가 있는 경우 다음 그림과 같이 보폭이 이미지의 너비보다 넓습니다.

diagram showing an image plus padding.

동일한 차원의 비디오 프레임을 포함하는 두 개의 버퍼는 두 개의 다른 보폭을 가질 수 있습니다. 비디오 이미지를 처리하는 경우 한 걸음 더 나아갔습니다.

또한 이미지를 메모리에 정렬할 수 있는 두 가지 방법이 있습니다. 하향식 이미지에서 이미지의 위쪽 픽셀 행이 메모리에 먼저 나타납니다. 상향식 이미지에서 마지막 픽셀 행이 메모리에 먼저 나타납니다. 다음 그림에서는 하향식 이미지와 상향식 이미지의 차이점을 보여 줍니다.

diagram showing top-down and bottom-up images.

보폭은 표시된 이미지를 기준으로 픽셀 행 아래로 이동해야 하는 바이트 수로 정의되므로 상향식 이미지의 보폭은 음수입니다. 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 시작에 대한 포인터입니다.
  • 원본 이미지의 보폭입니다.
  • 이미지의 너비(픽셀)입니다.
  • 이미지의 높이(픽셀)입니다.

일반적인 아이디어는 행의 각 픽셀을 반복하여 한 번에 하나의 행을 처리하는 것입니다. 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 형식입니다.) 이 예제에서 함수는 대상 이미지의 세 평면에 대한 세 개의 개별 포인터를 기본. 그러나 기본 방법은 이전 예제와 동일합니다.

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);
    }
}

이러한 모든 예제에서는 애플리케이션이 이미 이미지 보폭을 결정한 것으로 가정합니다. 미디어 버퍼에서 이 정보를 가져올 수 있는 경우도 있습니다. 그렇지 않으면 비디오 형식에 따라 계산해야 합니다. 이미지 보폭 계산 및 비디오용 미디어 버퍼 작업에 대한 자세한 내용은 압축되지 않은 비디오 버퍼를 참조 하세요.

비디오 미디어 형식

미디어 형식