除了标准 RGB (sRGB) 外,Android 8.0(API 级别 26)引入了对其他颜色空间的颜色管理支持,用于在具有兼容显示屏的设备上呈现图形。借助此支持,您的应用可以通过 Java 或原生代码,使用从 PNG、JPEG 和 WebP 文件加载的嵌入式广色域配置文件渲染位图。使用 OpenGL 或 Vulkan 的应用可以直接输出广色域内容(使用 Display P3 和 scRGB)。此功能对于创建涉及高保真度颜色再现的应用(如图片和视频编辑应用)非常有用。
了解广色域模式
广色域配置文件是 ICC 配置文件(例如 Adobe RGB、 Pro Photo RGB 和 DCI-P3),能够表示比 sRGB 更丰富的颜色。支持广色域配置文件的屏幕可以显示具有较深的基本色(红色、绿色和蓝色)以及更丰富的辅色(如品红色、青色和黄色)的图片。
在搭载 Android 8.0(API 级别 26)或更高版本的 Android 设备上,您的应用可以为 activity 启用广色域颜色模式,以便系统识别并正确处理具有嵌入式广色域配置文件的位图图片。ColorSpace.Named
类枚举了 Android 支持的常用颜色空间的部分列表。
注意:启用广色域模式后,activity 的窗口会为屏幕合成使用更多内存和 GPU 处理。在启用广色域模式之前,您应仔细考虑该 activity 是否确实能够从中受益。例如,以全屏模式显示照片的 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 中以编程方式设置颜色模式。
呈现广色域内容
如需渲染广色域内容,您的应用必须加载广色域位图,该位图的颜色配置文件包含比 sRGB 更宽的颜色空间。常见的广色域配置文件包括 Adobe RGB、DCI-P3 和 Display P3。
应用可通过调用 getColorSpace()
查询位图的颜色空间。如需确定系统是否将特定颜色空间识别为广色域,您可以调用 isWideGamut()
方法。
Color
类允许您表示将四个成分打包到一个 64 位 Long 值中的颜色,而不是使用整数值的最常用表示形式。使用 long 值,您可以定义比整数值更精确的颜色。如果您需要将颜色创建或编码为 long 值,请使用 Color
类中的 pack()
方法之一。
您可以通过检查 getColorMode()
方法是否返回 COLOR_MODE_WIDE_COLOR_GAMUT
(但此方法不指示实际是否已授予广色域模式)来验证应用是否正确请求了广色域模式。
在原生代码中使用广色域支持
本部分介绍了如何使用 OpenGL 和 Vulkan 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
期间启用它,然后才能创建任何使用由该扩展定义的额外颜色空间的交换链图片。
在创建交换链之前,您需要选择所需的颜色空间,然后循环遍历可用的实体设备 surface,并为该颜色空间选择有效的颜色格式。
在 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; }