rk3566 + 安卓 11 关机充电 logo 优化

    rk 关机充电的显示基于 minui 显示框架, 它具有简单的图形绘制, 以及解析 png 图片并将其简单的显示出来的能力. 它支持三种显示框架 fbdevdrmadf. 由于需要解析 png 图片因此也需要使用到 libpng 库.

    充电 logo 显示自身包括一个服务 charger. 用于监测按键是否按下充电图片的解析显示, 以及按键状态的处理.

一、charger 线程

    同样的根据 bp 文件跟踪调用链.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cc_binary {
name: "charger",
defaults: ["charger_defaults"],
recovery_available: true,
srcs: [
"charger.cpp",
"charger_utils.cpp",
],

target: { // 需要的库来自 recovery
recovery: {
// No UI and libsuspend for recovery charger.
cflags: [
"-DCHARGER_FORCE_NO_UI=1",
],
exclude_shared_libs: [
"libpng",
],
exclude_static_libs: [
"libhealthd_draw",
"libhealthd_charger",
"libminui",
"libsuspend",
],
}
}
}

    调用链接如下

1
2
healthd_charger_main(argc, argv) --> 
harger.StartLoop();

    最终调用 StartLoop 接口, 它是哪来的呢? 查看 charge 类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
public:
using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;

Charger(const sp<android::hardware::health::V2_1::IHealth>& service);
~Charger();

protected: // 继承 HalHealthLoop 类并重写了下面方法.
// HealthLoop overrides.
void Heartbeat() override;
int PrepareToWait() override;
void Init(struct healthd_config* config) override;
// HalHealthLoop overrides
void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;

......

animation batt_anim_;
GRSurface* surf_unknown_ = nullptr;
int boot_min_cap_ = 0;

