JPEG YCbCr 支援
從Windows 8.1開始,Windows映射元件 (WIC) JPEG 編解碼器支援以原生YCbCr形式讀取和寫入影像資料。 WIC YCbCr支援可以與Direct2D搭配使用,以影像效果轉譯YCbCr圖元資料。 此外,WIC JPEG 編解碼器可以透過媒體基礎取用特定相機驅動程式所產生的YCbCr圖元資料。
YCbCr圖元資料耗用的記憶體明顯小於標準 BGRA 像素格式。 此外,存取YCbCr資料可讓您將 JPEG 解碼/編碼管線的某些階段卸載至 GPU 加速的 Direct2D。 藉由使用YCbCr,您的應用程式可以降低相同大小和品質影像的 JPEG 記憶體耗用量和負載時間。 或者,您的應用程式可以使用更高解析度的 JPEG 影像,而不會產生效能負面影響。
本主題描述YCbCr資料的運作方式,以及如何在 WIC 和 Direct2D 中使用。
關於 JPEG YCbCr資料
本節說明瞭解 WIC 中的YCbCr支援方式及其重要優點所需的一些重要概念。
YCbCr色彩模型
Windows 8和更早版本中的 WIC 支援四種不同的色彩模型,最常見的是 RGB/BGR。 此色彩模型會使用紅色、綠色和藍色元件來定義色彩資料;也可以使用第四個 Alpha 元件。
以下是分解成其紅色、綠色和藍色元件的影像。

YCbCr是一種替代色彩模型,使用亮度元件定義色彩資料, (Y) 和兩個色階元件 (Cb和Cr) 。 它通常用於數位影像處理和視訊案例。 YCbCr一詞通常會與 YUV 交換使用,但兩者在技術上是相異的。
YCbCr有數種變化,其差異在於色彩空間和動態範圍定義 – WIC 特別支援 JPEG JFIF YCbCr資料。 如需詳細資訊,請參閱 JPEGWT-T81 規格。
以下是分解成其 Y、Cb和Cr 元件的映射。

平面記憶體配置與交錯記憶體配置
本節說明在記憶體中存取和儲存 RGB 圖元資料與YCbCr資料之間的一些差異。
RGB 圖元資料通常會使用交錯記憶體配置來儲存。 這表示單一色彩元件的資料會在圖元之間交錯,而且每個圖元會連續儲存在記憶體中。
下圖顯示儲存在交錯記憶體配置中的 RGBA 圖元資料。

YCbCr資料通常會使用平面記憶體配置來儲存。 這表示每個色彩元件會分別儲存在自己的連續平面中,總共有三個平面。 在另一個常見的組態中,Cb 和Cr 元件會交錯並儲存在一起,而 Y 元件會保留在自己的平面中,總共有兩個平面。
下圖顯示平面 Y 和交錯CbCr圖元資料,這是常見的YCbCr記憶體配置。

