Aprimorar gráficos com amplo conteúdo de cores

O Android 8.0 (nível 26 da API) introduziu suporte ao gerenciamento de cores com outros espaços de cor além do RGB padrão (sRGB) para renderizar gráficos em dispositivos com telas compatíveis. Com essa compatibilidade, seu app pode renderizar bitmaps com perfis de cores amplos incorporados carregados de arquivos PNG, JPEG e WebP usando Java ou código nativo. Apps que usam OpenGL ou Vulkan podem gerar diretamente conteúdos de ampla gama de cores (usando o Display P3 e o scRGB). Esse recurso é útil para criar apps que envolvem reprodução de cores de alta fidelidade, como apps de edição de imagens e vídeos.

Compreender o modo ampla gama de cores

Perfis amplos de cores são perfis ICC, como Adobe RGB, Pro Photo RGB e DCI-P3, que são capazes de representar uma variedade maior de cores do que o sRGB. Telas com suporte a perfis de cores amplos podem exibir imagens com cores primárias mais profundas (vermelhos, verdes e azuis), além de cores secundárias mais ricas (como magentas, cianos e amarelos).

Em dispositivos Android com Android 8.0 (nível 26 da API) ou versões mais recentes, o app pode ativar o modo ampla gama de cores para uma atividade em que o sistema reconhece e processa corretamente imagens de bitmap com perfis de cores amplos incorporados. A classe ColorSpace.Named enumera uma lista parcial de espaços de cor comumente usados que têm suporte no Android.

Observação:quando o modo ampla gama de cores está ativado, a janela da atividade usa mais memória e processamento de GPU para a composição da tela. Antes de ativar o modo ampla gama de cores, considere cuidadosamente se a atividade realmente se beneficia dele. Por exemplo, uma atividade que mostra fotos em tela cheia é uma boa candidata para o modo ampla gama de cores, mas uma atividade que mostra miniaturas pequenas não é.

Ativar o modo ampla gama de cores

Use o atributo colorMode para solicitar que a atividade seja mostrada no modo ampla gama de cores em dispositivos compatíveis. Nesse modo, uma janela pode renderizar fora da gama sRGB para mostrar cores mais vibrantes. Se o dispositivo não tiver suporte ao modo de gama ampla de cores, o atributo não terá efeito. Se o app precisar determinar se uma determinada tela é compatível com a ampla gama de cores, chame o método isWideColorGamut(). O app também pode chamar isScreenWideColorGamut(), que retorna true somente se a tela for compatível com a ampla gama de cores e o dispositivo oferecer suporte à renderização de cores da ampla gama de cores.

Uma tela pode ser compatível com a ampla gama de cores, mas não ser gerenciada por cores. Nesse caso, o sistema não concederá a um app o modo ampla gama de cores. Quando uma tela não é gerenciada por cores, como acontece com todas as versões do Android anteriores à 8.0, o sistema remapeia as cores desenhadas pelo app para a gama da tela.

Para ativar a gama ampla de cores na sua atividade, defina o atributo colorMode como wideColorGamut no arquivo AndroidManifest.xml. É necessário fazer isso para cada atividade para a qual você quer ativar o modo amplo de cores.

android:colorMode="wideColorGamut"

Você também pode definir o modo de cor de forma programática na atividade, chamando o método setColorMode(int) e transmitindo COLOR_MODE_WIDE_COLOR_GAMUT.

Renderizar conteúdos de ampla gama de cores

Figura 1. Exibir espaços de cor P3 (laranja) x sRGB (branco)

Para renderizar um conteúdo de ampla gama de cores, seu app precisa carregar um bitmap de cores amplo, ou seja, um bitmap com um perfil de cores que contenha um espaço de cores mais amplo que o sRGB. Os perfis amplos de cores comuns incluem Adobe RGB, DCI-P3 e Display P3.

Seu app pode consultar o espaço de cor de um bitmap chamando getColorSpace(). Para determinar se o sistema reconhece um espaço de cor específico para ter ampla gama, chame o método isWideGamut().

A classe Color permite representar uma cor com quatro componentes empacotados em um valor longo de 64 bits, em vez da representação mais comum que usa um valor inteiro. Usando valores longos, você pode definir cores com mais precisão do que valores inteiros. Se você precisar criar ou codificar uma cor como um valor longo, use um dos métodos pack() na classe Color.

Você pode verificar se o app solicitou corretamente o modo ampla gama de cores conferindo se o método getColorMode() retorna COLOR_MODE_WIDE_COLOR_GAMUT. No entanto, esse método não indica se o modo foi realmente concedido.

Usar a compatibilidade com a ampla gama de cores no código nativo

Esta seção descreve como ativar o modo ampla gama de cores com as APIs OpenGL e Vulkan caso seu app use código nativo.

OpenGL

Para usar o modo ampla gama de cores no OpenGL, seu app precisa incluir a biblioteca EGL 1.4 com uma das seguintes extensões:

Para ativar o recurso, primeiro é necessário criar um contexto de GL via eglChooseConfig, com um dos três formatos de buffer de cores com suporte para cores amplas nos atributos. O formato do buffer de cores para cores amplas precisa ser um destes conjuntos de valores RGBA:

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

Em seguida, solicite a extensão de espaço de cores P3 ao criar seus destinos de renderização, conforme mostrado no snippet de código a seguir:

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

O suporte do Vulkan à ampla gama de cores é fornecido pela extensão VK_EXT_swapchain_colorspace.

Antes de ativar o suporte a cores amplas no seu código Vulkan, confira se a extensão oferece suporte via vkEnumerateInstanceExtensionProperties. Se a extensão estiver disponível, será necessário ativá-la durante vkCreateInstance antes de criar qualquer imagem de cadeia de troca que use os outros espaços de cor definidos pela extensão.

Antes de criar a cadeia de troca, é necessário escolher o espaço de cor desejado, percorrer as superfícies disponíveis do dispositivo físico e escolher um formato de cor válido para esse espaço de cores.

Em dispositivos Android, o Vulkan oferece suporte à ampla gama de cores com os seguintes espaços de cor e formatos de cor VkSurfaceFormatKHR:

  • Espaços de cores da gama ampla de cores do Vulkan:
    • VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT
    • VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT
  • Formatos de cores com suporte para ampla gama de cores do Vulkan:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

O snippet de código abaixo mostra como conferir se o dispositivo oferece suporte ao espaço de cores do 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
}

O snippet de código abaixo mostra como solicitar uma cadeia de troca do Vulkan com 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;
}