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

Android 8.0 (nivel de API 26) agregó compatibilidad de administración de color para espacios de color adicionales además de RGB estándar (sRGB) para renderizar 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 aplicaciones que implican la reproducción de color de alta fidelidad, como imágenes y videos de edición de fotos.

Cómo funciona el modo de amplia gama de colores

Los perfiles de colores amplios son perfiles ICC, como Adobe RGB, Pro Photo RGB y DCI-P3, que pueden representar un rango más amplio de colores que sRGB. Las pantallas que admiten perfiles de colores amplios pueden mostrar imágenes con colores primarios más intensos (rojos, verdes y azules), así como colores secundarios más ricos (como magentas, cyan y amarillos).

En los 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 una 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 se muestre la actividad. en el 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 la orientación ampliada, haz lo siguiente: gamut, este atributo no tiene efecto. Si tu app necesita determinar si un determinado pantalla admite una amplia gama de colores, llama al 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.

Para habilitar la amplia gama de colores en tu actividad, establece el colorMode a 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 de manera programática en tu actividad llamando al setColorMode(int) y pasarlo 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 a getColorSpace(). Para determinar si el sistema reconoce un un espacio de color específico para que sea de una amplia gama, puedes llamar isWideGamut().

La clase Color te permite representar un color con cuatro componentes. empaquetado en un valor largo de 64 bits, en lugar de la representación más común que usa un número entero valor. 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

Para 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 OpenGL y APIs de 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:

Para 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 del búfer de color para la color 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 a través del 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 cualquier imagen de cadena de intercambio que usa 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, hacer un bucle a lo largo de los plataformas físicas disponibles de los dispositivos y elige un formato de color válido para ellas espacio de color.

En dispositivos Android, Vulkan admite una amplia gama de colores con los siguientes espacios de color y Formatos de color de 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 Display P3. espacio de color:

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