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

Android 8.0 (nivel de API 26) presentó compatibilidad de administración con espacios de color adicionales además de RGB estándar (sRGB) para renderizar gráficos en dispositivos con pantallas compatibles. Con esta compatibilidad, tu app puede renderizar mapas de bits con perfiles con amplia gama de colores incorporados que se cargan 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 requieran reproducción de color de alta fidelidad, como apps de edición de imágenes y video.

Cómo funciona el modo de amplia gama de colores

Los perfiles con 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 colores variados pueden mostrar imágenes con colores primarios más profundos (rojos, verdes y azules) y con colores secundarios más intensos (como magenta, cian y amarillo).

En dispositivos Android que ejecutan Android 8.0 (nivel de API 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 cuidado si la actividad realmente se beneficia de él. 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 con el modo de amplia gama de colores en dispositivos compatibles. En este modo, una ventana puede renderizarse fuera de la gama sRGB para mostrar colores más vívidos. Si el dispositivo no es compatible con la representación en este modo, el atributo queda sin 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 renderización.

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

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

android:colorMode="wideColorGamut"

También puedes configurar el modo de color de manera programática en tu actividad. Para ello, llama al método setColorMode(int) y pasa 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 renderizar contenido de una amplia gama de colores, tu app debe cargar un mapa de bits de una amplia gama de colores, es decir, un mapa de bits con un perfil de color que contenga un espacio de color más amplio que sRGB. Los perfiles comunes de amplia gama de colores incluyen Adobe RGB, DCI-P3 y Display P3.

Tu app puede consultar el espacio de color de un mapa de bits llamando a 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() en la clase Color.

Puedes verificar si tu app solicitó correctamente el modo de amplia gama de colores. Para ello, 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 APIs de OpenGL y Vulkan si tu app usa código nativo.

OpenGL

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

Si quieres 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 para 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 renderizació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 de Vulkan, comprueba que la extensión sea compatible con vkEnumerateInstanceExtensionProperties. Si la extensión está disponible, debes habilitarla durante vkCreateInstance antes de crear cualquier 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;
}