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

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

広色域モードを理解する

広色域プロファイルは、Adobe RGBPro Photo RGBDCI-P3 などの ICC プロファイルで、表現できる色が 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 クラスを使用すると、整数値で色を表現する一般的な方法ではなく、4 つのコンポーネントで構成される 64 ビット長の値で色を表現できます。Long 形式の値により、整数値よりも高い精度で色を定義することができます。色を Long 値として作成またはエンコードする必要がある場合は、Color クラスで pack() メソッドのいずれかを使用します。

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

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

ここでは、アプリでネイティブ コードが使用されている場合に、OpenGL API および Vulkan API で広色域モードを有効にする方法について説明します。

OpenGL

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

この機能を有効にするには、まず eglChooseConfig を介して、サポートされている 3 つの広色域用カラーバッファ形式のいずれかを属性で使用して、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;
    }