HealthInfo_1_0 health_info_ = {};
std::unique_ptr<HealthdDraw> healthd_draw_;
std::vector<animation::frame> owned_frames_;
};
} // namespace android

    通过类可以知道 charge 继承了 HalHealthLoop 并在此基础上重写了 Heartbeat()PrepareToWait()OnHealthInfoChanged()Init() 方法.
    HalHealthLoop 类由 hall 层提供对应的路径 hardware/interfaces/health/2.0/utils/libhealthservice/*. 它提供的 StartLoop 方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int HealthLoop::StartLoop() {
int ret;

// 设置 log 重定向
klog_set_level(KLOG_LEVEL);

// 做一些初始化, uevent 等.
ret = InitInternal();
if (ret) {
KLOG_ERROR(LOG_TAG, "Initialization failed, exiting\n");
return 2;
}

// 调用, mainloop
MainLoop();
KLOG_ERROR(LOG_TAG, "Main loop terminated, exiting\n");
return 3;
}

    该方法调用了 MainLoop() 方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void HealthLoop::MainLoop(void) {
int nevents = 0;
while (1) {
reject_event_register_ = true;
size_t eventct = event_handlers_.size();
struct epoll_event events[eventct];
int timeout = awake_poll_interval_;

int mode_timeout;

if (!nevents) PeriodicChores();

Heartbeat(); // 被 charger 重写

mode_timeout = PrepareToWait();
if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout)) timeout = mode_timeout;
// 检测 event 时间
nevents = epoll_wait(epollfd_, events, eventct, timeout);
if (nevents == -1) {
if (errno == EINTR) continue;
KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
break;
}

// 发生事件调用事件处理函数 event_handler->func 更新按键状态
for (int n = 0; n < nevents; ++n) {
if (events[n].data.ptr) {
auto* event_handler = reinterpret_cast<EventHandler*>(events[n].data.ptr);
event_handler->func(event_handler->object, events[n].events);
}
}
}

return;
}

    MainLoop() 就是一个循环, 在循环中有两件事情.

    1. 通过 Heartbeat() 根据按键状态亮屏, 根据充电状态判断是否亮屏, 更新充电 logo 显示.
    1. 监测 event 状态, 发生事件之后调用 event_handler->func 更新按键状态. 它通过 int HealthLoop::RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) 接口注册

    其中关键的就是 charger 重写的 Heartbeat 方法.

1
2
3
4
5
6
7
8
9
10
void Charger::Heartbeat() {
// charger* charger = &charger_state;
int64_t now = curr_time_ms();

LOGV("[%" PRId64 "] Heartbeat\n", now);
HandleInputState(now); // 根据按键状态亮屏.
HandlePowerSupplyState(now); // 根据充电状态判断是否亮屏

UpdateScreenState(now); // 更新充电 logo 显示
}

这个实现逻辑是有问题的, 不支持按键灭屏, 拔出充电器之后, 会先亮屏显示一次充电 logo 再灭屏关机.

1、根据按键状态亮屏.

    长按则使用 reboot 命令重启机器, 重启时间等于 POWER_ON_KEY_TIME, 短按亮屏, 如果是 key->pending 也亮屏.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void Charger::HandleInputState(int64_t now) {
ProcessKey(KEY_POWER, now);

if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1;
}

void Charger::ProcessKey(int code, int64_t now) {
key_state* key = &keys_[code];

LOGV("[%" PRId64 "] key[%d] ProcessKey\n", now, code);

if (code == KEY_POWER) {
if (key->down) { // 长安则重启重启时间等于 POWER_ON_KEY_TIME
int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;
if (now >= reboot_timeout) {
if (property_get_bool("ro.enable_boot_charger_mode", false)) {
LOGE("[%" PRId64 "] booting from charger mode\n", now);
property_set("sys.boot_from_charger_mode", "1");
} else {
if (batt_anim_.cur_level >= boot_min_cap_) {
LOGE("[%" PRId64 "] rebooting\n", now);
reboot(RB_AUTOBOOT);
} else {
LOGV("[%" PRId64
"] ignore power-button press, battery level "
"less than minimum\n",
now);
}
}
} else { // 不是长按就开机.
SetNextKeyCheck(key, POWER_ON_KEY_TIME);

kick_animation(&batt_anim_);
request_suspend(false);
}
} else {
// 如果是 key->pending 也亮屏
if (key->pending) {
kick_animation(&batt_anim_);
request_suspend(false);
}
}
}

key->pending = false;
}

2、充电状态判断是否亮屏

    它实现俩个功能, 没插充电器超时关机、第一次插入充电器亮屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void Charger::HandlePowerSupplyState(int64_t now) {
int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
if (!have_battery_state_) return;

if (!charger_online()) { // 没插充电器超时关机
request_suspend(false);
if (next_pwr_check_ == -1) {
timer_shutdown =
property_get_int32(UNPLUGGED_SHUTDOWN_TIME_PROP, UNPLUGGED_SHUTDOWN_TIME);
next_screen_transition_ = now - 1;
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
next_pwr_check_ = now + timer_shutdown;
LOGE("[%" PRId64 "] device unplugged: shutting down in %" PRId64 " (@ %" PRId64 ")\n",
now, (int64_t)timer_shutdown, next_pwr_check_);
} else if (now >= next_pwr_check_) {
LOGE("[%" PRId64 "] shutting down\n", now);
reboot(RB_POWER_OFF);
} else {
/* otherwise we already have a shutdown timer scheduled */
}
} else { // 第一次插入充电器亮屏
if (next_pwr_check_ != -1) {
request_suspend(false);
next_screen_transition_ = now - 1;
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
LOGE("[%" PRId64 "] device plugged in: shutdown cancelled\n", now);
}
next_pwr_check_ = -1;
}
}

3、更新充电 logo 显示

3.1 UpdateScreenState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Charger::UpdateScreenState(int64_t now) {
......
healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_);
....
}

void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) {

if (!graphics_available) return;
clear_screen(); // 清屏
if (batt_anim->cur_status == BATTERY_STATUS_UNKNOWN || batt_anim->cur_level < 0 ||
batt_anim->num_frames == 0)
draw_unknown(surf_unknown);
else
draw_battery(batt_anim); // 绘图
gr_flip(); // 初始化显示硬件 pip, 并将显存中的图片显示出来.
}

