使用广色域内容增强图形效果

除标准 RGB (sRGB) 外,Android 8.0(API 级别 26)还引入了对额外颜色空间的颜色管理支持,用于在具有兼容显示屏的设备上呈现图形。借助这种支持,您的应用可以通过 Java 或原生代码,使用从 PNG、JPEG 和 WebP 文件加载的嵌入式广色域配置文件呈现位图。使用 OpenGL 或 Vulkan 的应用可以直接输出广色域内容(使用 Display P3scRGB)。此功能适用于创建涉及高保真度颜色再现的应用,例如图片和视频编辑应用。

了解广色域模式

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

在运行 Android 8.0(API 级别 26)或更高级别的 Android 设备上,您的应用可为 Activity 启用广色域颜色模式,以便系统识别并正确处理具有嵌入式广色域配置文件的位图图片。ColorSpace.Named 类枚举了 Android 支持的常用颜色空间的部分列表。

注意:启用广色域模式时,Activity 的窗口将更多内存和 GPU 处理能力用于画面构成。在启用广色域模式之前,您应仔细考虑该 Activty 是否真的能从中受益。例如,以全屏模式显示照片的 Activity 适合使用广色域模式,但显示较小缩略图的 Activity 则不适合。

启用广色域模式

使用 colorMode 属性请求在兼容设备上以广色域模式显示 Activity。在广色域模式下,窗口可以在 sRGB 色域之外呈现,显示更鲜明的色彩。如果设备不支持广色域呈现,则此属性无效。如果您的应用需要确定某个特定显示屏是否支持广色域,请调用 isWideColorGamut() 方法。您的应用还可以调用 isScreenWideColorGamut(),该方法仅当显示屏支持广色域且设备支持广色域颜色呈现时才会返回 true

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

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

    android:colorMode="wideColorGamut"
    

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

呈现广色域内容

图 1. Display P3(橙色)与 sRGB(白色)颜色空间的对比情况

如需呈现广色域内容,您的应用必须加载广色域位图,该位图的颜色配置文件包含比 sRGB 更广的颜色空间。常见广色域配置文件包括 Adobe RGB、DCI-P3 和 Display P3。

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

Color 类可用于表示将四种颜色成分打包成一个 64 位 long 值的颜色,而不是使用整数值的最常用表示法。使用 long 值,您可以定义比整数值更精确的颜色。如果您需要将颜色创建或编码为 long 值,请使用 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;
    }