1. 概述
为了进一步分析recovery系统使用资源png文件的过程,我们把相关代码剥离出来,作成小例子进行分析。
2. 正向分析的代码
这个小例子的第一步是能够遍历出png中所有locale的图片信息。
2.1 代码
代码如下:
/*
* gcc png_example.c -Iinclude -lpng
*
* The original code is based android_4.4.2_r2 branch.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <png.h>
#include <pixelflinger/pixelflinger.h> // android/system/core/include/pixelfligner/pixelflinger.h
// copy from minui.h
typedef struct {
int width;
int height;
int row_bytes;
int pixel_bytes;
unsigned char* data;
} GRSurface;
typedef GRSurface* gr_surface;
#define SURFACE_DATA_ALIGNMENT 8
static gr_surface malloc_surface(size_t data_size) {
unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT);
if (temp == NULL) return NULL;
gr_surface surface = (gr_surface) temp;
surface->data = temp + sizeof(GRSurface) +
(SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
return surface;
}
static int open_png(const char* file_name, png_structp* png_ptr, png_infop* info_ptr,
png_uint_32* width, png_uint_32* height, png_byte* channels) {
unsigned char header[8];
int result = 0;
FILE* fp = fopen(file_name, "rb");
if (fp == NULL) {
result = -1;
goto exit;
}
size_t bytesRead = fread(header, 1, sizeof(header), fp);
if (bytesRead != sizeof(header)) {
result = -2;
goto exit;
}
if (png_sig_cmp(header, 0, sizeof(header))) {
result = -3;
goto exit;
}
*png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!*png_ptr) {
result = -4;
goto exit;
}
*info_ptr = png_create_info_struct(*png_ptr);
if (!*info_ptr) {
result = -5;
goto exit;
}
if (setjmp(png_jmpbuf(*png_ptr))) {
result = -6;
goto exit;
}
png_init_io(*png_ptr, fp);
png_set_sig_bytes(*png_ptr, sizeof(header));
png_read_info(*png_ptr, *info_ptr);
int color_type, bit_depth;
png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
&color_type, NULL, NULL, NULL);
*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);
result = -7;
goto exit;
}
return result;
exit:
if (result < 0) {
png_destroy_read_struct(png_ptr, info_ptr, NULL);
}
if (fp != NULL) {
fclose(fp);
}
return result;
}
static int matches_locale(const char* loc, const char* locale) {
if (locale == NULL) return 0;
if (strcmp(loc, locale) == 0) return 1;
// if loc does *not* have an underscore, and it matches the start
// of locale, and the next character in locale *is* an underscore,
// that's a match. For instance, loc == "en" matches locale ==
// "en_US".
int i;
for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i);
if (loc[i] == '_') return 0;
return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
}
int read_png(const char* file_name, const char* locale/*,
gr_surface* pSurface*/) {
gr_surface surface = NULL;
int result = 0;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_uint_32 width, height;
png_byte channels;
//*pSurface = NULL;
if (locale == NULL) {
surface = malloc_surface(0);
surface->width = 0;
surface->height = 0;
surface->row_bytes = 0;
surface->pixel_bytes = 1;
goto exit;
}
result = open_png(file_name, &png_ptr, &info_ptr, &width, &height, &channels);
if (result < 0) return result;
if (channels != 1) {
result = -7;
goto exit;
}
unsigned char* row = malloc(width);
png_uint_32 y;
for (y = 0; y < height; ++y) {
png_read_row(png_ptr, row, NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
int len = row[4];
char* loc = (char*)row+5;
printf(" %20s: %s (%d x %d @ %lu)\n", file_name, loc, w, h, y);
if (y+1+h >= height || matches_locale(loc, locale)) {
surface = malloc_surface(w*h);
if (surface == NULL) {
result = -8;
goto exit;
}
surface->width = w;
surface->height = h;
surface->row_bytes = w;
surface->pixel_bytes = 1;
int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
memcpy(surface->data + i*w, row, w);
}
//*pSurface = (gr_surface) surface;
break;
} else {
int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
}
}
}
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
if (result < 0 && surface != NULL) free(surface);
return result;
}
int main(int argc, const char* argv[]) {
if (argc != 3) {
printf("Usage: ./a.out file_name locale\n");
exit(1);
}
read_png(argv[1], argv[2]);
return 0;
}
2.2 运行结果
如果机器上还没有安装png库,则需要先做这个准备工作。其实这一步只需要一条命令即可:
sudo apt-get install libpng-dev
因为是小例子,就没有makefile,直接用gcc命令来完成编译&链接的事情。命令在代码一开始的注释中给出了,下面是运行的效果:
flying-bird@flyingbird:~/examples/png$ gcc png_example.c -Iinclude -lpng
flying-bird@flyingbird:~/examples/png$ ./a.out ./res/installing_text.png en_GB
./res/installing_text.png: ar (305 x 38 @ 0)
./res/installing_text.png: bg (384 x 71 @ 39)
./res/installing_text.png: ca (429 x 71 @ 111)
./res/installing_text.png: cs (398 x 38 @ 183)
./res/installing_text.png: da (414 x 38 @ 222)
./res/installing_text.png: de (382 x 38 @ 261)
./res/installing_text.png: el (338 x 71 @ 300)
./res/installing_text.png: en_GB (324 x 38 @ 372)
flying-bird@flyingbird:~/examples/png$ ./a.out ./res/installing_text.png en
./res/installing_text.png: ar (305 x 38 @ 0)
./res/installing_text.png: bg (384 x 71 @ 39)
./res/installing_text.png: ca (429 x 71 @ 111)
./res/installing_text.png: cs (398 x 38 @ 183)
./res/installing_text.png: da (414 x 38 @ 222)
./res/installing_text.png: de (382 x 38 @ 261)
./res/installing_text.png: el (338 x 71 @ 300)
./res/installing_text.png: en_GB (324 x 38 @ 372)
./res/installing_text.png: en (324 x 38 @ 411)
flying-bird@flyingbird:~/examples/png$ ./a.out ./res/installing_text.png xy_GB
./res/installing_text.png: ar (305 x 38 @ 0)
./res/installing_text.png: bg (384 x 71 @ 39)
./res/installing_text.png: ca (429 x 71 @ 111)
./res/installing_text.png: cs (398 x 38 @ 183)
./res/installing_text.png: da (414 x 38 @ 222)
./res/installing_text.png: de (382 x 38 @ 261)
./res/installing_text.png: el (338 x 71 @ 300)
./res/installing_text.png: en_GB (324 x 38 @ 372)
./res/installing_text.png: en (324 x 38 @ 411)
./res/installing_text.png: es_ES (474 x 38 @ 450)
./res/installing_text.png: es (474 x 38 @ 489)
./res/installing_text.png: fa (416 x 38 @ 528)
./res/installing_text.png: fi (438 x 38 @ 567)
./res/installing_text.png: fr (388 x 71 @ 606)
./res/installing_text.png: hr (393 x 38 @ 678)
./res/installing_text.png: hu (370 x 38 @ 717)
./res/installing_text.png: in (397 x 38 @ 756)
./res/installing_text.png: it (384 x 71 @ 795)
./res/installing_text.png: iw (245 x 38 @ 867)
./res/installing_text.png: ja (474 x 71 @ 906)
./res/installing_text.png: ko (317 x 38 @ 978)
./res/installing_text.png: lt (384 x 38 @ 1017)
./res/installing_text.png: lv (385 x 71 @ 1056)
./res/installing_text.png: nb (434 x 38 @ 1128)
./res/installing_text.png: nl (350 x 38 @ 1167)
./res/installing_text.png: pl (394 x 38 @ 1206)
./res/installing_text.png: pt_BR (449 x 38 @ 1245)
./res/installing_text.png: pt (459 x 38 @ 1284)
./res/installing_text.png: ro (472 x 38 @ 1323)
./res/installing_text.png: ru (446 x 38 @ 1362)
./res/installing_text.png: sk (392 x 71 @ 1401)
./res/installing_text.png: sl (439 x 38 @ 1473)
./res/installing_text.png: sr (336 x 71 @ 1512)
./res/installing_text.png: sv (404 x 38 @ 1584)
./res/installing_text.png: th (334 x 38 @ 1623)
./res/installing_text.png: tl (419 x 38 @ 1662)
./res/installing_text.png: tr (414 x 38 @ 1701)
./res/installing_text.png: uk (471 x 38 @ 1740)
./res/installing_text.png: vi (462 x 38 @ 1779)
./res/installing_text.png: zh_CN (240 x 38 @ 1818)
./res/installing_text.png: zh (240 x 38 @ 1857)
./res/installing_text.png: (1 x 1 @ 1896)
flying-bird@flyingbird:~/examples/png$ ./a.out ./res/installing_text.png en_ES
./res/installing_text.png: ar (305 x 38 @ 0)
./res/installing_text.png: bg (384 x 71 @ 39)
./res/installing_text.png: ca (429 x 71 @ 111)
./res/installing_text.png: cs (398 x 38 @ 183)
./res/installing_text.png: da (414 x 38 @ 222)
./res/installing_text.png: de (382 x 38 @ 261)
./res/installing_text.png: el (338 x 71 @ 300)
./res/installing_text.png: en_GB (324 x 38 @ 372)
./res/installing_text.png: en (324 x 38 @ 411)
flying-bird@flyingbird:~/examples/png$
2.3 locale匹配规则
locale匹配的规则在Android源代码中有详细的说明(下面代码中间的一段注释),另外通过代码也可以分析出来。为了阅读方便,再copy&paste这个函数:
static int matches_locale(const char* loc, const char* locale) {
if (locale == NULL) return 0;
if (strcmp(loc, locale) == 0) return 1;
// if loc does *not* have an underscore, and it matches the start
// of locale, and the next character in locale *is* an underscore,
// that's a match. For instance, loc == "en" matches locale ==
// "en_US".
int i;
for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i);
if (loc[i] == '_') return 0;
return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
}
参数中第一个loc是从png文件中读取出来的字符串,第二个locale是目标字符串。运行时输入了几种参数进行对比运行,其中最后一个en_ES是非法代码,并没有西班牙英语
3. 图片规则
3.1 每个图片的width & height
进一步地,通过打印信息,可以看出每种locale的图片高度和宽度并不一样。——@ xxx这个数据无需关注,通常仅用于调试用,验证是否地区某个locale的图片at实现放置的位置。(此仅为猜测)
3.2 色彩深度等
通过Android的各个分支代码的对比分析,可以看到Android准备支持越来越多的图片格式。至于目前,我们仅关注如下规则:
- bit_depth == 8
- channels == 1
- color_type == PNG_COLOR_TYPE_GRAY