Aspect d’image

Cette rubrique décrit deux concepts associés : l’aspect d’image et l’aspect en pixels. Elle décrit ensuite comment ces concepts sont exprimés dans Microsoft Media Foundation à l’aide de types de médias.

Aspect d’image

L’aspect d’image définit la forme de l’image vidéo affichée. L’aspect d’image est indiqué par X:Y, où X:Y est le rapport entre la largeur de l’image et la hauteur de l’image. La plupart des normes vidéo utilisent soit l’aspect 4:3, soit l’aspect 16:9. L’aspect d’image 16:9 est communément appelé grand écran. Le cinéma utilise souvent des aspects d’image de 1:85:1 ou 1:66:1. L’aspect d’image est également appelé facteur de forme (ou DAR, pour « Display Aspect Ratio »).

diagram showing 4:3 and 16:9 aspect ratios

Parfois, l’image vidéo n’a pas la même forme que la zone d’affichage. Par exemple, une vidéo 4:3 peut être affichée sur une télévision grand écran (16×9). Sur ordinateur, la vidéo peut être affichée à l’intérieur d’une fenêtre dont la taille est arbitraire. Dans ce cas, il existe trois façons d’adapter l’image à la zone d’affichage :

  • Étirer l’image le long d’un axe pour ajuster la zone d’affichage.
  • Mettre l’image à l’échelle pour qu’elle corresponde à la zone d’affichage, tout en conservant l’aspect d’image d’origine.
  • Rogner l’image.

Étirer l’image pour l’ajuster à la zone d’affichage donne presque toujours de mauvais résultats, car l’image ne conserve pas le bon aspect.

Letterboxing

Le processus de mise à l’échelle d’une image de type grand écran pour l’adapter à un affichage 4:3 est appelé letterboxing, comme illustré dans le diagramme suivant. Les zones rectangulaires créées en haut et en bas de l’image sont généralement remplies en noir, bien que d’autres couleurs puissent être utilisées.

diagram showing the correct way to letterbox

La procédure inverse, qui consiste à mettre à l’échelle une image 4:3 pour l’adapter à un grand écran, est parfois appelée pillarboxing. Toutefois, le terme letterbox est également utilisé dans un sens général pour désigner la mise à l’échelle d’une image vidéo pour l’adapter à une zone d’affichage donnée.

diagram showing pillarboxing

Pan-and-Scan

Le pan-and-scan est une technique dans laquelle une image de type grand écran est rognée sur une zone rectangulaire 4×3 pour l’afficher sur un périphérique d’affichage 4:3. L’image qui en résulte remplit l’affichage entier sans les bandes noires du letterboxing, mais des parties de l’image d’origine sont rognées. La zone rognée peut changer d’une trame à l’autre en fonction de la zone d’intérêt. Le terme « pan » dans pan-and-scan fait référence à l’effet panoramique provoqué par le déplacement de la zone de pan-and-scan.

diagram showing pan-and-scan

Aspect en pixels

L’aspect en pixels (PAR, pour « Pixel Aspect Ratio ») mesure la forme d’un pixel.

Lorsqu’une image numérique est capturée, elle est échantillonnée verticalement et horizontalement, ce qui crée un tableau rectangulaire d’échantillons quantifiés appelés pixels ou pels. La forme de la grille d’échantillonnage détermine la forme des pixels dans l’image numérisée.

Voici un exemple basé sur de petits nombres pour simplifier les choses. Supposons que l’image d’origine soit carrée (autrement dit, l’aspect de l’image est de 1:1) et que la grille d’échantillonnage contienne 12 éléments, organisés dans une grille 4×3. La forme de chaque pixel qui en résulte est plus haute que large. Plus précisément, la forme de chaque pixel est de 3×4. Les pixels qui ne sont pas carrés sont appelés pixels non carrés.

diagram showing a non-square sampling grid

L’aspect en pixels s’applique également au périphérique d’affichage. La forme physique du périphérique d’affichage et la résolution des pixels physiques (horizontale et verticale) déterminent l’aspect en pixels du périphérique d’affichage. Les moniteurs d’ordinateur utilisent généralement des pixels carrés. Si l’aspect en pixels de l’image et l’aspect en pixels du périphérique d’affichage ne correspondent pas, l’une des dimensions de l’image (verticale ou horizontale) doit être mise à l’échelle pour que l’image s’affiche correctement. La formule suivante met en relation le PAR, le facteur de forme (DAR) et la taille de l’image en pixels :

DAR = (largeur de l’image en pixels / hauteur de l’image en pixels) × PAR

Notez que la largeur de l’image et la hauteur de l’image dans cette formule font référence à l’image en mémoire, et non à l’image affichée.

