利用廣色內容增強圖像效果

除了標準 RGB (sRGB),Android 8.0 (API 級別 26) 還支援其他色域,支援在相容螢幕裝置上算繪圖像。透過這項支援,您的應用程式可以使用從 PNG、JPEG 和 WebP 檔案載入的 PNG、JPEG 和 WebP 檔案,透過 Java 或原生程式碼載入點陣圖。使用 OpenGL 或 Vulkan 的應用程式可以直接輸出廣色域內容 (使用 Display P3scRGB)。這項功能有助於建立涉及高擬真色彩複製的應用程式,例如圖片和影片編輯應用程式。

瞭解廣色域模式

廣色設定檔是 ICC 設定檔,例如 Adobe RGB Pro 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. 顯示 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;
}