Шаг изображения
Если изображение видео хранится в памяти, буфер памяти может содержать дополнительные байты с заполнением после каждой строки пикселей. Заполнение байтов влияет на то, как изображение хранится в памяти, но не влияет на отображение изображения.
Шаг — это количество байтов из одной строки пикселей в памяти до следующей строки пикселей в памяти. Шаг также называется полем. Если байты заполнения присутствуют, шаг шире ширины изображения, как показано на следующем рисунке.
Два буфера, содержащие видеокадры с равными измерениями, могут иметь два разных шага. При обработке изображения видео необходимо учитывать шаг.
Кроме того, существует два способа упорядочить изображение в памяти. В верхнем нижнем изображении верхняя строка пикселей в изображении отображается сначала в памяти. В нижнем углу изображения в памяти отображается последняя строка пикселей. На следующем рисунке показано различие между изображением сверху вниз и изображением вниз.
Снизу вверх изображение имеет отрицательный шаг, так как шаг определяется как количество байтов, необходимо переместить вниз по строке пикселей относительно отображаемого изображения. Изображения 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);
}
}
Во всех этих примерах предполагается, что приложение уже определило шаг изображения. Иногда эти сведения можно получить из буфера мультимедиа. В противном случае необходимо вычислить его на основе формата видео. Дополнительные сведения о вычислении шага изображения и работе с буферами мультимедиа для видео см. в разделе "Несжатые буферы видео".
См. также