圖片外觀比例

本主題描述兩個相關概念:圖片外觀比例和像素外觀比例。 然後,它會描述這些概念如何使用媒體類型在 Microsoft Media Foundation 中表達。

圖片外觀比例

圖片外觀比例 會定義顯示視訊影像的形狀。 圖片外觀比例會註明 X:Y,其中 X:Y 是圖片寬度與圖片高度的比例。 大部分的視訊標準都使用 4:3 或 16:9 圖片外觀比例。 16:9 外觀比例通常稱為 寬螢幕。 電影電影通常使用 1:85:1 或 1:66:1 外觀比例。 圖片外觀比例也稱為 顯示外觀比例 (DAR)。

diagram showing 4:3 and 16:9 aspect ratios

有時候視訊影像的圖形與顯示區域不同。 例如,4:3 視頻可能會在寬螢幕(16×9)電視上顯示。 在計算機視訊中,視訊可能會顯示在具有任意大小的視窗中。 在此情況下,有三種方式可以製作影像以符合顯示區域:

  • 沿著一個軸延展影像以符合顯示區域。
  • 調整影像以符合顯示區域,同時維持原始圖片外觀比例。
  • 裁剪影像。

縮放影像以符合顯示區域幾乎總是錯誤的,因為它不會保留正確的圖片外觀比例。

寄信箱

調整寬螢幕影像以符合 4:3 顯示器的程式稱為 信箱,如下圖所示。 影像頂端和底部產生的矩形區域通常會填滿黑色,不過可以使用其他色彩。

diagram showing the correct way to letterbox

相反的情況是,縮放 4:3 影像以符合寬屏幕顯示器,有時稱為 柱形箱。 不過,一般意義上也會使用信箱一詞,表示調整視訊影像以符合任何指定的顯示區域。

diagram showing pillarboxing

Pan-and-Scan

Pan-and-scan 是一種技術,其中寬螢幕影像裁剪為 4×3 矩形區域,以顯示在 4:3 顯示裝置上。 產生的影像會填滿整個顯示器,而不需要黑色信箱區域,但原始影像的部分會從圖片中裁剪出來。 裁剪的區域可以從框架移至框架,因為感興趣的區域會改變。 移動瀏覽和掃描中的「移動流覽」一詞是指移動移動瀏覽區域所造成的行動瀏覽效果。

diagram showing pan-and-scan

像素外觀比例

像素外觀比例 (PAR) 測量圖元的形狀。

擷取數位影像時,影像會以垂直和水準方式取樣,產生矩形的量化樣本陣列,稱為 圖元拼字。 取樣方格的形狀會決定數位影像中像素的形狀。

以下是使用小數位來保持數學簡單的例子。 假設原始影像為正方形(也就是圖片外觀比例為 1:1):和 假設取樣方格包含12個元素,排列在4×3方格中。 每個產生的圖形會比寬度高。 具體來說,每個圖元的形狀會是 3×4。 不是正方形的像素稱為 非平方圖元

diagram showing a non-square sampling grid

像素外觀比例也適用於顯示裝置。 顯示裝置的實體形狀和實體圖元解析度(跨和向下)決定顯示裝置的 PAR。 計算機監視器通常會使用平方圖元。 如果影像 PAR 和顯示 PAR 不相符,則必須以垂直或水準方式調整影像,才能正確顯示。 下列公式與 PAR、顯示外觀比例 (DAR) 和以像素為單位的影像大小相關:

DAR = (以像素為單位的影像寬度,以像素 / 為單位)× PAR

請注意,此公式中的影像寬度和影像高度是指記憶體中的影像,而不是顯示的影像。

以下是真實世界的範例:NTSC-M 模擬影片包含使用中影像區域中的 480 條掃描線。 ITU-R Rec. BT.601 指定每行 704 個可見圖元的水平取樣率,產生 704 x 480 像素的數位影像。 預期的圖片外觀比例為 4:3,產生 10:11 的 PAR。

  • DAR:4:3
  • 圖元寬度:704
  • 以像素為單位的高度:480
  • PAR:10/11

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

若要在具有平方像素的顯示裝置上正確顯示此影像,您必須將寬度縮放為 10/11 或高度 11/10。

使用外觀比例

視訊畫面的正確形狀是由 像素外觀比例 (PAR) 和 顯示區域所定義。

  • PAR 會定義影像中像素的形狀。 方形像素的外觀比例為1:1。 任何其他外觀比例描述非平方圖元。 例如,NTSC 電視使用 10:11 PAR。 假設您正在電腦監視器上呈現視訊,顯示器會有平方圖元(1:1 PAR)。 來源內容的 PAR 是在媒體類型的 MF_MT_PIXEL_ASPECT_RATIO 屬性中提供。
  • 顯示區域是要顯示的視訊影像區域。 媒體類型中可能會指定兩個相關的顯示區域:
    • 平移掃描孔徑。 平移掃描光圈是 4×3 個視訊區域,應該以平移/掃描模式顯示。 它用來在 4×3 顯示器上顯示寬螢幕內容,而不顯示信箱。 pan-and-scan 光圈是在 MF_MT_PAN_SCAN_APERTURE 屬性中提供,而且只有在MF_MT_PAN_SCAN_ENABLED屬性為 TRUE 時才應該使用。
    • 顯示光圈。 此光圈定義於某些視訊標準中。 顯示孔徑以外的任何專案都是過度掃描區域,不應顯示。 例如,NTSC 電視是 720×480 像素,顯示光圈為 704×480。 顯示孔徑會在 MF_MT_MINIMUM_DISPLAY_APERTURE 屬性中提供。 如果存在,則當 pan-and-scan 模式為 FALSE 時,應該使用它。

如果 Pan-can 模式為 FALSE ,且未定義任何顯示孔徑,則應該顯示整個視訊畫面。 事實上,這是電視與 DVD 視頻以外的大多數視訊內容的情況。 整個圖片的外觀比例會計算為 (顯示區域寬度 / 顯示區域高度) × PAR。

程式碼範例

尋找顯示區域

下列程式代碼示範如何從媒體類型取得顯示區域。

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

在像素外觀比例之間轉換

下列程式代碼示範如何將矩形從一個像素外觀比例(PAR)轉換成另一個像素外觀比例,同時保留圖片外觀比例。

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

計算寄件箱區域

下列程式代碼會計算指定來源和目的地矩形的信箱區域。 假設這兩個矩形具有相同的 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;
}

媒體類型

視訊媒體類型

MF_MT_MINIMUM_DISPLAY_APERTURE

MF_MT_PAN_SCAN_APERTURE

MF_MT_PAN_SCAN_ENABLED

MF_MT_PIXEL_ASPECT_RATIO