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:
@@ -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 {
|
||||||
|
|||||||
@@ -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, рендерит строку
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user