Phase 11b: подложка для text overlay + сразу-видимый placeholder

User: "у него есть полупрозрачная подложка? у него правильный z-order?"

Z-order был ок (overlays draw'аются после layout.render). Но без bg
текст плохо читался на пёстром кадре — и до прихода MQTT overlay был
visible=0 (не виден вообще).

Изменения:
  - overlay.h: cfc_overlay_text_config_t расширена bg_alpha / bg_y/u/v / bg_pad.
    bg_alpha=0 — фон отключён (default).
  - overlay.c draw_text: если bg_alpha>0, перед blit'ом текста рисуем fill
    rect (atlas_w+2*pad) × (atlas_h+2*pad) с заданным цветом и alpha.
  - overlay.c update_text: пробрасывает bg-поля при апдейте.
  - mqtt_overlay: MqttOverlayCfg + JSON loader научились читать bg_alpha,
    bg_y/u/v, bg_pad, placeholder. Default bg = чёрный 160 alpha, pad 10.
  - MqttOverlayItem::start: overlay сразу visible=1 с placeholder (default "—"),
    reposition_overlay вызывается до получения MQTT — placeholder
    позиционируется в anchor сразу.

User'у теперь видна тёмная подложка с текстом в правом-нижнем углу даже
если sensor молчит.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 10:19:29 +01:00
parent 75271436f7
commit 88fa73f922
4 changed files with 69 additions and 4 deletions
@@ -50,6 +50,16 @@ struct MqttOverlayCfg {
int r = 255, g = 255, b = 255; int r = 255, g = 255, b = 255;
int alpha = 230; int alpha = 230;
std::string font_path = "/fonts/DejaVuSans-Bold.ttf"; std::string font_path = "/fonts/DejaVuSans-Bold.ttf";
/* Полупрозрачная подложка. bg_alpha=0 → отключено. */
int bg_alpha = 160;
int bg_y = 16, bg_u = 128, bg_v = 128; /* по умолчанию чёрный */
int bg_pad = 10;
/* Что показывать пока нет MQTT-данных. Пусто → overlay невидим до
* первого сообщения. По умолчанию "—" чтобы было видно что overlay
* жив, но данные ещё не пришли. */
std::string placeholder = "";
}; };
struct MqttBrokerCfg { struct MqttBrokerCfg {
+8
View File
@@ -105,6 +105,14 @@ typedef struct cfc_overlay_text_config {
int r, g, b; /* sRGB цвет 0..255 */ int r, g, b; /* sRGB цвет 0..255 */
int extra_alpha; /* 0..255 общий множитель прозрачности */ int extra_alpha; /* 0..255 общий множитель прозрачности */
int visible; /* 0/1 — выводить ли */ int visible; /* 0/1 — выводить ли */
/* Опциональный полупрозрачный фон (подложка) под текстом.
* bg_alpha = 0 → без фона (default)
* bg_alpha > 0 → fill rect (atlas_w + 2*bg_pad) × (atlas_h + 2*bg_pad)
* с цветом bg_y/u/v перед blit'ом текста.
* bg_pad чётный, default 8 если bg_alpha>0 и bg_pad==0. */
int bg_alpha; /* 0..255 (0 = отключено) */
int bg_y, bg_u, bg_v; /* BT.709 limited (Y=16..235, UV=16..240) */
int bg_pad; /* px padding вокруг текста */
} cfc_overlay_text_config_t; } cfc_overlay_text_config_t;
/* Создать TEXT overlay. Открывает font через FreeType, рендерит строку /* Создать TEXT overlay. Открывает font через FreeType, рендерит строку
+24 -4
View File
@@ -105,6 +105,9 @@ void MqttOverlayItem::update_text(const std::string& text)
tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b; tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b;
tc.extra_alpha = cfg_.alpha; tc.extra_alpha = cfg_.alpha;
tc.visible = 1; tc.visible = 1;
tc.bg_alpha = cfg_.bg_alpha;
tc.bg_y = cfg_.bg_y; tc.bg_u = cfg_.bg_u; tc.bg_v = cfg_.bg_v;
tc.bg_pad = cfg_.bg_pad;
cfc_overlay_update_text(overlay_, &tc); cfc_overlay_update_text(overlay_, &tc);
reposition_overlay(); reposition_overlay();
std::fprintf(stderr, "[cfc/mqtt-overlay/%s] '%s'\n", std::fprintf(stderr, "[cfc/mqtt-overlay/%s] '%s'\n",
@@ -149,26 +152,36 @@ void MqttOverlayItem::reposition_overlay()
tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b; tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b;
tc.extra_alpha = cfg_.alpha; tc.extra_alpha = cfg_.alpha;
tc.visible = 1; tc.visible = 1;
tc.bg_alpha = cfg_.bg_alpha;
tc.bg_y = cfg_.bg_y; tc.bg_u = cfg_.bg_u; tc.bg_v = cfg_.bg_v;
tc.bg_pad = cfg_.bg_pad;
cfc_overlay_update_text(overlay_, &tc); cfc_overlay_update_text(overlay_, &tc);
} }
bool MqttOverlayItem::start() bool MqttOverlayItem::start()
{ {
/* Persistent text overlay. */ /* Persistent text overlay — сразу visible=1 с placeholder, чтобы было
* видно (с подложкой) даже без MQTT-сообщения. */
const std::string ph = cfg_.placeholder.empty() ? std::string("") : cfg_.placeholder;
cfc_overlay_text_config_t tc{}; cfc_overlay_text_config_t tc{};
tc.font_path = cfg_.font_path.c_str(); tc.font_path = cfg_.font_path.c_str();
tc.text = ""; tc.text = ph.c_str();
tc.pixel_size = cfg_.pixel_size; tc.pixel_size = cfg_.pixel_size;
tc.x = 0; tc.y = 0; tc.x = cfg_.margin_x; tc.y = cfg_.margin_y;
tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b; tc.r = cfg_.r; tc.g = cfg_.g; tc.b = cfg_.b;
tc.extra_alpha = cfg_.alpha; tc.extra_alpha = cfg_.alpha;
tc.visible = 0; tc.visible = 1;
tc.bg_alpha = cfg_.bg_alpha;
tc.bg_y = cfg_.bg_y; tc.bg_u = cfg_.bg_u; tc.bg_v = cfg_.bg_v;
tc.bg_pad = cfg_.bg_pad;
if (cfc_overlay_create_text(&tc, &overlay_) != 0) { if (cfc_overlay_create_text(&tc, &overlay_) != 0) {
std::fprintf(stderr, "[cfc/mqtt-overlay/%s] create_text failed (font '%s')\n", std::fprintf(stderr, "[cfc/mqtt-overlay/%s] create_text failed (font '%s')\n",
cfg_.id.c_str(), cfg_.font_path.c_str()); cfg_.id.c_str(), cfg_.font_path.c_str());
return false; return false;
} }
cfc_overlay_set_id(overlay_, cfg_.id.c_str()); cfc_overlay_set_id(overlay_, cfg_.id.c_str());
last_text_ = ph;
reposition_overlay(); /* поставить в anchor сразу */
/* MQTT subscriber. */ /* MQTT subscriber. */
char cid[64]; char cid[64];
@@ -255,6 +268,13 @@ int MqttOverlayManager::load_from_file(const std::string& path, int W, int H)
cfg.margin_y = jint(jo, "margin_y", 24); cfg.margin_y = jint(jo, "margin_y", 24);
cfg.pixel_size = jint(jo, "pixel_size", 32); cfg.pixel_size = jint(jo, "pixel_size", 32);
cfg.alpha = jint(jo, "alpha", 230); cfg.alpha = jint(jo, "alpha", 230);
cfg.bg_alpha = jint(jo, "bg_alpha", 160);
cfg.bg_y = jint(jo, "bg_y", 16);
cfg.bg_u = jint(jo, "bg_u", 128);
cfg.bg_v = jint(jo, "bg_v", 128);
cfg.bg_pad = jint(jo, "bg_pad", 10);
const char* ph = jstr(jo, "placeholder", "");
if (*ph) cfg.placeholder = ph;
const char* fp = jstr(jo, "font_path", ""); const char* fp = jstr(jo, "font_path", "");
if (*fp) cfg.font_path = fp; if (*fp) cfg.font_path = fp;
+27
View File
@@ -537,6 +537,11 @@ int cfc_overlay_update_text(cfc_overlay_t *ov,
td->cfg.y = cfg->y; td->cfg.y = cfg->y;
td->cfg.extra_alpha = cfg->extra_alpha ? cfg->extra_alpha : 255; td->cfg.extra_alpha = cfg->extra_alpha ? cfg->extra_alpha : 255;
td->cfg.visible = cfg->visible; td->cfg.visible = cfg->visible;
td->cfg.bg_alpha = cfg->bg_alpha;
td->cfg.bg_y = cfg->bg_y;
td->cfg.bg_u = cfg->bg_u;
td->cfg.bg_v = cfg->bg_v;
td->cfg.bg_pad = cfg->bg_pad;
if (need_rebuild) return text_rebuild_atlas(td); if (need_rebuild) return text_rebuild_atlas(td);
return 0; return 0;
@@ -563,6 +568,28 @@ static int draw_text(cfc_overlay_t *ov, CUstream stream,
int x = t->cfg.x & ~1; int x = t->cfg.x & ~1;
int y = t->cfg.y & ~1; int y = t->cfg.y & ~1;
/* Опциональный фон-подложка (для читаемости текста на любом фоне). */
if (t->cfg.bg_alpha > 0) {
int pad = t->cfg.bg_pad > 0 ? t->cfg.bg_pad : 8;
pad &= ~1;
int bx = x - pad, by = y - pad;
int bw = t->width + 2 * pad, bh = t->height + 2 * pad;
if (bx < 0) { bw += bx; bx = 0; }
if (by < 0) { bh += by; by = 0; }
if (bx + bw > frame_w) bw = frame_w - bx;
if (by + bh > frame_h) bh = frame_h - by;
bx &= ~1; by &= ~1; bw &= ~1; bh &= ~1;
if (bw > 0 && bh > 0) {
int by_v = t->cfg.bg_y ? t->cfg.bg_y : 16;
int bu_v = t->cfg.bg_u ? t->cfg.bg_u : 128;
int bv_v = t->cfg.bg_v ? t->cfg.bg_v : 128;
cfc_cugrid_fill_nv12(stream, dst_y, pitch_y, dst_uv, pitch_uv,
bx, by, bw, bh,
by_v, bu_v, bv_v, t->cfg.bg_alpha);
}
}
return cfc_cugrid_blit_rgba_nv12( return cfc_cugrid_blit_rgba_nv12(
stream, stream,
dst_y, pitch_y, dst_uv, pitch_uv, dst_y, pitch_y, dst_uv, pitch_uv,