34723087fe
The legacy png files have an empty line in the end. And the recovery
used to match any missing locale, e.g. "he" with that line and gets an
empty image.
Since the empty image is barely useful, we should just error out and
fall back to the default locale.
This reversed the unit test check added in d17a688525
Bug: 128934634
Test: run locale test with "he" and legacy images, recovery reports
error and doesn't crash even without default locale fall back
Change-Id: Ibdb7dd0b42348de5e392c834cce67ff02be85c24
454 lines
13 KiB
C++
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::ABGR || 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::ABGR || 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::ABGR || 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);
|
|
}
|