在 WIC 和 Direct2D 中,每個色彩平面都會視為自己的不同物件, (IWICBitmapSource或ID2D1Bitmap) ,而且這些平面共同構成 YCbCr 影像的支援資料。
雖然 WIC 支援在 2 和 3 平面設定中存取YCbCr資料,但 Direct2D 僅支援先前的 (Y 和CbCr) 。 因此,使用 WIC 和 Direct2D 時,您應該一律使用 2 個平面YCbCr組態。
Chroma 子取樣
YCbCr色彩模型非常適合數位影像處理案例,因為它可以利用人類視覺系統的某些層面。 特別是,人類對影像亮度 (亮度) 的變更更敏感,而且對色彩 (色彩) 較不敏感。 藉由將色彩資料分割成個別的亮度和色度元件,我們可以選擇性地只壓縮 chrominance 元件,以節省空間,同時降低品質損失。
執行此動作的其中一種技術稱為 chroma 子取樣。 Cb 和Cr 平面會在一個或兩個水準和垂直維度中,子取樣 (縮小) 。 基於歷史原因,每個 chroma 子取樣模式通常會使用三個部分 J:a:b 比例來參考。
| 子取樣模式 | 水準向下調整 | 垂直向下調整 | 每個圖元的位* |
|---|---|---|---|
| 4:4:4 | 1 倍 | 1 倍 | 24 |
| 4:2:2 | 2x | 1 倍 | 16 |
| 4:4:0 | 1 倍 | 2x | 16 |
| 4:2:0 | 2x | 2x | 12 |
* 包含 Y 資料。
從上表中,如果您使用YCbCr來儲存未壓縮的影像資料,您可以根據使用的 chroma 子取樣模式,達到每圖元 RGBA 資料的 25% 到 62.5% 與 32 位的記憶體節省。
YCbCr的 JPEG 使用量
概括而言,JPEG 解壓縮管線包含下列階段:
- 執行 entropy (,例如 Huffman) 解壓縮
- 執行取消量化
- 執行反向余弦轉換
- 對CbCr資料執行 chroma upsampling
- 視) 需要將YCbCr資料轉換成 RGBA (
藉由讓 JPEG 編解碼器產生YCbCr資料,我們可以避免解碼程式的最後兩個步驟,或將其延遲至 GPU。 除了上一節所列的記憶體節省,這可大幅減少解碼影像所需的整體時間。 編碼YCbCr資料時,會套用相同的節省成本。
使用 JPEG YCbCr資料
本節說明如何使用 WIC 和 Direct2D 操作YCbCr資料。
若要查看本檔在實務中使用的指引,請參閱Direct2D 和 WIC 範例中的 JPEG YCbCr 優化,其中示範在 Direct2D 應用程式中解碼和轉譯 YCbCr 內容所需的所有步驟。
使用YCbCr JPEG 映射
大部分的 JPEG 映射會儲存為YCbCr。 某些 JPEG 包含 CMYK 或灰階資料,且不使用YCbCr。 這表示您通常不一定可以直接使用預先存在的 JPEG 內容,而不需進行任何修改。
WIC 和 Direct2D 不支援每個可能的YCbCr組態,而 Direct2D 中的YCbCr支援則取決於基礎圖形硬體和驅動程式。 因此,一般用途的映射管線必須強固到不使用YCbCr的映射 (包括其他常見的影像格式,例如 PNG 或 BMP) ,或無法使用YCbCr支援的情況。 建議您保留現有的 BGRA 型映射管線,並在可用時啟用YCbCr作為效能優化。
Windows映射元件 API
Windows 8.1中的 WIC 新增三個新的介面,以提供 JPEG YCbCr資料的存取權。
IWICPlanarBitmapSourceTransform
IWICPlanarBitmapSourceTransform類似于IWICBitmapSourceTransform,不同之處在于它會在平面設定中產生圖元,包括YCbCr資料。 您可以在支援平面存取的 IWICBitmapSource 實作上呼叫 QueryInterface 來取得此介面。 這包括 JPEG 編解碼器的IWICBitmapFrameDecode實作,以及IWICBitmapScaler、IWICBitmapFlipRotator和IWICColorTransform。
IWICPlanarBitmapFrameEncode
IWICPlanarBitmapFrameEncode可讓您編碼平面圖元資料,包括YCbCr資料。 您可以在 JPEG 編解碼器的 IWICBitmapFrameEncode實作上呼叫 QueryInterface 來取得此介面。
IWICPlanarFormatConverter
IWICPlanarFormatConverter可讓IWICFormatConverter取用平面圖元資料,包括YCbCr,並將其轉換成交錯像素格式。 它不會公開將交錯圖元資料轉換成平面格式的能力。 您可以在IWICFormatConverter的Windows提供實作上呼叫 QueryInterface,以取得此介面。
Direct2D API
Windows 8.1中的 Direct2D 支援具有新 YCbCr 影像效果的YCbCr平面圖元資料。 此效果可讓您轉譯YCbCr資料。 效果會採用輸入兩個 ID2D1Bitmap 介面:一個包含DXGI_FORMAT_R8_UNORM格式的平面 Y 資料,另一個包含DXGI_FORMAT_R8G8_UNORM格式的交錯 CbCr 資料。 您通常會使用此效果來取代包含 BGRA 圖元資料的 ID2D1Bitmap 。
YCbCr影像效果旨在與提供 YCbCr 資料的 WIC YCbCr API 搭配使用。 這可有效地將部分解碼工作從 CPU 卸載至 GPU,以便更快速且平行處理。
判斷是否支援YCbCr組態
如先前所述,您的應用程式應該對無法使用YCbCr支援的情況強固。 本節將討論您的應用程式應該檢查的條件。 如果下列任一檢查失敗,您的應用程式應該回復為標準 BGRA 型管線。
WIC 元件是否支援YCbCr資料存取?
只有Windows提供的 JPEG 編解碼器和特定 WIC 轉換支援YCbCr資料存取。 如需完整清單,請參閱Windows映射元件 API一節。
若要取得其中一個平面YCbCr介面,請在原始介面上呼叫 QueryInterface。 如果元件不支援YCbCr資料存取,這會失敗。
YCbCr是否支援要求的 WIC 轉換?
取得 IWICPlanarBitmapSourceTransform之後,您應該先呼叫 DoesSupportTransform。 這個方法會接受輸入參數一組您想要套用至平面YCbCr資料的完整轉換,並傳回布林值,指出支援,以及最接近可傳回的要求大小維度。 您應該先檢查這三個值,再使用 IWICPlanarBitmapSourceTransform::CopyPixels存取圖元資料。
此模式類似于 IWICBitmapSourceTransform 的使用方式。
圖形驅動程式是否支援搭配 Direct2D 使用YCbCr所需的功能?
只有在您使用 Direct2D YCbCr效果來轉譯YCbCr內容時,才需要這項檢查。 Direct2D 會使用DXGI_FORMAT_R8_UNORM和DXGI_FORMAT_R8G8_UNORM像素格式來儲存YCbCr資料,這些格式無法從所有圖形驅動程式取得。
使用YCbCr影像效果之前,您應該呼叫ID2D1DeviceCoNtext::IsDxgiFormatSupported,以確保驅動程式支援這兩種格式。
範例程式碼
以下是示範建議檢查的程式碼範例。 此範例取自 Direct2D 和 WIC 範例中的 JPEG YCbCr 優化。
bool DirectXSampleRenderer::DoesWicSupportRequestedYCbCr()
{
ComPtr<IWICPlanarBitmapSourceTransform> wicPlanarSource;
HRESULT hr = m_wicScaler.As(&wicPlanarSource);
if (SUCCEEDED(hr))
{
BOOL isTransformSupported;
uint32 supportedWidth = m_cachedBitmapPixelWidth;
uint32 supportedHeight = m_cachedBitmapPixelHeight;
DX::ThrowIfFailed(
wicPlanarSource->DoesSupportTransform(
&supportedWidth,
&supportedHeight,
WICBitmapTransformRotate0,
WICPlanarOptionsDefault,
SampleConstants::WicYCbCrFormats,
m_planeDescriptions,
SampleConstants::NumPlanes,
&isTransformSupported
)
);
// The returned width and height may be larger if IWICPlanarBitmapSourceTransform does not
// exactly support what is requested.
if ((isTransformSupported == TRUE) &&
(supportedWidth == m_cachedBitmapPixelWidth) &&
(supportedHeight == m_cachedBitmapPixelHeight))
{
return true;
}
}
return false;
}
bool DirectXSampleRenderer::DoesDriverSupportYCbCr()
{
auto d2dContext = m_deviceResources->GetD2DDeviceContext();
return (d2dContext->IsDxgiFormatSupported(DXGI_FORMAT_R8_UNORM)) &&
(d2dContext->IsDxgiFormatSupported(DXGI_FORMAT_R8G8_UNORM));
}
解碼YCbCr圖元資料
如果您想要取得YCbCr圖元資料,您應該呼叫IWICPlanarBitmapSourceTransform::CopyPixels。 這個方法會將圖元資料複製到填滿WICBitmapPlane結構的陣列,其中一個用於您想要 (的每個平面,例如 Y 和CbCr) 。 WICBitmapPlane包含圖元資料的相關資訊,並指向將接收資料的記憶體緩衝區。
如果您想要搭配其他 WIC API 使用YCbCr圖元資料,您應該建立適當設定的IWICBitmap、呼叫Lock以取得基礎記憶體緩衝區,並將緩衝區與用來接收YCbCr圖元資料的WICBitmapPlane產生關聯。 然後,您可以正常使用 IWICBitmap 。
最後,如果您想要在 Direct2D 中轉譯YCbCr資料,您應該從每個IWICBitmap 建立 ID2D1Bitmap,並將其作為YCbCr影像效果的來源。 WIC 可讓您要求多個平面設定。 與 Direct2D 互通時,您應該要求兩個平面,一個使用 GUID_WICPixelFormat8bppY,另一個則使用 GUID_WICPixelFormat16bppCbCr,因為這是 Direct2D 預期的設定。
程式碼範例
以下是示範在 Direct2D 中解碼和轉譯YCbCr資料的步驟的程式碼範例。 此範例取自 Direct2D 和 WIC 範例中的 JPEG YCbCr 優化。
void DirectXSampleRenderer::CreateYCbCrDeviceResources()
{
auto wicFactory = m_deviceResources->GetWicImagingFactory();
auto d2dContext = m_deviceResources->GetD2DDeviceContext();
ComPtr<IWICPlanarBitmapSourceTransform> wicPlanarSource;
DX::ThrowIfFailed(
m_wicScaler.As(&wicPlanarSource)
);
ComPtr<IWICBitmap> bitmaps[SampleConstants::NumPlanes];
ComPtr<IWICBitmapLock> locks[SampleConstants::NumPlanes];
WICBitmapPlane planes[SampleConstants::NumPlanes];
for (uint32 i = 0; i < SampleConstants::NumPlanes; i++)
{
DX::ThrowIfFailed(
wicFactory->CreateBitmap(
m_planeDescriptions[i].Width,
m_planeDescriptions[i].Height,
m_planeDescriptions[i].Format,
WICBitmapCacheOnLoad,
&bitmaps[i]
)
);
LockBitmap(bitmaps[i].Get(), WICBitmapLockWrite, nullptr, &locks[i], &planes[i]);
}
DX::ThrowIfFailed(
wicPlanarSource->CopyPixels(
nullptr, // Copy the entire source region.
m_cachedBitmapPixelWidth,
m_cachedBitmapPixelHeight,
WICBitmapTransformRotate0,
WICPlanarOptionsDefault,
planes,
SampleConstants::NumPlanes
)
);
DX::ThrowIfFailed(d2dContext->CreateEffect(CLSID_D2D1YCbCr, &m_d2dYCbCrEffect));
ComPtr<ID2D1Bitmap1> d2dBitmaps[SampleConstants::NumPlanes];
for (uint32 i = 0; i < SampleConstants::NumPlanes; i++)
{
// IWICBitmapLock must be released before using the IWICBitmap.
locks[i] = nullptr;
// First ID2D1Bitmap1 is DXGI_FORMAT_R8 (Y), second is DXGI_FORMAT_R8G8 (CbCr).
DX::ThrowIfFailed(d2dContext->CreateBitmapFromWicBitmap(bitmaps[i].Get(), &d2dBitmaps[i]));
m_d2dYCbCrEffect->SetInput(i, d2dBitmaps[i].Get());
}
}
void DirectXSampleRenderer::LockBitmap(
_In_ IWICBitmap *pBitmap,
DWORD bitmapLockFlags,
_In_opt_ const WICRect *prcSource,
_Outptr_ IWICBitmapLock **ppBitmapLock,
_Out_ WICBitmapPlane *pPlane
)
{
// ComPtr guarantees the IWICBitmapLock is released if an exception is thrown.
ComPtr<IWICBitmapLock> lock;
DX::ThrowIfFailed(pBitmap->Lock(prcSource, bitmapLockFlags, &lock));
DX::ThrowIfFailed(lock->GetStride(&pPlane->cbStride));
DX::ThrowIfFailed(lock->GetDataPointer(&pPlane->cbBufferSize, &pPlane->pbBuffer));
DX::ThrowIfFailed(lock->GetPixelFormat(&pPlane->Format));
*ppBitmapLock = lock.Detach();
}
轉換YCbCr圖元資料
轉換YCbCr資料與解碼幾乎完全相同,因為兩者都牽涉到 IWICPlanarBitmapSourceTransform。 唯一的差異在於您從中取得介面的 WIC 物件。 Windows提供的縮放器、翻轉旋轉器和色彩轉換全都支援YCbCr存取。
將轉換鏈結在一起
WIC 支援將多個轉換鏈結在一起的概念。 例如,您可以建立下列 WIC 管線:

然後,您可以在 IWICColorTransform 上呼叫 QueryInterface,以取得 IWICPlanarBitmapSourceTransform。 色彩轉換可以與上述轉換通訊,而且可以公開管線中每個階段的匯總功能。 WIC 可確保YCbCr資料會透過整個程式保留。 此鏈結僅適用于使用支援YCbCr存取的元件時。
JPEG 編解碼器優化
類似于 IWICBitmapSourceTransform的 JPEG 框架解碼實作, IWICPlanarBitmapSourceTransform 的 JPEG 框架解碼實作支援原生 JPEG DCT 網域縮放和旋轉。 您可以直接從 JPEG 解碼器要求兩個縮小或旋轉的電源。 這通常會產生比使用離散轉換更高的品質和效能。
此外,當您在 JPEG 解碼器之後鏈結一或多個 WIC 轉換時,可以利用原生 JPEG 縮放和旋轉來滿足匯總要求的作業。
格式轉換
使用IWICPlanarFormatConverter將平面YCbCr圖元資料轉換成交錯的像素格式,例如 GUID_WICPixelFormat32bppPBGRA。 Windows 8.1中的 WIC 無法轉換為平面YCbCr像素格式。
編碼YCbCr圖元資料
使用IWICPlanarBitmapFrameEncode將YCbCr圖元資料編碼為 JPEG 編碼器。 編碼YCbCr資料IWICPlanarBitmapFrameEncode類似,但與使用IWICBitmapFrameEncode編碼交錯的資料不同。 平面介面只會公開撰寫平面框架影像資料的能力,而且您應該繼續使用框架編碼介面來設定中繼資料或縮圖,並在作業結束時認可。
針對一般案例,您應該遵循下列步驟:
- 如常取得 IWICBitmapFrameEncode 。 如果您想要設定 chroma 子取樣,請在建立畫面時設定 JpegYCrCbSubsampling 編碼器選項。
- 如果您需要設定中繼資料或縮圖,請使用 IWICBitmapFrameEncode 做為一般。
- IWICPlanarBitmapFrameEncode的 QueryInterface。
- 使用IWICPlanarBitmapFrameEncode::WriteSource或IWICPlanarBitmapFrameEncode::WritePixels設定 YCbCr 圖元資料。 不同于其IWICBitmapFrameEncode對應專案,您可以使用IWICBitmapSource陣列或包含YCbCr圖元平面的 WICBitmapPlane來提供這些方法。
- 完成時,請呼叫 IWICBitmapFrameEncode::Commit。
在 Windows 10 中解碼YCbCr圖元資料
從Windows 10組建 1507 開始,Direct2D 提供ID2D1ImageSourceFromWic,這是將 JPEG 解碼為 Direct2D 的更簡單方式,同時運用YCbCr優化。 ID2D1ImageSourceFromWic 會自動為您執行所有必要的YCbCr功能檢查; 它會盡可能使用優化的程式碼路徑,否則會使用後援。 它也會啟用新的優化,例如只快取一次所需的影像子集。
如需使用 ID2D1ImageSourceFromWic的詳細資訊,請參閱 Direct2D 相片調整 SDK 範例。