Voici un exemple réel : la vidéo analogique NTSC-M contient 480 lignes de balayage dans la zone d’image active. ITU-R Rec. BT.601 spécifie un taux d’échantillonnage horizontal de 704 pixels visibles par ligne, ce qui donne une image numérique de 704 x 480 pixels. L’aspect d’image prévu est de 4:3, ce qui donne un aspect en pixels de 10:11.

  • DAR : 4:3
  • Largeur en pixels : 704
  • Hauteur en pixels : 480
  • PAR : 10/11

4/3 = (704/480) x (10/11)

Pour afficher correctement cette image sur un périphérique d’affichage avec des pixels carrés, vous devez mettre à l’échelle la largeur de 10/11 ou la hauteur de 11/10.

Utilisation des aspects

La forme correcte d’une trame vidéo est définie par l’aspect en pixels (PAR) et la zone d’affichage.

  • L’aspect en pixels (PAR) définit la forme des pixels dans une image. Les pixels carrés ont un aspect de 1:1. Tout autre aspect décrit un pixel non carré. Par exemple, la télévision NTSC utilise un aspect en pixels de 10:11. En supposant que vous affichiez la vidéo sur un moniteur d’ordinateur, l’affichage aurait des pixels carrés (PAR 1:1). Le PAR du contenu source est donné dans l’attribut MF_MT_PIXEL_ASPECT_RATIO du type de média.
  • La zone d’affichage est la région de l’image vidéo qui est destinée à être affichée. Il existe deux zones d’affichage pertinentes qui peuvent être spécifiées dans le type de média :
    • Ouverture pan-and-scan. L’ouverture pan-and-scan est une zone de 4×3 de la vidéo qui doit être affichée en mode pan-and-scan. Elle est utilisée pour afficher du contenu grand écran sur un périphérique d’affichage 4×3 sans letterboxing. L’ouverture pan-and-scan est donnée dans l’attribut MF_MT_PAN_SCAN_APERTURE et doit être utilisée uniquement lorsque l’attribut MF_MT_PAN_SCAN_ENABLED est défini sur la valeur TRUE.
    • Ouverture d’affichage. Cette ouverture est définie dans certaines normes vidéo. Tout ce qui se trouve en dehors de l’ouverture d’affichage est la région dite « overscan » et ne doit pas être affiché. Par exemple, la télévision NTSC est de 720×480 pixels avec une ouverture d’affichage de 704×480. L’ouverture d’affichage est donnée dans l’attribut MF_MT_MINIMUM_DISPLAY_APERTURE. Le cas échéant, elle doit être utilisée lorsque le mode pan-and-scan est défini sur FALSE.

Si le mode pan-and-scan est défini sur FALSE et qu’aucune ouverture d’affichage n’est définie, toute la trame vidéo sera affichée. En fait, c’est le cas pour la plupart du contenu vidéo autre que la télévision et la vidéo DVD. L’aspect de l’image entière est calculé comme suit : (largeur de la zone d’affichage / hauteur de la zone d’affichage) × PAR.

Exemples de code

Recherche de la zone d’affichage

Le code suivant montre comment obtenir la zone d’affichage à partir du type de média.

MFVideoArea MakeArea(float x, float y, DWORD width, DWORD height);

HRESULT GetVideoDisplayArea(IMFMediaType *pType, MFVideoArea *pArea)
{
    HRESULT hr = S_OK;
    BOOL bPanScan = FALSE;
    UINT32 width = 0, height = 0;

    bPanScan = MFGetAttributeUINT32(pType, MF_MT_PAN_SCAN_ENABLED, FALSE);

    // In pan-and-scan mode, try to get the pan-and-scan region.
    if (bPanScan)
    {
        hr = pType->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)pArea, 
            sizeof(MFVideoArea), NULL);
    }

    // If not in pan-and-scan mode, or the pan-and-scan region is not set, 
    // get the minimimum display aperture.

    if (!bPanScan || hr == MF_E_ATTRIBUTENOTFOUND)
    {
        hr = pType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)pArea, 
            sizeof(MFVideoArea), NULL);

        if (hr == MF_E_ATTRIBUTENOTFOUND)
        {
            // Minimum display aperture is not set.

            // For backward compatibility with some components, 
            // check for a geometric aperture. 

            hr = pType->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)pArea, 
                sizeof(MFVideoArea), NULL);
        }

        // Default: Use the entire video area.

        if (hr == MF_E_ATTRIBUTENOTFOUND)
        {
            hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);

            if (SUCCEEDED(hr))
            {
                *pArea = MakeArea(0.0, 0.0, width, height);
            }
        }
    }
    return hr;
}
MFOffset MakeOffset(float v)
{
    MFOffset offset;
    offset.value = short(v);
    offset.fract = WORD(65536 * (v-offset.value));
    return offset;
}
MFVideoArea MakeArea(float x, float y, DWORD width, DWORD height)
{
    MFVideoArea area;
    area.OffsetX = MakeOffset(x);
    area.OffsetY = MakeOffset(y);
    area.Area.cx = width;
    area.Area.cy = height;
    return area;
}

