Wzbogacanie grafiki o bogatą kolorystykę

W Androidzie 8.0 (poziom interfejsu API 26) wprowadzono obsługę zarządzania kolorami w przypadku dodatkowej przestrzeni kolorów, oprócz standardowego RGB (sRGB) do renderowania grafik na urządzeniach ze zgodnymi wyświetlaczami. Dzięki tej obsłudze aplikacja może renderować mapy bitowe z osadzonymi profilami kolorów wczytywanych z plików PNG, JPEG i WebP za pomocą kodu Java lub natywnego. Aplikacje używające OpenGL lub Vulkan mogą bezpośrednio wyświetlać treści o szerokiej gamie kolorów (za pomocą Display P3 i scRGB). Ta funkcja jest przydatna podczas tworzenia aplikacji wymagających wysokiej jakości reprodukcji kolorów, np. aplikacji do edycji zdjęć i filmów.

Tryb szerokiej gamy kolorów

Szerokie profile kolorów to profile IC, takie jak Adobe RGB, Pro Photo RGB i DCI-P3, które mogą prezentować szerszy zakres kolorów niż sRGB. Na ekranach obsługujących szerokie profile kolorów można wyświetlać obrazy w głębszych kolorach podstawowych (czerwonych, zielonych i niebieskich) oraz w kolorach drugorzędnych (takich jak magenta, błękitny i żółty).

Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym, które obsługują tę funkcję, aplikacja może włączyć tryb szerokiej gamy kolorów dla aktywności, w ramach której system rozpoznaje i prawidłowo przetwarza obrazy bitmapowe z osadzonymi szerokimi profilami kolorów. Klasa ColorSpace.Named zawiera częściową listę często używanych przestrzeni kolorów obsługiwanych przez Androida.

Uwaga: gdy włączony jest tryb szerokiej gamy kolorów, okno aktywności wykorzystuje więcej pamięci i procesorów GPU na potrzeby kompozycji ekranu. Zanim włączysz tryb szerokiej gamy kolorów, zastanów się, czy dany rodzaj aktywności przyniesie korzyści. Na przykład działanie polegające na wyświetlaniu zdjęć na pełnym ekranie dobrze sprawdza się w trybie szerokiej gamy kolorów, ale działanie polegające na wyświetlaniu małych miniatur już nie.

Włącz tryb szerokiej gamy kolorów

Aby poprosić o wyświetlenie aktywności w trybie szerokiej gamy kolorów na zgodnych urządzeniach, użyj atrybutu colorMode. W trybie szerokiej gamy kolorów okno może renderować się poza zakresem sRGB, aby wyświetlać bardziej żywe kolory. Jeśli urządzenie nie obsługuje renderowania w szerokiej gamie kolorów, atrybut nie ma żadnego efektu. Jeśli aplikacja musi określić, czy dany wyświetlacz obsługuje szerokie gamy kolorów, wywołaj metodę isWideColorGamut(). Możesz też wywołać funkcję isScreenWideColorGamut(), która zwraca wartość true tylko wtedy, gdy wyświetlacz obsługuje szerokie gamy kolorów, a urządzenie obsługuje renderowanie w szerokiej gamie kolorów.

Wyświetlacz może korzystać z szerokiej gamy kolorów, ale nie może być zarządzany. W takim przypadku system nie udostępni aplikacji trybu szerokiej gamy kolorów. Jeśli wyświetlacz nie jest zarządzany przez kolory (jak miało to miejsce w przypadku wszystkich wersji Androida starszych niż 8.0), system przypisuje kolory rysowane przez aplikację do gamy wyświetlacza.

Aby włączyć szeroki zakres kolorów w aktywności, w pliku AndroidManifest.xml ustaw atrybut colorMode na wideColorGamut. Musisz to zrobić w przypadku każdej aktywności, w której chcesz włączyć szeroki tryb kolorów.

android:colorMode="wideColorGamut"

Tryb koloru możesz też ustawić automatycznie w aktywności, wywołując metodę setColorMode(int) i przekazując COLOR_MODE_WIDE_COLOR_GAMUT.