3.2 draw_battery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void HealthdDraw::draw_battery(const animation* anim) {
int y_start = 0;

// 打印绘制的图片
printf("drawing frame #%d\n", anim->cur_frame);

if (!graphics_available) return;

// 拿到对应的图片
const animation::frame& frame = anim->frames[anim->cur_frame];

if (anim->num_frames != 0) {
// 绘制图 surface 片并返回, y 轴左下角起始位置用于绘制电量百分比
y_start = draw_surface_centered(frame.surface);
printf("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame, frame.min_level,
frame.disp_time);
}
draw_clock(anim); // 绘制时间, 一般也不用
draw_percent(anim, y_start); // 绘制电量百分比
}

3.3 draw_surface_centered

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int HealthdDraw::draw_surface_centered(GRSurface* surface) {
if (!graphics_available) return 0;

int w = gr_get_width(surface); // 拿到图片的宽
int h = gr_get_height(surface); // 拿到图片的高

// 计算图片的原点 x, y 默认在中间显示
int x = (screen_width_ - w) / 2 + kSplitOffset;
int y = (screen_height_ - h) / 2;

printf("drawing surface %dx%d+%d+%d\n", w, h, x, y);
// 将 surface 描述的图片写入映射到的显存
gr_blit(surface, 0, 0, w, h, x, y);
if (kSplitScreen) {
x += screen_width_ - 2 * kSplitOffset;
printf("drawing surface %dx%d+%d+%d\n", w, h, x, y);
gr_blit(surface, 0, 0, w, h, x, y);
}

return y + h; // 返回左下角的坐标点
}

3.4 draw_percent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void HealthdDraw::draw_percent(const animation* anim, int y_start) {

if (!graphics_available) return;
int cur_level = anim->cur_level;
if (anim->cur_status == BATTERY_STATUS_FULL) {
cur_level = 100;
}

if (cur_level < 0) return; // 电量必须大于 0 才能显示百分比

const animation::text_field& field = anim->text_percent; // 拿到电量图片包含 0 1 2 3 ...... % 等, 就是一个 ascii 组合.
if (field.font == nullptr || field.font->char_width == 0 || field.font->char_height == 0) {
return;
}

// 将电量转换成字符串
std::string str = base::StringPrintf("%d%%", cur_level);

int x, y;
determine_xy(field, str.size(), &x, &y); // 计算显示的位置

// 设置颜色
gr_color(field.color_r, field.color_g, field.color_b, field.color_a);
// 显示电量
gr_text(field.font, x, y_start, str.c_str(), false);
}

