Partager via


Stride d’image

Lorsqu’une image vidéo est stockée en mémoire, la mémoire tampon peut contenir des octets de remplissage supplémentaires après chaque ligne de pixels. Les octets de remplissage affectent la façon dont l’image est stockée en mémoire, mais n’affectent pas la façon dont l’image est affichée.

Le stride correspond au nombre d’octets d’une ligne de pixels en mémoire à la ligne suivante de pixels en mémoire. Le stride est également appelé pitch. Si des octets de remplissage sont présents, le stride est plus large que la largeur de l’image, comme illustré dans l’illustration suivante.

diagram showing an image plus padding.

Deux mémoires tampons qui contiennent des trames vidéo avec des dimensions égales peuvent avoir deux strides différents. Si vous traitez une image vidéo, vous devez prendre en compte le stride.

En outre, il existe deux façons d’organiser une image en mémoire. Dans une image de haut en bas , la ligne supérieure de pixels de l’image apparaît en premier en mémoire. Dans une image de bas en haut, la dernière ligne de pixels apparaît en premier en mémoire. L’illustration suivante montre la différence entre une image de haut en bas et une image de bas en haut.

diagram showing top-down and bottom-up images.

Une image de bas en haut a un stride négatif, car le stride est défini comme le nombre d’octets nécessaires pour passer à une ligne de pixels suivante, par rapport à l’image affichée. Les images YUV doivent toujours être de haut en bas, et toute image contenue dans une surface Direct3D doit être de haut en bas. Les images RVB dans la mémoire système sont généralement de bas en haut.

Les transformations vidéo doivent en particulier gérer les mémoires tampons avec des strides incompatibles, car la mémoire tampon d’entrée peut ne pas correspondre à la mémoire tampon de sortie. Par exemple, supposons que vous souhaitez convertir une image source et écrire le résultat dans une image de destination. Supposons que les deux images ont la même largeur et la même hauteur, mais peuvent ne pas avoir le même format de pixel ou le même stride d’image.

L’exemple de code suivant montre une approche généralisée pour écrire ce type de fonction. Il ne s’agit pas d’un exemple qui fonctionne complètement, car il abstrait de nombreux détails spécifiques.

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

Cette fonction accepte six paramètres :

  • Un pointeur vers le début de la ligne d’analyse 0 dans l’image de destination.
  • Le stride de l’image de destination.
  • Un pointeur vers le début de la ligne d’analyse 0 dans l’image source.
  • Le stride de l’image source.
  • La largeur de l’image est en pixels.
  • La hauteur de l’image est en pixels.

L’idée générale consiste à traiter une ligne à la fois, en itérant sur chaque pixel de la ligne. Supposons que SOURCE_PIXEL_TYPE et DEST_PIXEL_TYPE sont des structures représentant la disposition de pixels pour les images source et de destination, respectivement. (Par exemple, RVB 32 bits utilise la structure RGBQUAD. Tous les formats de pixel n’ont pas de structure prédéfinie.) Le cast du pointeur de tableau vers le type de structure vous permet d’accéder aux composants RVB ou YUV de chaque pixel. Au début de chaque ligne, la fonction stocke un pointeur vers la ligne. À la fin de la ligne, il incrémente le pointeur de la largeur du stride de l’image, ce qui fait avancer le pointeur vers la ligne suivante.

Cet exemple appelle une fonction hypothétique nommée TransformPixelValue pour chaque pixel. Il peut s’agir de n’importe quelle fonction qui calcule un pixel cible à partir d’un pixel source. Bien sûr, les détails exacts dépendent de la tâche particulière. Par exemple, si vous avez un format YUV planaire, vous devez accéder aux plans chromatiques indépendamment du plan luma. Avec une vidéo entrelacée, vous devrez peut-être traiter les champs séparément. Ainsi de suite.

Pour donner un exemple plus concret, le code suivant convertit une image RVB 32 bits en image AYUV. Les pixels RVB sont accessibles à l’aide d’une structure RGBQUAD et les pixels AYUV sont accessibles à l’aide d’une structure 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;
    }
}

L’exemple suivant convertit une image RVB 32 bits en image YV12. Cet exemple montre comment gérer un format YUV planaire. (YV12 est un format planaire 4:2:0.) Dans cet exemple, la fonction conserve trois pointeurs distincts pour les trois plans de l’image cible. Toutefois, l’approche de base est la même que dans l’exemple précédent.

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

Dans tous ces exemples, il est supposé que l’application a déjà déterminé le stride de l’image. Vous pouvez parfois obtenir ces informations à partir de la mémoire tampon du support. Sinon, vous devez le calculer en fonction du format vidéo. Pour plus d’informations sur le calcul du stride de l’image et l’utilisation de mémoires tampons de support pour la vidéo, consultez Mémoires tampons vidéo non compressées.

Types de supports vidéo

Types de supports