Renderuj treści o szerokiej gamie kolorów

Rysunek 1. Wyświetlacz P3 (pomarańczowy) i przestrzenie kolorów sRGB (białe)

Aby renderować treści o szerokiej gamie kolorów, aplikacja musi wczytać bitmapę o szerokiej gamie kolorów, czyli bitmapę z profilem kolorów zawierającym przestrzeń kolorów szerszą niż sRGB. Najczęściej używane szerokie profile kolorów to Adobe RGB, DCI-P3 i Display P3.

Aplikacja może wysyłać zapytania o przestrzeń kolorów bitmapy, wywołując metodę getColorSpace(). Aby sprawdzić, czy system rozpoznaje konkretną przestrzeń kolorów, możesz wywołać metodę isWideGamut().

Klasa Color umożliwia reprezentowanie koloru z 4 komponentami ustawionymi w 64-bitową wartość długości, a nie w przypadku najpopularniejszej reprezentacji, która zawiera liczbę całkowitą. Stosując długie wartości, możesz definiować kolory z większą precyzją niż wartości całkowite. Jeśli chcesz utworzyć lub zakodować kolor jako długa wartość, użyj jednej z metod pack() w klasie Color.

Aby sprawdzić, czy aplikacja prawidłowo zażądała trybu szerokiej gamy kolorów, sprawdź, czy metoda getColorMode() zwraca wartość COLOR_MODE_WIDE_COLOR_GAMUT (nie wskazuje jednak, czy tryb szerokiej gamy kolorów rzeczywiście został przyznany).

Użyj obsługi szerokiej gamy kolorów w kodzie natywnym

W tej sekcji opisujemy, jak włączyć tryb szerokiej gamy kolorów za pomocą interfejsów API OpenGL i Vulkan, jeśli aplikacja używa kodu natywnego.

OpenGL

Aby używać trybu szerokiej gamy kolorów w trybie OpenGL, aplikacja musi zawierać bibliotekę EGL 1.4 z jednym z tych rozszerzeń:

Aby włączyć tę funkcję, musisz najpierw utworzyć kontekst GL za pomocą eglChooseConfig z jednym z 3 obsługiwanych formatów bufora kolorów dla szerokiego koloru w atrybutach. Format bufora kolorów dla szerokiego zakresu kolorów musi mieć jeden z tych zestawów wartości RGBA:

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

Następnie wyślij żądanie rozszerzenia przestrzeni kolorów P3 podczas tworzenia celów renderowania, jak pokazano w tym fragmencie kodu:

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());

Wulkan

Obsługę szerokiej gamy kolorów z interfejsu Vulkan zapewnia rozszerzenie VK_EXT_swapchain_colorspace.

Zanim włączysz obsługę szerokich kolorów w kodzie Vulkan, najpierw sprawdź, czy rozszerzenie jest obsługiwane przez vkEnumerateInstanceExtensionProperties. Jeśli rozszerzenie jest dostępne, musisz je włączyć podczas vkCreateInstance, zanim utworzysz jakiekolwiek obrazy łańcucha wymiany, które korzystają z dodatkowych przestrzeni kolorów zdefiniowanych przez rozszerzenie.

Zanim utworzysz łańcuch kolorów, musisz wybrać odpowiednią przestrzeń kolorów, a następnie przejrzeć dostępne fizyczne powierzchnie urządzeń i wybrać prawidłowy format dla tej przestrzeni kolorów.

Na urządzeniach z Androidem Vulkan obsługuje szeroki wachlarz kolorów z tymi przestrzeniami barw i formatami kolorów VkSurfaceFormatKHR:

  • Szeroka gama kolorów Vulkan:
    • VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT
    • VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT
  • Formaty kolorów Vulkana z obsługą szerokiej gamy kolorów:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

Ten fragment kodu pozwala sprawdzić, czy urządzenie obsługuje przestrzeń kolorów 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
}

Ten fragment kodu pokazuje, jak zażądać zamiany interfejsu Vulkan za pomocą 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;
}