JPEG YCbCr 支持

从Windows 8.1开始,Windows 映像组件 (WIC) JPEG 编解码器支持在其本机 YCbCr 窗体中读取和写入图像数据。 WIC YCbCr 支持可与 Direct2D 结合使用,以呈现具有图像效果的 YCbCr 像素数据。 此外,WIC JPEG 编解码器可以使用某些相机驱动程序通过 Media Foundation 生成的 YCbCr 像素数据。

与标准 BGRA 像素格式相比,YCbCr 像素数据消耗的内存要少得多。 此外,通过访问 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 数据。 有关详细信息,请参阅 JPEGITU-T81 规范

下面是分解为其 Y、Cb 和 Cr 组件的映像。

分解为其 y、cb 和 cr 组件的图像。

平面内存布局与交错内存布局

本部分介绍在内存中访问和存储 RGB 像素数据与 YCbCr 数据之间的一些差异。

RGB 像素数据通常使用交错内存布局进行存储。 这意味着单个颜色分量的数据在像素之间交错,并且每个像素都连续存储在内存中。

下图显示了存储在交错内存布局中的 RGBA 像素数据。

显示存储在交错内存布局中的 rgba 像素数据的图。

YCbCr 数据通常使用平面内存布局进行存储。 这意味着每个颜色分量单独存储在其自己的连续平面中,总共三个平面。 在另一种常见配置中,Cb 和 Cr 组件交错存储在一起,而 Y 组件仍保留在自己的平面中,总共有两个平面。

下图显示了平面 Y 和交错的 CbCr 像素数据,即常见的 YCbCr 内存布局。

显示平面 y 和交错 cbcr 像素数据的图,这是常见的 ycbcr 内存布局。

在 WIC 和 Direct2D 中,每个颜色平面都被视为其自己的不同对象 (IWICBitmapSourceID2D1Bitmap) ,这些平面共同构成了 YCbCr 图像的后备数据。

虽然 WIC 支持在 2 和 3 平面配置中访问YC bCr 数据,但 Direct2D 仅支持以前的 (Y 和 CbCr) 。 因此,在同时使用 WIC 和 Direct2D 时,应始终使用 2 平面 YCbCr 配置。

Chroma Subsampling

YCbCr 颜色模型非常适合数字成像方案,因为它可以利用人类视觉系统的某些方面。 特别是,人类对图像亮度 (亮度) 的变化更敏感,对色度 (颜色) 更不敏感。 通过将颜色数据拆分为单独的亮度和色度分量,我们可以选择性地仅压缩色度分量,以实现空间节省,同时尽量减少质量损失。

执行此操作的一种技术称为色度子采样。 Cb 和 Cr 平面在一个或两个水平和两个垂直维度中 (缩减) 进行子采样。 出于历史原因,通常使用三部分 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 存储未压缩的图像数据,则可以节省 25% 到 62.5% 的内存,而每像素 RGBA 数据 32 位,具体取决于使用的色度子采样模式。

YCbCr 的 JPEG 用法

在高级别上,JPEG 解压缩管道由以下阶段组成:

  1. 执行熵 (例如 Huffman) 解压缩
  2. 执行取消量化
  3. 执行反离散余弦变换
  4. 对 Cb Cr 数据执行色度向上采样
  5. 如果需要,请将 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 以及 IWICBitmapScalerIWICBitmapFlipRotatorIWICColorTransform

IWICPlanarBitmapFrameEncode

IWICPlanarBitmapFrameEncode 提供对平面像素数据(包括 YCbCr 数据)进行编码的功能。 可以通过在 JPEG 编解码器的 IWICBitmapFrameEncode 实现上调用 QueryInterface 来获取此接口。

IWICPlanarFormatConverter

IWICPlanarFormatConverter 允许 IWICFormatConverter 使用平面像素数据(包括 YCbCr),并将其转换为交错像素格式。 它不公开将交错像素数据转换为平面格式的功能。 可以通过在 Windows 提供的 IWICFormatConverter 实现上调用 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 数据访问,则此操作将失败。

请求的 WIC 转换是否支持 YCbCr

获取 IWICPlanarBitmapSourceTransform 后,应首先调用 DoesSupportTransform。 此方法采用要应用于平面 YCbCr 数据的完整转换集作为输入参数,并返回指示支持的布尔值,以及最接近可返回的请求大小的维度。 在使用 IWICPlanarBitmapSourceTransform::CopyPixels 访问像素数据之前,应检查所有三个值。

此模式类似于 IWICBitmapSourceTransform 的使用方式。

图形驱动程序是否支持将 YCbCr 与 Direct2D 配合使用所需的功能?

仅当使用 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 包含有关像素数据的信息,并指向将接收数据的内存缓冲区。

如果要将 YCbCr 像素数据与其他 WIC API 一起使用,则应创建适当配置的 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 管道:

以 jpeg 解码器开头的 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 对交错的数据进行编码相似,但并不完全相同。 平面接口仅公开写入平面帧图像数据的功能,应继续使用帧编码接口来设置元数据或缩略图,并在操作结束时提交。

对于典型情况,应执行以下步骤:

  1. 正常获取 IWICBitmapFrameEncode 。 如果要配置色度子采样,请在创建帧时设置 JpegYCrCbSubsampling 编码器选项。
  2. 如果需要设置元数据或缩略图,请正常使用 IWICBitmapFrameEncode 执行此操作。
  3. IWICPlanarBitmapFrameEncode 的 QueryInterface。
  4. 使用 IWICPlanarBitmapFrameEncode::WriteSourceIWICPlanarBitmapFrameEncode::WritePixels 设置 YC bC r 像素数据。它们的 IWICBitmapFrameEncode 对应项不同,为这些方法提供包含 YCbCr 像素平面的 IWICBitmapSourceWICBitmapPlane 数组。
  5. 完成后,调用 IWICBitmapFrameEncode::Commit

在 Windows 10 中解码 YCbCr 像素数据

从 Windows 10 版本 1507 开始,Direct2D 提供 ID2D1ImageSourceFromWic,这是一种在利用 YCbCr 优化的同时将 JPEG 解码为 Direct2D 的更简单方法。 ID2D1ImageSourceFromWic 会自动为你执行所有必要的 YCbCr 功能检查;它尽可能使用优化的代码路径,否则使用回退。 它还支持新的优化,例如一次仅缓存所需的映像的子区域。

有关使用 ID2D1ImageSourceFromWic 的详细信息,请参阅 Direct2D 照片调整 SDK 示例