Cải thiện đồ hoạ với nội dung màu rộng

Android 8.0 (API cấp 26) đã ra mắt tính năng hỗ trợ quản lý màu sắc cho các không gian màu bổ sung bên cạnh RGB (sRGB) tiêu chuẩn để kết xuất đồ hoạ trên các thiết bị có màn hình tương thích. Với tính năng hỗ trợ này, ứng dụng của bạn có thể kết xuất bitmap có cấu hình màu rộng được nhúng được tải từ các tệp PNG, JPEG và WebP thông qua mã Java hoặc mã gốc. Các ứng dụng sử dụng OpenGL hoặc Vulkan có thể trực tiếp tạo ra nội dung gam màu rộng (sử dụng Display P3scRGB). Khả năng này rất hữu ích khi tạo các ứng dụng có liên quan đến việc tái tạo màu sắc có độ chân thực cao, chẳng hạn như các ứng dụng chỉnh sửa hình ảnh và video.

Tìm hiểu về chế độ gam màu rộng

Hồ sơ màu rộng Hồ sơ ICC, chẳng hạn như Adobe RGB, Pro Photo RGBDCI-P3, tức là có khả năng thể hiện dải màu rộng hơn so với sRGB. Màn hình hỗ trợ cấu hình màu rộng có thể hiển thị hình ảnh có các màu cơ bản sâu sắc hơn (đỏ, xanh lục và xanh lam) cũng như các hình ảnh phụ phong phú hơn (chẳng hạn như đỏ tươi, xanh lơ và vàng).

Trên thiết bị Android chạy Android 8.0 (API cấp 26) trở lên có hỗ trợ phiên bản này, ứng dụng của bạn có thể bật chế độ màu gam màu rộng cho một hoạt động, trong đó hệ thống nhận dạng và xử lý chính xác hình ảnh bitmap với cấu hình màu rộng được nhúng. Chiến lược phát hành đĩa đơn Lớp ColorSpace.Named liệt kê một phần danh sách các lớp thường dùng hệ màu mà Android hỗ trợ.

Lưu ý: Khi bạn bật chế độ gam màu rộng, giá trị cửa sổ sử dụng nhiều bộ nhớ và xử lý GPU hơn cho cấu trúc màn hình. Trước khi bật chế độ gam màu rộng, bạn nên cân nhắc kỹ xem hoạt động có thực sự hưởng lợi từ chế độ này hay không. Ví dụ: một hiển thị hình ảnh ở chế độ toàn màn hình là một lựa chọn phù hợp cho chế độ gam màu rộng, nhưng thì hoạt động có hiển thị hình thu nhỏ nhỏ thì không.

Bật chế độ gam màu rộng

Dùng thuộc tính colorMode để yêu cầu hiển thị hoạt động ở chế độ gam màu rộng trên các thiết bị tương thích. Ở chế độ gam màu rộng, một cửa sổ có thể hiển thị bên ngoài gam màu sRGB để hiển thị các màu rực rỡ hơn. Nếu thiết bị không hỗ trợ tính năng hiển thị gam màu rộng, thì thuộc tính này sẽ không có hiệu lực. Nếu ứng dụng của bạn cần xác định xem một màn hình có gam màu rộng, hãy gọi phương thức isWideColorGamut(). Ứng dụng của bạn cũng có thể gọi isScreenWideColorGamut(), trả về true chỉ khi màn hình có gam màu rộng và thiết bị hỗ trợ gam màu rộng kết xuất màu.

Một màn hình có thể có gam màu rộng nhưng không được quản lý màu. Trong trường hợp đó, hệ thống sẽ không cấp cho ứng dụng chế độ gam màu rộng. Khi màn hình không được quản lý bằng màu sắc —như trường hợp của tất cả các phiên bản Android trước 8.0 — hệ thống đã ánh xạ lại các màu do ứng dụng vẽ theo gam màu của màn hình.

Để bật gam màu rộng trong hoạt động, hãy đặt thuộc tính colorMode thành wideColorGamut trong tệp AndroidManifest.xml. Bạn cần thực hiện việc này cho từng hoạt động mà bạn muốn bật chế độ màu rộng.

android:colorMode="wideColorGamut"

