画像縦横比
このトピックでは、画像縦横比とピクセル縦横比という 2 つの関連する概念について説明します。 次に、これらの概念が Microsoft メディア ファンデーションでメディアの種類を使ってどのように表現されるかについて説明します。
画像縦横比
画像縦横比では、表示されるビデオ画像の形状を定義します。 画像縦横比は X:Y で表されます。X:Y は画像の幅と高さの比です。 ほとんどのビデオ標準では、4:3 または 16:9 の画像縦横比が使われます。 16:9 の縦横比は一般にワイドスクリーンと呼ばれます。 多くの場合、映画フィルムでは 1:85:1 または 1:66:1 の縦横比が使われます。 画像縦横比はディスプレイ縦横比 (DAR) とも呼ばれます。
ビデオ画像はディスプレイ領域と同じ形状でない場合があります。 たとえば、4:3 のビデオはワイドスクリーン (16×9) テレビに表示されることがあります。 コンピューター ビデオでは、ビデオが任意のサイズのウィンドウ内に表示されることがあります。 この場合、画像をディスプレイ領域内に収めるようにするには、3 つの方法があります。
- ディスプレイ領域に合わせて 1 つの軸に沿って画像を引き伸ばす。
- 元の画像縦横比を維持したまま、ディスプレイ領域に合わせて画像を拡大縮小する。
- 画像をトリミングする。
ディスプレイ領域に合わせて画像を拡大する方法では画像の正しい縦横比が維持されないため、ほとんどの場合、不適切です。
レターボックス化
次の図に示すように、ワイドスクリーン画像を 4:3 ディスプレイに合わせて拡大縮小するプロセスはレターボックス化と呼ばれます。 結果の画像の上部と下部に表示される四角形の領域は、通常は黒で塗りつぶされますが、他の色を使うこともできます。
逆に、ワイドスクリーン ディスプレイに合わせて 4:3 画像を拡大縮小することは、ピラーボックス化とも呼ばれます。 ただし、レターボックスという用語は、特定のディスプレイ領域に合わせてビデオ画像を拡大縮小するという一般的な意味でも使われます。
パンアンドスキャン
パンアンドスキャンは、4:3 ディスプレイ デバイスに表示するために、ワイドスクリーン画像を 4×3 の四角形領域にトリミングする手法です。 結果の画像はディスプレイ全体に表示され、黒いレターボックス領域は必要ありませんが、その画像からは元の画像の一部がトリミングされます。 対象領域が変わった場合は、トリミングする領域をフレーム間で移動することができます。 パンアンドスキャンの "パン" という用語は、パンアンドスキャン領域を移動することで生じるパン効果を指します。
ピクセル縦横比
ピクセル縦横比 (PAR) では、ピクセルの形状を測定します。
デジタル画像をキャプチャすると、画像は垂直方向と水平方向の両方でサンプリングされ、その結果、ピクセルまたはペルという量子化されたサンプルの四角形の配列が生成されます。 サンプリング グリッドの形状によって、デジタル化された画像内のピクセルの形状が決まります。
ここでは、計算を簡単にするために小さな数値を使った例を示します。 たとえば、元の画像が正方形であるとします (つまり、画像縦横比が 1:1)。サンプリング グリッドには 4×3 グリッドに配置された 12 個の要素が含まれているとします。 結果として得られる各ピクセルの形状は、幅よりも高さの方が大きくなります。 具体的には、各ピクセルの形状は 3×4 になります。 正方形ではないピクセルは、非正方形ピクセルと呼ばれます。
ピクセル縦横比はディスプレイ デバイスにも適用されます。 ディスプレイ デバイスの物理的形状と物理的なピクセル解像度 (横方向と縦方向) によって、ディスプレイ デバイスの PAR が決まります。 通常、コンピューター モニターには正方形のピクセルが使われています。 画像の PAR とディスプレイの PAR が一致しない場合、正しく表示するには、画像を 1 つの次元 (垂直方向または水平方向) で拡大縮小する必要があります。 次の数式を使って、PAR、ディスプレイ縦横比 (DAR)、ピクセル単位の画像サイズを関連付けることができます。
DAR = (画像の幅 (ピクセル単位) / 画像の高さ (ピクセル単位)) × PAR
この数式の画像の幅と画像の高さは、表示されている画像ではなく、メモリ内の画像を指していることに注意してください。
実際の例を示します。NTSC-M アナログ ビデオには、アクティブな画像領域に 480 本の走査線があります。 ITU-R Rec。BT.601 は、1 ラインあたり 704 可視ピクセルの水平サンプリング レートと規定しており、704 x 480 ピクセルのデジタル画像が生成されます。 意図した画像縦横比は 4:3 であり、PAR は 10:11 になります。
- 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 属性で指定されます。
- ディスプレイ領域は、表示対象のビデオ画像の領域です。 メディアの種類で指定できる関連するディスプレイ領域は 2 つあります。
- パンアンドスキャンの絞り。 パンアンドスキャンの絞りは、パンアンドスキャン モードで表示する必要があるビデオの 4×3 領域です。 これは、ワイドスクリーン コンテンツをレターボックス化せずに 4×3 ディスプレイに表示するために使われます。 パンアンドスキャンの絞りは、MF_MT_PAN_SCAN_APERTURE 属性で指定します。また、MF_MT_PAN_SCAN_ENABLED 属性が TRUE の場合にのみ使うようにします。
- ディスプレイ アパーチャ。 このアパーチャは一部のビデオ標準で定義されています。 ディスプレイ アパーチャの外部にあるものはすべてオーバースキャン領域であり、表示すべきではありません。 たとえば、NTSC テレビは 720 × 480 ピクセルであり、ディスプレイ アパーチャは 704 × 480 です。 ディスプレイ アパーチャは、MF_MT_MINIMUM_DISPLAY_APERTURE 属性で指定します。 指定されている場合は、パンアンドスキャン モードが FALSE のときに使うようにします。
パンアンドスキャン モードが 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;
}
関連トピック