二、客制化充电图片

    默认的原图非常丑陋, 因此对充电图片进行客制化. 充电图片的位置在 system\core\healthd\images, 编译后会被打包到 ```res/images/charger`` 目录. 根据前面的显示流程可以知道关键在于 batt_anim_ 它是一个 animation 类型的对象, 保存在charger 对象里面.

1
2
3
4
5
6
class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
......
private:
animation batt_anim_;
......
};

2、 修改 animation 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
diff --git a/healthd/animation.h b/healthd/animation.h
index d02d7a7ea..d735256b9 100644
-- a/healthd/animation.h
+++ b/healthd/animation.h
@@ -23,6 +23,8 @@
class GRSurface;
struct GRFont;

+#define CHARGER_USER_ANIMATION
+
namespace android {

#define CENTER_VAL INT_MAX
@@ -48,11 +50,20 @@ struct animation {
GRFont* font;
};

+ #define USER_IMAGE_NUM 6
+
// When libminui loads PNG images:
// - When treating paths as relative paths, it adds ".png" suffix.
// - When treating paths as absolute paths, it doesn't add the suffix. Hence, the suffix
// is added here.
void set_resource_root(const std::string& root) {
+
+ for (int i=0; i < USER_IMAGE_NUM; i++){
+ if (!usr_animation_file[i].empty()) {
+ usr_animation_file[i] = root + usr_animation_file[i] + ".png";
+ }
+ }
+
if (!animation_file.empty()) {
animation_file = root + animation_file + ".png";
}
@@ -67,6 +78,7 @@ struct animation {
}
}

+ std::string usr_animation_file[USER_IMAGE_NUM];
std::string animation_file;
std::string fail_file;

2、修改 InitAnimation 方法

    在 InitAnimation 方法中获取 png 图片的路径等信息, 默认的图片是 battery_scale.png , 一张图片里面有很多图片合成的, 通过解析一张图片就能得到 6 帧的信息. 我客制化用的图片是分开的6帧, 因此需要在这里逐一添加对应的信息. 代码路径 system/core/healthd/healthd_mode_charger.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index bfecab866..46b96c13a 100644
-- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -58,6 +58,10 @@
#include <health2impl/Health.h>
#include <healthd/healthd.h>

+#ifndef CHARGER_USER_ANIMATION
+#define CHARGER_USER_ANIMATION
+#endif
+
using namespace android;
using android::hardware::Return;
using android::hardware::health::GetHealthServiceOrDefault;
@@ -634,7 +638,17 @@ void Charger::InitAnimation() {
if (!parse_success) {
LOGW("Could not parse animation description. Using default animation.\n");
batt_anim_ = BASE_ANIMATION;
+
+#ifdef CHARGER_USER_ANIMATION
+ batt_anim_.usr_animation_file[0].assign("charger/battery_0");
+ batt_anim_.usr_animation_file[1].assign("charger/battery_1");
+ batt_anim_.usr_animation_file[2].assign("charger/battery_2");
+ batt_anim_.usr_animation_file[3].assign("charger/battery_3");
+ batt_anim_.usr_animation_file[4].assign("charger/battery_4");
+ batt_anim_.usr_animation_file[5].assign("charger/battery_5");
+#else
batt_anim_.animation_file.assign("charger/battery_scale");
+#endif
InitDefaultAnimationFrames();
batt_anim_.frames = owned_frames_.data();
batt_anim_.num_frames = owned_frames_.size();

3、修改 Init 方法

    在 minui 中每一个图像都由一个 GRSurface 描述. init 方法中调用 res_create_display_surface 则用来解析前面配置的 usr_animation_file[i] 对应的目录下的 png 图片并输出 GRSurface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index bfecab866..46b96c13a 100644
-- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -691,6 +705,19 @@ void Charger::Init(struct healthd_config* config) {
}
}

+#ifdef CHARGER_USER_ANIMATION
+ GRSurface* scale_frames[USER_IMAGE_NUM];
+ for(i = 0; i < USER_IMAGE_NUM; i++){
+ ret = res_create_display_surface(batt_anim_.usr_animation_file[i].c_str(), &scale_frames[i]);
+ if (ret < 0) {
+ LOGE("Cannot load custom %s image. Reverting to built in.\n",batt_anim_.usr_animation_file[i].c_str());
+ }else{
+ batt_anim_.frames[i].surface = scale_frames[i];
+ LOGE("file is:[%s],batt_anim_.frames[%d].surface = charger->surf_unknown;\n",
+ batt_anim_.usr_animation_file[i].c_str(),i);
+ }
+ }
+#else
GRSurface** scale_frames;
int scale_count;
int scale_fps; // Not in use (charger/battery_scale doesn't have FPS text
@@ -711,6 +738,8 @@ void Charger::Init(struct healthd_config* config) {
batt_anim_.frames[i].surface = scale_frames[i];
}
}
+#endif

    需要注意的是默认的 minui 只支解析持单通道的 png 因此需要修改. 为我使用的平台是 rk3566, drm 驱动是支持 4 通道的图片显示, 4 通道的图片就是 ARGB 图片. 单通道就是 8 位描述 rgb 信息. 默认的图片就是单通道的灰度图, 直接注释掉就行了.

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/minui/resources.cpp b/minui/resources.cpp
index f635acd1..07bd4f8a 100644
-- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -128,7 +128,7 @@ PngHandler::PngHandler(const std::string& name) {
} else {
fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth_,
channels_, color_type_);
- error_code_ = -7;
+ //error_code_ = -7;
}
}

三、minui 框架

安卓提供的一个进行简单图像显示框架

1、函数接口

1.1 gr_init

  1. 获取系统的显示像素formatdevice/rockchip/common/BoardConfig.mk中的 TARGET_RECOVERY_PIXEL_FORMAT1 配置. (ABGR_8888/RGBX_8888/ARGB_8888/ARGB_8888)
  2. 调用 adf 或 drm 或 fbdev (谁成功用谁), 提供的接口初始化显示接口, 并返回 gr_draw 用来描述显存信息
  3. 获取由device/rockchip/common/device.mk中的TARGET_RECOVERY_OVERSCAN_PERCENT配置的overscan_percent
  4. 初始化硬件 pip
  5. 获取由device/rockchip/common/BoardConfig.mk中的TARGET_RECOVERY_DEFAULT_ROTATION配置的系统的方向rotation_str

1.2 gr_flip

    回调 gr_flip 初始化硬件 pip 并显示 gr_draw.data() 显存中的图片.

1.3 简单通用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 以当前为起点, 增加 x 轴像素指针, 指向下一个像素
static void incr_x(uint32_t** p, int row_pixels);
// 以当前为起点, 向下增加 y 轴像素指针, 即指向下一行像素.
static void incr_y(uint32_t** p, int row_pixels);
// 判断( x, y )是否在屏幕外边, 在屏幕外边返回真
static bool outside(int x, int y);
// 根据方向返回 x , y 处的像素指针
static uint32_t* PixelAt(GRSurface* surface, int x, int y, int row_pixels);

// 返回 surface 的 width
unsigned int gr_get_width(const GRSurface* surface);
// 返回 surface 的 height
unsigned int gr_get_height(const GRSurface* surface);
// 计算字符串显示宽度单位 bit
int gr_measure(const GRFont* font, const char* s);

1.4 gr_color

设置背景画布, 如果 pixel_format 是 ARGB 或者 BGRA 则将 gr_current(背景画布) 统一为 ARGB , 否则转换为 ABGR

1
2
3
4
// r: 背景画布的 r
// g: 背景画布的 g
// b: 背景画布的 b
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);

1.5 gr_blit

将起点为 (sx,sy) 宽高为 (w,h) 的 source 描述原图复制到显存 (dx,dy) 处

1
2
3
4
5
// source: 原图
// sx, sy: 原图起点
// w, h : 原图宽高
// dx, dy: 显存位置
void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);

1.6 gr_clear

清屏, 即将屏幕清理为 gr_current 描述的颜色, 如果是灰度图片则调用 memset 提高效率, 因此在 set_clear 前要 set_color 设置画布的像素颜色 gr_current

1.7 字符串显示接口

1
2
3
// name: 对应的图片的决定路径, 在充电 healthd 中为 res/images/charger
// pSurface : 返回保存图片信息的 GRSurface 对象指针
int gr_init_font(const char* name, GRSurface** pSurface)

    初始化 font 对应的 png, 其实就是一张包含了 assic 码表的图片, 充电百分比显示的数字就是直接解析图片获取的. 但是默认情况只支持单通的黑白图片. 如下图所示, 内容的顺序就是 assic 的顺序

1
static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix);

    图像合成, alpha 表示灰度图像的亮度级别,范围从 0 到 255,其中 0 表示完全黑(最暗),255 表示完全白(最亮),这里的 alpha 被用作亮度级别,而不是真正的透明度。根据 alpha 的亮度级别以及 pix 和 gr_current 的颜色值来生成一个混合后的 RGB 像素.

1
static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels, int width, int height);

     将 src_p 指向的灰度图片, 合成到 dst_p 指向的源图, 图片的宽为 width, 高为 height.

1
void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);

    显示 s 中的字符, 字符的来源为 font->texture->data() 指向的图片, blod 表示是否加粗. texture 就是 gr_init_font 返回的 GRSurface 对象.

四、支持 4通道百分比图片显示

修改 minui 支持 4 通道, 百分比图片显示, 完整 patch

1、修改 graphics.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index d34da567..f11c097f 100644
-- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -139,6 +139,24 @@ static uint32_t* PixelAt(GRSurface* surface, int x, int y, int row_pixels) {
return nullptr;
}

+static void TextBlend_4channel(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
+ int width, int height) {
+
+ for (int j = 0; j < height; ++j) {
+ const uint32_t* sx = reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(src_p));;
+ uint32_t* px = dst_p;
+
+ for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) {
+ *px = *sx ++;
+ }
+
+ src_p += src_row_bytes;
+ incr_y(&dst_p, dst_row_pixels);
+ }
+}
+
static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
int width, int height) {
uint8_t alpha_current = static_cast<uint8_t>((alpha_mask & gr_current) >> 24);
@@ -158,10 +176,12 @@ static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p,
void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) {
if (!font || !font->texture || (gr_current & alpha_mask) == 0) return;

- if (font->texture->pixel_bytes != 1) {
- printf("gr_text: font has wrong format\n");
- return;
- }
+ // if (font->texture->pixel_bytes != 1) {
+ // printf("gr_text: font has wrong format\n");
+ // return;
+ // }

bold = bold && (font->texture->height != font->char_height);

@@ -181,10 +201,17 @@ void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) {
(bold ? font->char_height * font->texture->row_bytes : 0);
uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels);

- TextBlend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+ if (font->texture->pixel_bytes != 1) {
+ TextBlend_4channel(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width / 4,
font->char_height);
-
- x += font->char_width;
+ x += font->char_width / 4;
+ }else{
+ TextBlend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+ font->char_height);
+ x += font->char_width;
+ }
}
}

@@ -329,7 +356,15 @@ int gr_init_font(const char* name, GRFont** dest) {
// The font image should be a 96x2 array of character images. The
// columns are the printable ASCII characters 0x20 - 0x7f. The
// top row is regular text; the bottom row is bold.
- font->char_width = font->texture->width / 96;
+
+ if(font->texture->pixel_bytes != 1){
+ font->char_width = font->texture->width * 4 / 96;
+ }else{
+ font->char_width = font->texture->width / 96;
+ }
+
font->char_height = font->texture->height / 2;

*dest = font;

2、修改 resources.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 07bd4f8a..b47ab335 100644
-- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -153,6 +153,7 @@ static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, in
int width) {
const uint8_t* ip = input_row;
uint8_t* op = output_row;
+ uint8_t a, r, g, b;

switch (channels) {
case 1:
@@ -178,7 +179,18 @@ static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, in

case 4:
// copy RGBA to RGBX
- memcpy(output_row, input_row, width * 4);
+ for (int x = 0; x < width * 4; ++x) {
+ r = *ip++;
+ g = *ip++;
+ b = *ip++;
+ a = *ip++;
+ *op++ = b;
+ *op++ = g;
+ *op++ = r;
+ *op++ = a;
+ }
+
+// memcpy(output_row, input_row, width * 4);
break;
}
}
@@ -299,20 +311,30 @@ exit:

int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
*pSurface = nullptr;
+ std::unique_ptr<GRSurface> surface;

PngHandler png_handler(name);
if (!png_handler) return png_handler.error_code();

- if (png_handler.channels() != 1) {
- return -7;
- }
+ // 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) {
+ if (png_handler.channels() != 1) {
+ surface = GRSurface::Create(width, height, width * 4, 4);
+ }else{
+ surface = GRSurface::Create(width, height, width, 1);
+ }
+
+ if (!surface){
return -8;
}

@@ -321,10 +343,22 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
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);
+
+ if (png_handler.channels() != 1) {
+ 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);
+ }
+ }else{
+ 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();

3. 修改 healthd_draw.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 179a51294..85a88d5eb 100644
-- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -135,9 +135,17 @@ int HealthdDraw::draw_text(const GRFont* font, int x, int y, const char* str) {

void HealthdDraw::determine_xy(const animation::text_field& field,
const int length, int* x, int* y) {
+ int str_len_px;
*x = field.pos_x;

- int str_len_px = length * field.font->char_width;
+ if(field.font->texture->pixel_bytes == 1){
+ str_len_px = length * field.font->char_width;
+ }else{
+ str_len_px = length * field.font->char_width / 4;
+ }
+
if (field.pos_x == CENTER_VAL) {
*x = (screen_width_ - str_len_px) / 2;
} else if (field.pos_x >= 0) {

第如果四张图片不显示, 做如下修改.

1
2
3
4
5
6
7
8
9
10
+++ b/healthd/healthd_mode_charger.cpp
@@ -181,7 +181,7 @@ void Charger::InitDefaultAnimationFrames() {
},
{
.disp_time = 750,
- .min_level = 80,
+ .min_level = 0,
.max_level = 95,
.surface = NULL,
},