Bạn cũng có thể đặt chế độ màu theo phương thức lập trình trong hoạt động bằng cách gọi phương thức setColorMode(int) và truyền vào COLOR_MODE_WIDE_COLOR_GAMUT.

Hiển thị nội dung gam màu rộng

Hình 1. Hiển thị P3 (cam) so với sRGB (trắng)

Để hiển thị nội dung gam màu rộng, ứng dụng của bạn phải tải một bitmap màu rộng, đó là một bitmap có cấu hình màu chứa hệ màu rộng hơn sRGB. Các hồ sơ màu rộng phổ biến bao gồm Adobe RGB, DCI-P3 và Display P3.

Ứng dụng có thể truy vấn hệ màu của bitmap bằng cách gọi getColorSpace(). Để xác định xem hệ thống có nhận ra một hệ màu cụ thể là gam rộng, bạn có thể gọi hàm isWideGamut().

Lớp Color cho phép bạn biểu thị màu bằng 4 thành phần được đóng gói thành một giá trị dài 64 bit, thay vì cách biểu diễn phổ biến nhất sử dụng số nguyên giá trị. Khi sử dụng các giá trị dài, bạn có thể xác định màu bằng chính xác hơn giá trị số nguyên. Nếu bạn cần tạo hoặc mã hoá màu dưới dạng giá trị dài, hãy sử dụng một trong các phương thức pack() trong lớp Color.

Bạn có thể xác minh xem ứng dụng của mình có yêu cầu đúng cách chế độ gam màu rộng hay không bằng cách kiểm tra xem phương thức getColorMode() có trả về COLOR_MODE_WIDE_COLOR_GAMUT hay không (tuy nhiên, phương thức này không cho biết liệu chế độ gam màu rộng có thực sự được cấp hay không).

Sử dụng tính năng hỗ trợ gam màu rộng trong mã gốc

Phần này mô tả cách bật chế độ gam màu rộng bằng OpenGL và Các API Vulkan nếu ứng dụng của bạn dùng mã gốc.

OpenGL

Để sử dụng chế độ gam màu rộng trong OpenGL, ứng dụng của bạn phải chứa thư viện EGL 1.4 có một trong các tiện ích sau:

Để bật tính năng này, trước tiên, bạn phải tạo bối cảnh GL qua eglChooseConfig, với 1 trong 3 tính năng được hỗ trợ định dạng vùng đệm màu cho màu rộng trong các thuộc tính. Định dạng vùng đệm màu cho quảng cáo rộng màu phải là một trong các nhóm giá trị RGBA sau:

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

Sau đó, hãy yêu cầu tiện ích không gian màu P3 khi tạo mục tiêu kết xuất, như minh hoạ trong đoạn mã sau:

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

Tính năng hỗ trợ Vulkan cho gam màu rộng được cung cấp thông qua VK_EXT_swapchain_colorspace.

Trước khi bật tính năng hỗ trợ màu rộng trong mã Vulkan, trước tiên, hãy kiểm tra xem tiện ích được hỗ trợ qua vkEnumerateInstanceExtensionProperties. Nếu tiện ích này có sẵn, bạn phải bật tiện ích đó trong thời gian vkCreateInstance trước khi tạo bất kỳ hình ảnh chuỗi hoán đổi nào sử dụng hệ màu bổ sung do tiện ích xác định.

Trước khi tạo chuỗi hoán đổi, bạn cần chọn không gian màu mong muốn, sau đó lặp lại các bề mặt thiết bị thực có sẵn và chọn một định dạng màu hợp lệ cho không gian màu đó.

Trên thiết bị Android, Vulkan hỗ trợ gam màu rộng với các không gian màu và định dạng màu VkSurfaceFormatKHR sau đây:

  • Hệ màu gam màu rộng của Vulkan:
    • VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT
    • VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT
  • Định dạng màu Vulkan có hỗ trợ gam màu rộng:
    • VK_FORMAT_R16G16B16A16_SFLOAT
    • VK_FORMAT_A2R10G10B10_UNORM_PACK32
    • VK_FORMAT_R8G8B8A8_UNORM

Đoạn mã sau đây cho biết cách bạn có thể kiểm tra xem thiết bị có hỗ trợ không gian màu Hiển thị P3 hay không:

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ạn mã sau đây cho biết cách yêu cầu một chuỗi hoán đổi Vulkan bằng 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;
}