Cómo mejorar los gráficos con un amplio contenido de color

En Android 8.0 (API nivel 26), se agregó compatibilidad de administración con espacios de color adicionales, además de RGB estándar (sRGB), a fin de procesar gráficos en dispositivos con pantallas compatibles. Gracias a esta compatibilidad, tu app puede procesar mapas de bits que contengan perfiles con amplia gama de colores cargados desde archivos PNG, JPEG y WebP mediante Java o código nativo. Las apps que usan OpenGL o Vulkan pueden generar directamente contenido con una amplia gama de colores (con Display P3 y scRGB). Esta función es útil para crear apps que requieren una reproducción de colores de alta fidelidad, como las apps de edición de imágenes y videos.

Cómo funciona el modo de amplia gama de colores

Los perfiles de amplia gama de colores son perfiles ICC, como Adobe RGB, Pro Photo  RGB y DCI-P3, que pueden representar una gama de colores más amplia que sRGB. Las pantallas que admiten perfiles de amplia gama de colores pueden mostrar imágenes con colores primarios más profundos (rojo, verde y azul), así como colores secundarios más ricos (como magenta, cian y amarillo).

En los dispositivos Android que ejecutan Android 8.0 (API nivel 26) o versiones posteriores que lo admiten, tu app puede habilitar el modo de amplia gama de colores para una actividad mediante la cual el sistema reconoce y procesa correctamente imágenes de mapas de bits con perfiles de amplia gama de colores incorporados. La clase ColorSpace.Named enumera una lista parcial de espacios de color de uso común que admite Android.

Nota: Cuando se habilita el modo de amplia gama de colores, la ventana de la actividad usa más memoria y procesamiento de GPU para la composición de la pantalla. Antes de habilitar el modo de amplia gama de colores, debes considerar con cautela si la actividad realmente se beneficia de ello. Por ejemplo, una actividad que muestra fotos en pantalla completa es una buena candidata para el modo de amplia gama de colores, pero una actividad que muestra miniaturas pequeñas no lo es.

Cómo habilitar el modo de amplia gama de colores

Usa el atributo colorMode para solicitar que la actividad se muestre en modo de amplia gama de colores en dispositivos compatibles. En ese modo, una ventana puede procesarse fuera de la gama sRGB para mostrar colores más vibrantes. Si el dispositivo no admite el procesamiento de la amplia gama de colores, este atributo no tiene ningún efecto. Si tu app necesita determinar si una pantalla determinada admite una amplia gama de colores, llama al método isWideColorGamut(). Tu app también puede llamar a isScreenWideColorGamut(), que muestra true solo si la pantalla admite una amplia gama de colores y el dispositivo admite tal procesamiento.

Una pantalla puede admitir una amplia gama de colores, pero no contar con administración por color. En ese caso, el sistema no otorgará a la app el modo de amplia gama de colores. Cuando una pantalla no tiene administración por color, como era el caso de todas las versiones de Android anteriores a 8.0, el sistema reasigna los colores dibujados por la app a la gama de la pantalla.

Si deseas habilitar el modo de amplia gama de colores en tu actividad, configura el atributo colorMode como wideColorGamut en tu archivo AndroidManifest.xml. Debes hacerlo para cada actividad en la que desees habilitar el modo de amplia gama de colores.

    android:colorMode="wideColorGamut"
    

También puedes establecer el modo de color programáticamente en tu actividad. Para ello, debes llamar al método setColorMode(int) y pasar COLOR_MODE_WIDE_COLOR_GAMUT.

Cómo procesar una amplia gama de colores

Figura 1: Espacios de color Display P3 (naranja) en comparación con sRGB (blanco)

Para representar una amplia gama de colores, tu app debe cargar un mapa de bits de muchos colores, es decir, un mapa de bits con un perfil de colores que contenga un espacio de color más amplio que sRGB. Entre los perfiles más comunes de este tipo, se incluyen Adobe RGB DCI-P3 y Display P3.

Tu app puede consultar el espacio de color de un mapa de bits; para ello, debes llamar al método getColorSpace(). Si quieres determinar si el sistema reconoce un espacio de color específico como una amplia gama, puedes llamar al método isWideGamut().

La clase Color te permite representar un color con cuatro componentes empaquetados en un valor largo de 64 bits, en lugar de la representación más común, que utiliza un valor de número entero. Con valores largos, puedes definir colores con más precisión que con valores enteros. Si necesitas crear o codificar un color como valor largo, usa uno de los métodos pack() de la clase Color.

Con el fin de verificar si tu app solicitó de manera correcta el modo de amplia gama de colores, comprueba que el método getColorMode() muestre COLOR_MODE_WIDE_COLOR_GAMUT (sin embargo, este método no indica si el modo de amplia gama de colores se otorgó realmente).

Cómo usar la compatibilidad con una amplia gama de colores en el código nativo

En esta sección, se describe cómo habilitar el modo de amplia gama de colores con las API de OpenGL y Vulkan si tu app usa código nativo.

OpenGL

Para utilizar el modo de amplia gama de colores en OpenGL, tu app debe incluir la biblioteca EGL 1.4 con una de las siguientes extensiones:

Con el objetivo de habilitar la función, primero debes crear un contexto de GL mediante eglChooseConfig con uno de los tres formatos de búfer de color admitidos para la amplia gama de colores en los atributos. El formato de búfer de color de la amplia gama de colores debe ser uno de estos conjuntos de valores RGBA:

  • 8, 8, 8, 8
  • 10, 10, 10, 2
  • FP16, FP16, FP16, FP16

Luego, solicita la extensión de espacio de color P3 cuando crees tus objetivos de representación, como se muestra en el siguiente fragmento de código:

    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

La compatibilidad de Vulkan con la amplia gama de colores se proporciona mediante la extensión VK_EXT_swapchain_colorspace.

Antes de habilitar la compatibilidad con la amplia gama de colores en tu código Vulkan, comprueba que la extensión sea compatible mediante vkEnumerateInstanceExtensionProperties. Si la extensión está disponible, debes habilitarla durante vkCreateInstance, antes de crear una imagen de cadena de intercambio que use los espacios de color adicionales definidos por la extensión.

Antes de crear la cadena de intercambio, debes elegir el espacio de color deseado y, luego, recorrer las superficies disponibles del dispositivo físico y elegir un formato de color válido para ese espacio de color.

En dispositivos Android, Vulkan admite una amplia gama de colores con los siguientes espacios de color y formatos de color VkSurfaceFormatKHR:

  • Espacios de color de la amplia gama de colores de Vulkan:
    • VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT
    • VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT
  • Formatos de color Vulkan compatibles con una amplia gama de colores:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

En el siguiente fragmento de código, se muestra cómo puedes verificar que el dispositivo sea compatible con el espacio de color 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
    }
    

En el siguiente fragmento de código, se muestra cómo solicitar una cadena de intercambio Vulkan con VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT:

    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;
    }