広色域コンテンツでグラフィックを強化する

Android 8.0(API レベル 26)では、互換性のあるディスプレイを備えたデバイスでグラフィックをレンダリングするための、標準 RGB(sRGB)以外の追加の色空間のカラー マネージメントのサポートが導入されました。このサポートにより、アプリは、Java またはネイティブ コードを使用して PNG、JPEG、WebP ファイルから読み込まれたワイドカラー プロファイルを埋め込んだビットマップをレンダリングできます。OpenGL または Vulkan を使用するアプリは、広色域コンテンツを直接出力できます(Display P3scRGB を使用)。この機能は、画像や動画の編集アプリなど、忠実度の高い色を再現するアプリの作成に役立ちます。

広色域モードを理解する

広色域プロファイルとは、sRGB よりも幅広い色を表現できる Adobe RGB Pro Photo RGBDCI-P3 などの ICC プロファイルです。広色域プロファイルをサポートする画面では、濃いプライマリ カラー(赤、緑、青)と豊かなセカンダリ カラー(マゼンタ、シアン、黄色など)の画像を表示できます。

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 ビットの long 値にまとめられた 4 つのコンポーネントで色を表現できます。長い値を使用すると、整数値よりも高い精度で色を定義できます。色を long 値として作成またはエンコードする必要がある場合は、Color クラスの pack() メソッドのいずれかを使用します。

アプリが広色域モードを適切にリクエストしているかどうかは、getColorMode() メソッドが COLOR_MODE_WIDE_COLOR_GAMUT を返すかどうかをチェックすることで確認できます(ただし、このメソッドは、広色域モードが実際に許可されたかどうかは示しません)。

ネイティブ コードで広色域サポートを使用する

このセクションでは、アプリでネイティブ コードを使用している場合に、OpenGL API と Vulkan API で広色域モードを有効にする方法について説明します。

OpenGL

OpenGL で広色域モードを使用するには、次のいずれかの拡張機能を持つ EGL 1.4 ライブラリをアプリに含める必要があります。

この機能を有効にするには、まず eglChooseConfig を使用して GL コンテキストを作成し、ワイドカラー用にサポートされている 3 つのカラーバッファ形式のいずれかを属性に指定します。広色域用のカラーバッファ形式は、次のいずれかの 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

次のコード スニペットは、デバイスがディスプレイ 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;
}