Шаг изображения

Если изображение видео хранится в памяти, буфер памяти может содержать дополнительные байты с заполнением после каждой строки пикселей. Заполнение байтов влияет на то, как изображение хранится в памяти, но не влияет на отображение изображения.

Шаг — это количество байтов из одной строки пикселей в памяти до следующей строки пикселей в памяти. Шаг также называется полем. Если байты заполнения присутствуют, шаг шире ширины изображения, как показано на следующем рисунке.

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

Эта функция принимает шесть параметров:

  • Указатель на начало строки сканирования 0 в целевом изображении.
  • Шаг целевого образа.
  • Указатель на начало строки сканирования 0 в исходном изображении.
  • Шаг исходного изображения.
  • Ширина изображения в пикселях.
  • Высота изображения в пикселях.

Общая идея заключается в обработке одной строки за раз, итерации по каждому пикселю в строке. Предположим, что SOURCE_PIXEL_TYPE и DEST_PIXEL_TYPE — это структуры, представляющие макет пикселей для исходных и целевых изображений соответственно. (Например, 32-разрядная RGB использует Структура RGBQUAD . Не каждый формат пикселей имеет предопределенную структуру.) Приведение указателя массива к типу структуры позволяет получить доступ к компонентам RGB или YUV каждого пикселя. В начале каждой строки функция сохраняет указатель на строку. В конце строки он увеличивает указатель на ширину шага изображения, который перемещает указатель на следующую строку.

В этом примере вызывается гипотетическая функция с именем TransformPixelValue для каждого пикселя. Это может быть любая функция, вычисляющая целевой пиксель из исходного пикселя. Конечно, точные сведения будут зависеть от конкретной задачи. Например, если у вас есть планарный формат YUV, необходимо получить доступ к плоскости хрома независимо от плоскости luma; с чередующимся видео может потребоваться обработать поля отдельно; и так далее.

Чтобы дать более конкретный пример, следующий код преобразует 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);
    }
}

Во всех этих примерах предполагается, что приложение уже определило шаг изображения. Иногда эти сведения можно получить из буфера мультимедиа. В противном случае необходимо вычислить его на основе формата видео. Дополнительные сведения о вычислении шага изображения и работе с буферами мультимедиа для видео см. в разделе "Несжатые буферы видео".

Типы мультимедиа видео

Типы носителей