Files
video-setup/dev/cli/xorg_cli.c
mikael-lovqvists-claude-agent 611376dbc1 feat: xorg text overlays, font atlas generator, v4l2_view_cli
- tools/gen_font_atlas: Python/Pillow build tool — skyline packs DejaVu
  Sans glyphs 32-255 into a grayscale atlas, emits build/gen/font_atlas.h
  with pixel data and Font_Glyph[256] metrics table
- xorg: bitmap font atlas text overlay rendering (GL_R8 atlas texture,
  alpha-blended glyph quads, dark background rect per overlay)
- xorg: add xorg_viewer_set_overlay_text / clear_overlays API
- xorg: add xorg_viewer_handle_events for streaming use (events only,
  no redundant render)
- xorg_cli: show today's date as white text overlay
- v4l2_view_cli: new tool — V4L2 capture with format auto-selection
  (highest FPS then largest resolution), MJPEG/YUYV, measured FPS overlay
- docs: update README, planning, architecture to reflect current status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:13:59 +00:00

137 lines
4.8 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "test_image.h"
#include "xorg.h"
static void usage(void)
{
fprintf(stderr,
"usage: xorg_cli [--pattern bars|ramp|grid]\n"
" [--width N] [--height N]\n"
" [--format yuv420|bgra]\n"
" [--scale stretch|fit|fill|1:1]\n"
" [--anchor center|topleft]\n"
" [--x N] [--y N]\n"
"\n"
"Opens a window and renders a test image using the xorg viewer sink.\n"
"Q or Escape closes the window.\n"
"\n"
"defaults: bars 1280x720 yuv420 stretch center at 0,0\n");
}
int main(int argc, char **argv)
{
Test_Pattern pattern = TEST_PATTERN_BARS;
Test_Fmt fmt = TEST_FMT_YUV420;
Xorg_Scale scale = XORG_SCALE_STRETCH;
Xorg_Anchor anchor = XORG_ANCHOR_CENTER;
int width = 1280;
int height = 720;
int win_x = 0;
int win_y = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--pattern") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "bars") == 0) { pattern = TEST_PATTERN_BARS; }
else if (strcmp(argv[i], "ramp") == 0) { pattern = TEST_PATTERN_RAMP; }
else if (strcmp(argv[i], "grid") == 0) { pattern = TEST_PATTERN_GRID; }
else { fprintf(stderr, "unknown pattern: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--width") == 0 && i + 1 < argc) {
width = atoi(argv[++i]);
} else if (strcmp(argv[i], "--height") == 0 && i + 1 < argc) {
height = atoi(argv[++i]);
} else if (strcmp(argv[i], "--x") == 0 && i + 1 < argc) {
win_x = atoi(argv[++i]);
} else if (strcmp(argv[i], "--y") == 0 && i + 1 < argc) {
win_y = atoi(argv[++i]);
} else if (strcmp(argv[i], "--scale") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "stretch") == 0) { scale = XORG_SCALE_STRETCH; }
else if (strcmp(argv[i], "fit") == 0) { scale = XORG_SCALE_FIT; }
else if (strcmp(argv[i], "fill") == 0) { scale = XORG_SCALE_FILL; }
else if (strcmp(argv[i], "1:1") == 0) { scale = XORG_SCALE_1_1; }
else { fprintf(stderr, "unknown scale: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--anchor") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "center") == 0) { anchor = XORG_ANCHOR_CENTER; }
else if (strcmp(argv[i], "topleft") == 0) { anchor = XORG_ANCHOR_TOP_LEFT; }
else { fprintf(stderr, "unknown anchor: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "yuv420") == 0) { fmt = TEST_FMT_YUV420; }
else if (strcmp(argv[i], "bgra") == 0) { fmt = TEST_FMT_BGRA; }
else { fprintf(stderr, "unknown format: %s\n", argv[i]); usage(); return 1; }
} else {
usage(); return 1;
}
}
if (!xorg_available()) {
fprintf(stderr, "xorg_cli: built without HAVE_GLFW — viewer not available\n");
return 1;
}
if (width < 2 || height < 2) {
fprintf(stderr, "xorg_cli: width and height must be >= 2\n");
return 1;
}
Test_Frame *f = test_image_alloc(width, height, fmt);
if (!f) {
fprintf(stderr, "xorg_cli: allocation failed\n");
return 1;
}
test_image_generate(f, pattern);
const char *pat_name = pattern == TEST_PATTERN_BARS ? "bars"
: pattern == TEST_PATTERN_RAMP ? "ramp"
: "grid";
const char *fmt_name = fmt == TEST_FMT_YUV420 ? "yuv420" : "bgra";
const char *scale_name = scale == XORG_SCALE_STRETCH ? "stretch"
: scale == XORG_SCALE_FIT ? "fit"
: scale == XORG_SCALE_FILL ? "fill"
: "1:1";
const char *anchor_name = anchor == XORG_ANCHOR_CENTER ? "center" : "topleft";
printf("opening %dx%d %s %s scale=%s anchor=%s at (%d,%d)\n",
width, height, fmt_name, pat_name, scale_name, anchor_name, win_x, win_y);
Xorg_Viewer *v = xorg_viewer_open(win_x, win_y, width, height, "xorg_cli");
if (!v) {
fprintf(stderr, "xorg_cli: failed to open viewer window\n");
test_image_free(f);
return 1;
}
xorg_viewer_set_scale(v, scale);
xorg_viewer_set_anchor(v, anchor);
/* Overlay: today's date, white text, top-left corner. */
{
char date_buf[32];
time_t now = time(NULL);
struct tm *tm = localtime(&now);
strftime(date_buf, sizeof(date_buf), "%Y-%m-%d", tm);
xorg_viewer_set_overlay_text(v, 0, 10, 10, date_buf, 1.0f, 1.0f, 1.0f);
}
if (fmt == TEST_FMT_YUV420) {
xorg_viewer_push_yuv420(v,
f->plane[0], f->plane[1], f->plane[2],
f->width, f->height);
} else {
xorg_viewer_push_bgra(v, f->plane[0], f->width, f->height);
}
test_image_free(f);
while (xorg_viewer_poll(v)) { /* wait for window close */ }
xorg_viewer_close(v);
return 0;
}