platform_bootable_recovery/minui/resources.cpp
Adrian Salido cc7b7eb737 minui: add ARGB_8888 format
Minui currently really only supports composing in 2 different formats
(see gr_color()) with ALPHA always as MSB. However, some devices
interpret PixelFormat as either Big Endian (i.e. ARGB has alpha at MSB)
or Little Endian (i.e. BGRA has alpha at MSB).

This change attempts to give multiple options to specify the same format
depending on device interpretation, while keeping just 2 different
composition formats supported by minui.
* ARGB + BGRA: Pixels have (A)lpha at MSB and (B)lue at LSB
* RGBX + ABGR: Pixels have (A)lpha at MSB and (R)ed at LSB

With this in mind, limiting the use of png_set_bgr() to happen only for
(ARGB/BGRA) combination while leaving (RGBX/ABGR) unchanged.

Bug: 143480444
Test: Boot device with TARGET_RECOVERY_PIXEL_FORMAT := <<all>>
Change-Id: Ia0f94ccbc564b8def7c9416483712ff1abbbf49a
2019-12-13 10:53:33 -08:00

454 lines
13 KiB
C++

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "private/resources.h"
#include <fcntl.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits>
#include <memory>
#include <regex>
#include <string>
#include <vector>
#include <android-base/strings.h>
#include <png.h>
#include "minui/minui.h"
static std::string g_resource_dir{ "/res/images" };
std::unique_ptr<GRSurface> GRSurface::Create(size_t width, size_t height, size_t row_bytes,
size_t pixel_bytes) {
if (width == 0 || row_bytes == 0 || height == 0 || pixel_bytes == 0) return nullptr;
if (std::numeric_limits<size_t>::max() / row_bytes < height) return nullptr;
// Cannot use std::make_unique to access non-public ctor.
auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes));
size_t data_size = row_bytes * height;
result->data_size_ =
(data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment;
result->data_.reset(
static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_)));
if (!result->data_) return nullptr;
return result;
}
std::unique_ptr<GRSurface> GRSurface::Clone() const {
auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes);
if (!result) return nullptr;
memcpy(result->data(), data(), data_size_);
return result;
}
PngHandler::PngHandler(const std::string& name) {
std::string res_path = g_resource_dir + "/" + name + ".png";
png_fp_.reset(fopen(res_path.c_str(), "rbe"));
// Try to read from |name| if the resource path does not work.
if (!png_fp_) {
png_fp_.reset(fopen(name.c_str(), "rbe"));
}
if (!png_fp_) {
error_code_ = -1;
return;
}
uint8_t header[8];
size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
if (bytesRead != sizeof(header)) {
error_code_ = -2;
return;
}
if (png_sig_cmp(header, 0, sizeof(header))) {
error_code_ = -3;
return;
}
png_ptr_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr_) {
error_code_ = -4;
return;
}
info_ptr_ = png_create_info_struct(png_ptr_);
if (!info_ptr_) {
error_code_ = -5;
return;
}
if (setjmp(png_jmpbuf(png_ptr_))) {
error_code_ = -6;
return;
}
png_init_io(png_ptr_, png_fp_.get());
png_set_sig_bytes(png_ptr_, sizeof(header));
png_read_info(png_ptr_, info_ptr_);
png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth_, &color_type_, nullptr, nullptr,
nullptr);
channels_ = png_get_channels(png_ptr_, info_ptr_);
if (bit_depth_ == 8 && channels_ == 3 && color_type_ == PNG_COLOR_TYPE_RGB) {
// 8-bit RGB images: great, nothing to do.
} else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_GRAY) {
// 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
png_set_expand_gray_1_2_4_to_8(png_ptr_);
} else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_PALETTE) {
// paletted images: expand to 8-bit RGB. Note that we DON'T
// currently expand the tRNS chunk (if any) to an alpha
// channel, because minui doesn't support alpha channels in
// general.
png_set_palette_to_rgb(png_ptr_);
channels_ = 3;
} else {
fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth_,
channels_, color_type_);
error_code_ = -7;
}
}
PngHandler::~PngHandler() {
if (png_ptr_) {
png_destroy_read_struct(&png_ptr_, &info_ptr_, nullptr);
}
}
// "display" surfaces are transformed into the framebuffer's required pixel format (currently only
// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row.
// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input
// format depends on the value of 'channels':
//
// 1 - input is 8-bit grayscale
// 3 - input is 24-bit RGB
// 4 - input is 32-bit RGBA/RGBX
//
// 'width' is the number of pixels in the row.
static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels,
int width) {
const uint8_t* ip = input_row;
uint8_t* op = output_row;
switch (channels) {
case 1:
// expand gray level to RGBX
for (int x = 0; x < width; ++x) {
*op++ = *ip;
*op++ = *ip;
*op++ = *ip;
*op++ = 0xff;
ip++;
}
break;
case 3:
// expand RGBA to RGBX
for (int x = 0; x < width; ++x) {
*op++ = *ip++;
*op++ = *ip++;
*op++ = *ip++;
*op++ = 0xff;
}
break;
case 4:
// copy RGBA to RGBX
memcpy(output_row, input_row, width * 4);
break;
}
}
int res_create_display_surface(const char* name, GRSurface** pSurface) {
*pSurface = nullptr;
PngHandler png_handler(name);
if (!png_handler) return png_handler.error_code();
png_structp png_ptr = png_handler.png_ptr();
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
auto surface = GRSurface::Create(width, height, width * 4, 4);
if (!surface) {
return -8;
}
PixelFormat pixel_format = gr_pixel_format();
if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
png_set_bgr(png_ptr);
}
for (png_uint_32 y = 0; y < height; ++y) {
std::vector<uint8_t> p_row(width * 4);
png_read_row(png_ptr, p_row.data(), nullptr);
TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes,
png_handler.channels(), width);
}
*pSurface = surface.release();
return 0;
}
int res_create_multi_display_surface(const char* name, int* frames, int* fps,
GRSurface*** pSurface) {
*pSurface = nullptr;
*frames = -1;
PngHandler png_handler(name);
if (!png_handler) return png_handler.error_code();
png_structp png_ptr = png_handler.png_ptr();
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
*frames = 1;
*fps = 20;
png_textp text;
int num_text;
if (png_get_text(png_ptr, png_handler.info_ptr(), &text, &num_text)) {
for (int i = 0; i < num_text; ++i) {
if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
*frames = atoi(text[i].text);
} else if (text[i].key && strcmp(text[i].key, "FPS") == 0 && text[i].text) {
*fps = atoi(text[i].text);
}
}
printf(" found frames = %d\n", *frames);
printf(" found fps = %d\n", *fps);
}
int result = 0;
GRSurface** surface = nullptr;
if (*frames <= 0 || *fps <= 0) {
printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
result = -10;
goto exit;
}
if (height % *frames != 0) {
printf("bad height (%d) for frame count (%d)\n", height, *frames);
result = -9;
goto exit;
}
surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
if (!surface) {
result = -8;
goto exit;
}
for (int i = 0; i < *frames; ++i) {
auto created_surface = GRSurface::Create(width, height / *frames, width * 4, 4);
if (!created_surface) {
result = -8;
goto exit;
}
surface[i] = created_surface.release();
}
if (gr_pixel_format() == PixelFormat::ARGB || gr_pixel_format() == PixelFormat::BGRA) {
png_set_bgr(png_ptr);
}
for (png_uint_32 y = 0; y < height; ++y) {
std::vector<uint8_t> p_row(width * 4);
png_read_row(png_ptr, p_row.data(), nullptr);
int frame = y % *frames;
uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width);
}
*pSurface = surface;
exit:
if (result < 0) {
if (surface) {
for (int i = 0; i < *frames; ++i) {
free(surface[i]);
}
free(surface);
}
}
return result;
}
int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
*pSurface = nullptr;
PngHandler png_handler(name);
if (!png_handler) return png_handler.error_code();
if (png_handler.channels() != 1) {
return -7;
}
png_structp png_ptr = png_handler.png_ptr();
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
auto surface = GRSurface::Create(width, height, width, 1);
if (!surface) {
return -8;
}
PixelFormat pixel_format = gr_pixel_format();
if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
png_set_bgr(png_ptr);
}
for (png_uint_32 y = 0; y < height; ++y) {
uint8_t* p_row = surface->data() + y * surface->row_bytes;
png_read_row(png_ptr, p_row, nullptr);
}
*pSurface = surface.release();
return 0;
}
void res_set_resource_dir(const std::string& dirname) {
g_resource_dir = dirname;
}
// This function tests if a locale string stored in PNG (prefix) matches
// the locale string provided by the system (locale).
bool matches_locale(const std::string& prefix, const std::string& locale) {
// According to the BCP 47 format, A locale string may consists of:
// language-{extlang}-{script}-{region}-{variant}
// The locale headers in PNG mostly consist of language-{region} except for sr-Latn, and some
// android's system locale can have the format language-{script}-{region}.
// Return true if the whole string of prefix matches the top part of locale. Otherwise try to
// match the locale string without the {script} section.
// For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale
// == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN".
if (prefix.empty()) {
return false;
}
if (android::base::StartsWith(locale, prefix)) {
return true;
}
size_t separator = prefix.find('-');
if (separator == std::string::npos) {
return false;
}
std::regex loc_regex(prefix.substr(0, separator) + "-[A-Za-z]*" + prefix.substr(separator));
return std::regex_match(locale, loc_regex);
}
std::vector<std::string> get_locales_in_png(const std::string& png_name) {
PngHandler png_handler(png_name);
if (!png_handler) {
printf("Failed to open %s, error: %d\n", png_name.c_str(), png_handler.error_code());
return {};
}
if (png_handler.channels() != 1) {
printf("Expect input png to have 1 data channel, this file has %d\n", png_handler.channels());
return {};
}
std::vector<std::string> result;
std::vector<uint8_t> row(png_handler.width());
for (png_uint_32 y = 0; y < png_handler.height(); ++y) {
png_read_row(png_handler.png_ptr(), row.data(), nullptr);
int h = (row[3] << 8) | row[2];
std::string loc(reinterpret_cast<char*>(&row[5]));
if (!loc.empty()) {
result.push_back(loc);
}
for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_handler.png_ptr(), row.data(), nullptr);
}
}
return result;
}
int res_create_localized_alpha_surface(const char* name,
const char* locale,
GRSurface** pSurface) {
*pSurface = nullptr;
if (locale == nullptr) {
return 0;
}
PngHandler png_handler(name);
if (!png_handler) return png_handler.error_code();
if (png_handler.channels() != 1) {
return -7;
}
png_structp png_ptr = png_handler.png_ptr();
png_uint_32 width = png_handler.width();
png_uint_32 height = png_handler.height();
for (png_uint_32 y = 0; y < height; ++y) {
std::vector<uint8_t> row(width);
png_read_row(png_ptr, row.data(), nullptr);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
__unused int len = row[4];
char* loc = reinterpret_cast<char*>(&row[5]);
// We need to include one additional line for the metadata of the localized image.
if (y + 1 + h > height) {
printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height);
return -8;
}
if (matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
auto surface = GRSurface::Create(w, h, w, 1);
if (!surface) {
return -9;
}
for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row.data(), nullptr);
memcpy(surface->data() + i * w, row.data(), w);
}
*pSurface = surface.release();
return 0;
}
for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row.data(), nullptr);
}
}
return -10;
}
void res_free_surface(GRSurface* surface) {
free(surface);
}