Conversion d’aspects en pixels

Le code suivant montre comment convertir un rectangle d’un aspect en pixels (PAR) à un autre, tout en conservant l’aspect de l’image.

//-----------------------------------------------------------------------------
// Converts a rectangle from one pixel aspect ratio (PAR) to another PAR.
// Returns the corrected rectangle.
//
// For example, a 720 x 486 rect with a PAR of 9:10, when converted to 1x1 PAR,
// must be stretched to 720 x 540.
//-----------------------------------------------------------------------------

RECT CorrectAspectRatio(const RECT& src, const MFRatio& srcPAR, const MFRatio& destPAR)
{
    // Start with a rectangle the same size as src, but offset to (0,0).
    RECT rc = {0, 0, src.right - src.left, src.bottom - src.top};

    // If the source and destination have the same PAR, there is nothing to do.
    // Otherwise, adjust the image size, in two steps:
    //  1. Transform from source PAR to 1:1
    //  2. Transform from 1:1 to destination PAR.

    if ((srcPAR.Numerator != destPAR.Numerator) || 
        (srcPAR.Denominator != destPAR.Denominator))
    {
        // Correct for the source's PAR.

        if (srcPAR.Numerator > srcPAR.Denominator)
        {
            // The source has "wide" pixels, so stretch the width.
            rc.right = MulDiv(rc.right, srcPAR.Numerator, srcPAR.Denominator);
        }
        else if (srcPAR.Numerator < srcPAR.Denominator)
        {
            // The source has "tall" pixels, so stretch the height.
            rc.bottom = MulDiv(rc.bottom, srcPAR.Denominator, srcPAR.Numerator);
        }
        // else: PAR is 1:1, which is a no-op.

        // Next, correct for the target's PAR. This is the inverse operation of 
        // the previous.

        if (destPAR.Numerator > destPAR.Denominator)
        {
            // The destination has "wide" pixels, so stretch the height.
            rc.bottom = MulDiv(rc.bottom, destPAR.Numerator, destPAR.Denominator);
        }
        else if (destPAR.Numerator < destPAR.Denominator)
        {
            // The destination has "tall" pixels, so stretch the width.
            rc.right = MulDiv(rc.right, destPAR.Denominator, destPAR.Numerator);
        }
        // else: PAR is 1:1, which is a no-op.
    }
    return rc;
}

Calcul de la zone de letterbox

Le code suivant calcule la zone de letterbox en fonction d’un rectangle source et de destination. Il est supposé que les deux rectangles ont le même PAR.

RECT LetterBoxRect(const RECT& rcSrc, const RECT& rcDst)
{
    // Compute source/destination ratios.
    int iSrcWidth  = rcSrc.right - rcSrc.left;
    int iSrcHeight = rcSrc.bottom - rcSrc.top;

    int iDstWidth  = rcDst.right - rcDst.left;
    int iDstHeight = rcDst.bottom - rcDst.top;

    int iDstLBWidth;
    int iDstLBHeight;

    if (MulDiv(iSrcWidth, iDstHeight, iSrcHeight) <= iDstWidth) 
    {
        // Column letterboxing ("pillar box")
        iDstLBWidth  = MulDiv(iDstHeight, iSrcWidth, iSrcHeight);
        iDstLBHeight = iDstHeight;
    }
    else 
    {
        // Row letterboxing.
        iDstLBWidth  = iDstWidth;
        iDstLBHeight = MulDiv(iDstWidth, iSrcHeight, iSrcWidth);
    }

    // Create a centered rectangle within the current destination rect

    LONG left = rcDst.left + ((iDstWidth - iDstLBWidth) / 2);
    LONG top = rcDst.top + ((iDstHeight - iDstLBHeight) / 2);

    RECT rc;
    SetRect(&rc, left, top, left + iDstLBWidth, top + iDstLBHeight);
    return rc;
}

Types de supports

Types de supports vidéo

MF_MT_MINIMUM_DISPLAY_APERTURE

MF_MT_PAN_SCAN_APERTURE

MF_MT_PAN_SCAN_ENABLED

MF_MT_PIXEL_ASPECT_RATIO