使用宽色域内容增强图形

Android 8.0 (API 级别 26) 引入了对除了标准 RGB (sRGB) 之外的其他 颜色空间 的颜色管理支持,用于在具有兼容显示屏的设备上呈现图形。通过此支持,您的应用程序可以呈现包含从 PNG、JPEG 和 WebP 文件(通过 Java 或本机代码)加载的嵌入式宽色域配置文件的位图。使用 OpenGL 或 Vulkan 的应用程序可以直接输出宽色域内容(使用 Display P3scRGB)。此功能对于创建涉及高保真色彩再现的应用程序(例如图像和视频编辑应用程序)很有用。

了解宽色域模式

宽色域配置文件是 ICC 配置文件,例如 Adobe RGBPro Photo RGBDCI-P3,能够表示比 sRGB 更广泛的颜色范围。支持宽色域配置文件的屏幕可以显示具有更深的主色(红色、绿色和蓝色)以及更丰富的次色(如洋红色、青色和黄色)的图像。

在运行 Android 8.0 (API 级别 26) 或更高版本的支持它的 Android 设备上,您的应用程序可以为活动启用“宽色域颜色模式”,系统将识别并正确处理包含嵌入式宽色域配置文件的位图图像。 ColorSpace.Named 类枚举了 Android 支持的一些常用颜色空间的列表。

注意:启用宽色域模式后,活动的窗口将使用更多内存和 GPU 处理来进行屏幕合成。在启用宽色域模式之前,您应该仔细考虑该活动是否确实能从该模式中获益。例如,全屏显示照片的活动是使用宽色域模式的良好候选者,但显示小缩略图的活动则不是。

启用宽色域模式

使用 colorMode 属性来请求在兼容设备上以宽色域模式显示活动。在宽色域模式下,窗口可以在 sRGB 色域之外进行渲染,以显示更鲜艳的颜色。如果设备不支持宽色域渲染,则此属性将不起作用。如果您的应用程序需要确定给定显示屏是否支持宽色域,请调用 isWideColorGamut() 方法。您的应用程序还可以调用 isScreenWideColorGamut(),该方法仅在显示屏支持宽色域且设备支持宽色域颜色渲染时才返回 true

显示屏可能支持宽色域,但未进行颜色管理,在这种情况下,系统不会向应用程序授予宽色域模式。当显示屏未进行颜色管理时(就像 Android 8.0 之前的所有版本一样),系统会将应用程序绘制的颜色重新映射到显示屏的色域。

要在活动中启用宽色域,请在 AndroidManifest.xml 文件中将 colorMode 属性设置为 wideColorGamut。您需要为要启用宽色域模式的每个活动执行此操作。

android:colorMode="wideColorGamut"

您还可以通过在活动中调用 setColorMode(int) 方法并传入 COLOR_MODE_WIDE_COLOR_GAMUT 来以编程方式设置颜色模式。

渲染宽色域内容

图 1. Display P3 (橙色) 与 sRGB (白色) 颜色空间

要渲染宽色域内容,您的应用程序必须加载宽色域位图,即包含比 sRGB 更宽的颜色空间的色域配置文件的位图。常见的宽色域配置文件包括 Adobe RGB、DCI-P3 和 Display P3。

您的应用程序可以通过调用 getColorSpace() 来查询位图的颜色空间。要确定系统是否识别特定颜色空间为宽色域,您可以调用 isWideGamut() 方法。

Color 类允许您使用打包到 64 位长值中的四个分量来表示颜色,而不是最常见的用整数表示的颜色。使用长值,您可以定义比整数值更精确的颜色。如果您需要创建或编码颜色作为长值,请使用 Color 类中的一个 pack() 方法。

您可以通过检查 getColorMode() 方法是否返回 COLOR_MODE_WIDE_COLOR_GAMUT 来验证您的应用程序是否正确请求了宽色域模式(但是,此方法不能指示是否实际授予了宽色域模式)。

在本机代码中使用宽色域支持

本节介绍如果您的应用程序使用本机代码,如何在 OpenGLVulkan API 中启用宽色域模式。

OpenGL

为了在 OpenGL 中使用宽色域模式,您的应用程序必须包含以下扩展之一的 EGL 1.4 库

要启用此功能,您必须首先通过 eglChooseConfig 创建一个 GL 上下文,并在属性中使用三种支持的宽色域颜色缓冲区格式之一。宽色域的颜色缓冲区格式必须是以下 RGBA 值集之一

  • 8, 8, 8, 8
  • 10, 10, 10, 2
  • FP16、FP16、FP16、FP16

然后,在创建渲染目标时请求 P3 色彩空间扩展,如以下代码片段所示

std::vector<EGLint> attributes;
attributes.push_back(EGL_GL_COLORSPACE_KHR);
attributes.push_back(EGL_GL_COLORSPACE_DISPLAY_P3_EXT);
attributes.push_back(EGL_NONE);
engine->surface_ = eglCreateWindowSurface(
    engine->display_, config, engine->app->window, attributes.data());

Vulkan

Vulkan 对宽色域的支持是通过 VK_EXT_swapchain_colorspace 扩展提供的。

在您的 Vulkan 代码中启用宽色域支持之前,请先通过 vkEnumerateInstanceExtensionProperties 检查该扩展是否受支持。如果该扩展可用,您必须在 vkCreateInstance 期间启用它,然后才能创建任何使用扩展定义的额外颜色空间的交换链图像。

在创建交换链之前,您需要选择所需的色彩空间,然后遍历可用的物理设备表面,并为该色彩空间选择有效的颜色格式。

在 Android 设备上,Vulkan 支持以下色彩空间和 VkSurfaceFormatKHR 颜色格式的宽色域。

  • Vulkan 宽色域色彩空间:
    • VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT
    • VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT
  • 支持宽色域的 Vulkan 颜色格式:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

以下代码片段显示了如何检查设备是否支持 Display P3 色彩空间

uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(
       vkPhysicalDev,
       vkSurface,
       &formatCount,
       nullptr);
VkSurfaceFormatKHR *formats = new VkSurfaceFormatKHR[formatCount];
vkGetPhysicalDeviceSurfaceFormatsKHR(
       vkPhysicalDev,
       vkSurface,
       &formatCount,
       formats);

uint32_t displayP3Index = formatCount;
for (uint32_t idx = 0; idx < formatCount; idx++) {
 if (formats[idx].format == requiredSwapChainFmt &&
     formats[idx].colorSpace==VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT)
 {
   displayP3Index = idx;
   break;
 }
}
if (displayP3Index == formatCount) {
    // Display P3 is not supported on the platform
    // choose other format
}

以下代码片段显示了如何请求使用 VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT 的 Vulkan 交换链

uint32_t queueFamily = 0;
VkSwapchainCreateInfoKHR swapchainCreate {
   .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
   .pNext = nullptr,
   .surface = AndroidVkSurface_,
   .minImageCount = surfaceCapabilities.minImageCount,
   .imageFormat = requiredSwapChainFmt,
   .imageColorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT,
   .imageExtent = surfaceCapabilities.currentExtent,
   .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
   .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
   .imageArrayLayers = 1,
   .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
   .queueFamilyIndexCount = 1,
   .pQueueFamilyIndices = &queueFamily,
   .presentMode = VK_PRESENT_MODE_FIFO_KHR,
   .oldSwapchain = VK_NULL_HANDLE,
   .clipped = VK_FALSE,
};
VkRresult status = vkCreateSwapchainKHR(
                       vkDevice,
                       &swapchainCreate,
                       nullptr,
                       &vkSwapchain);
if (status != VK_SUCCESS) {
    // Display P3 is not supported
    return false;
}