Aprimorar gráficos com amplo conteúdo de cores

O Android 8.0 (API de nível 26) introduziu a compatibilidade com o gerenciamento de cores a outros espaços de cor (link em inglês) 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 a partir de arquivos PNG, JPEG e WebP por 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, links em inglês). Esse recurso é útil para criar apps que envolvam uma 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 de cores amplos são perfis ICC, como Adobe RGB, Pro Photo RGB e DCI-P3 (links em inglês), capazes de representar uma gama mais ampla de cores que o sRGB. Telas que são compatíveis com 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 (API de nível 26) ou versões mais recentes, o app pode ativar o modo ampla gama de cores para uma atividade na qual 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 são compatíveis com o Android.

Observação: quando o modo ampla gama de cores está ativado, a janela da atividade usa mais memória e processamento da GPU para a composição da tela. Antes de ativar o modo ampla gama de cores, considere se a atividade realmente se beneficia dele. Por exemplo, uma atividade que exibe 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 exibida no modo ampla gama de cores em dispositivos compatíveis. No modo ampla gama de cores, uma janela pode renderizar fora da gama sRGB para exibir cores mais vibrantes. Se o dispositivo não for compatível com a renderização de ampla gama de cores, esse atributo não terá nenhum efeito. Se o app precisa identificar 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 só retornará true se a tela for compatível com a ampla gama de cores e o dispositivo compatível com a 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á ao app o modo ampla gama de cores. Quando uma tela não é gerenciada por cores, o que era o caso de todas as versões do Android anteriores à 8.0, o sistema refaz o mapeamento das cores desenhadas pelo app para a gama da tela.

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

    android:colorMode="wideColorGamut"
    

Você também pode definir o modo de cores de forma programática na sua atividade, chamando o método setColorMode(int) e transmitindo-o a 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 conteúdos com 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 cor maior que sRGB. Os perfis de cores amplos 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, você pode chamar o método isWideGamut().

A classe Color permite representar uma cor com quatro componentes compactados em um valor de 64 bits, em vez da representação mais comum que usa um valor inteiro. Usando valores longos, é possível definir cores com mais precisão do que com 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. Contudo, esse método não indica se o modo ampla gama de cores foi realmente concedido.

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

Esta seção descreve como habilitar o modo ampla gama de cores com as APIs OpenGL e Vulkan, se seu app usar 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, é necessário primeiro criar um contexto de GL via eglChooseConfig, com um dos três formatos de buffer de cores compatíveis 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 do espaço de cor 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

A compatibilidade do Vulkan com a ampla gama de cores é oferecida por meio da extensão VK_EXT_swapchain_colorspace.

Antes de ativar a compatibilidade com a ampla gama cores no seu código Vulkan, verifique se a extensão é compatível 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, percorrer as superfícies disponíveis do dispositivo físico e escolher um formato de cor válido para esse espaço de cor.

Em dispositivos Android, o Vulkan é compatível com a ampla gama de cores com os seguintes espaços de cor e formatos de cores VkSurfaceFormatKHR:

  • Espaços de cor 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 compatibilidade com ampla gama de cores do Vulkan:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

O snippet de código a seguir mostra como é possível verificar se o dispositivo é compatível com o espaço de cor 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 a seguir 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;
    }