diff options
| author | gbc dev <gbc@localhost> | 2026-07-18 20:41:19 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-18 20:41:19 +0200 |
| commit | fa1579cc9f209dbeb14ab277dc9c4ac741ace57c (patch) | |
| tree | 7ca130f0d360e93460de2cae71daeb97c3819d0f | |
| parent | main: allow --sock (control/debug socket) in headless mode (diff) | |
| download | sl0pboy-fa1579cc9f209dbeb14ab277dc9c4ac741ace57c.tar.gz sl0pboy-fa1579cc9f209dbeb14ab277dc9c4ac741ace57c.tar.xz sl0pboy-fa1579cc9f209dbeb14ab277dc9c4ac741ace57c.zip | |
render: --shrink N - downscale the half-block live display (1-4)
Decimates the LCD N x N before the half-block encode so the monitor pane
takes 160/N cols x 72/N rows. Display-only: capture, recording and sixel
still read the full-res framebuffer.
Decimation is max-luma pick, not box-average: terminal fonts are 1px
bright strokes on dark background, and averaging dims them into grey
mush (the 'blurry font' regression). Keeping the brightest source pixel
of each block preserves stroke brightness and hue.
| -rw-r--r-- | src/main.c | 5 | ||||
| -rw-r--r-- | src/render.c | 45 | ||||
| -rw-r--r-- | src/render.h | 1 |
3 files changed, 48 insertions, 3 deletions
@@ -76,6 +76,8 @@ static void print_usage(FILE *out, const char *prog) { " --sixel [scale] sixel graphics output; scale = pixel zoom 1-6 (def 2)\n" " --chrome draw a Game Boy body around the LCD (sixel display)\n" " --palette NAME recolor the LCD: dmg|green (pea green), pocket|gray\n" +" --shrink N shrink the half-block display N x (1-4); capture and\n" +" recording stay full-res\n" " --green alias for --palette dmg\n" " --frameskip N draw 1 of every (N+1) frames\n" "\n" @@ -172,6 +174,9 @@ int main(int argc, char **argv) { } else if (!strcmp(a, "--sixel")) { sixel = true; if (has_val) sixel_scale = atoi(argv[++i]); + } else if (!strcmp(a, "--shrink")) { + if (!has_val) { fprintf(stderr, "%s: --shrink needs a value\n", prog); return 1; } + render_set_shrink(atoi(argv[++i])); } else if (!strcmp(a, "--chrome")) { chrome = true; } else if (!strcmp(a, "--palette")) { diff --git a/src/render.c b/src/render.c index 4f612ae..1f232d8 100644 --- a/src/render.c +++ b/src/render.c @@ -477,6 +477,24 @@ const u8 *render_capture_frame(GB *gb, int *w, int *h) { return &fb[0][0][0]; } +// ---- half-block live-display downscale (--shrink N) ------------------------ +// Purely a *display* concern: the LCD is decimated N x N before the +// half-block encode, so the pane takes 160/N cols x 72/N rows. Capture, +// recording and sixel are untouched (they read the full-res framebuffer). +// Decimation is MAX-LUMA PICK, not box-average: terminal fonts are 1px +// bright strokes on dark background, and averaging dims them into grey +// mush. Keeping the brightest source pixel of each block preserves both +// stroke brightness and hue (it's always a real pixel, never a blend). +static int shrink = 1; +static u8 shrink_fb[SCREEN_H][SCREEN_W][3]; + +void render_set_shrink(int n) { + if (n < 1) n = 1; + if (n > 4) n = 4; + shrink = n; + resized = 1; // force a clear + full repaint +} + void render_frame(GB *gb) { if (sixel_enabled) { if (chrome_enabled) render_frame_chrome(gb); @@ -485,6 +503,27 @@ void render_frame(GB *gb) { } PPU *p = &gb->ppu; u8 (*fb)[SCREEN_W][3] = lcd_fb(p); + int W = SCREEN_W, H = SCREEN_H; + if (shrink > 1) { + W = SCREEN_W / shrink; + H = (SCREEN_H / shrink) & ~1; // even, so rows pair into cells + for (int y = 0; y < H; y++) + for (int x = 0; x < W; x++) { + u8 *best = fb[y*shrink][x*shrink]; + int bl = -1; + for (int dy = 0; dy < shrink; dy++) + for (int dx = 0; dx < shrink; dx++) { + u8 *s = fb[y*shrink + dy][x*shrink + dx]; + // integer luma: 2R + 5G + B (~BT.601) + int l = 2*s[0] + 5*s[1] + s[2]; + if (l > bl) { bl = l; best = s; } + } + shrink_fb[y][x][0] = best[0]; + shrink_fb[y][x][1] = best[1]; + shrink_fb[y][x][2] = best[2]; + } + fb = shrink_fb; + } char *o = outbuf; if (resized) { resized = 0; need_full = true; o += sprintf(o, "\x1b[2J"); } @@ -496,9 +535,9 @@ void render_frame(GB *gb) { int PR = -1, PG = -1, PB = -1; // last-emitted bg int cur_row = -2, cur_col = -2; // terminal cell just written (0-indexed) - for (int y = 0; y < SCREEN_H; y += 2) { + for (int y = 0; y < H; y += 2) { int row = y / 2; - for (int x = 0; x < SCREEN_W; x++) { + for (int x = 0; x < W; x++) { u8 *top = fb[y][x]; u8 *bot = fb[y+1][x]; if (!need_full) { @@ -528,7 +567,7 @@ void render_frame(GB *gb) { if (need_full) { o += sprintf(o, "\x1b[0m\x1b[%d;1H\x1b[38;2;150;150;150m %.*s " "skip:%d%s [f]ast [ ][ ] q:quit\x1b[K", - SCREEN_H/2 + 1, 12, gb->cart.title, + H/2 + 1, 12, gb->cart.title, hud_frameskip, hud_turbo ? " TURBO" : ""); } diff --git a/src/render.h b/src/render.h index 1be1290..fed4ec8 100644 --- a/src/render.h +++ b/src/render.h @@ -11,6 +11,7 @@ void render_set_hud(int frameskip, bool turbo); // update HUD readout // Switch terminal output to sixel graphics (scale = integer pixel zoom, >=1). // Pass on=false to use the default Unicode half-block renderer. void render_set_sixel(bool on, int scale); +void render_set_shrink(int n); // half-block display downscale (1-4, def 1) // Draw a Game Boy body/frame around the LCD (sixel output only); buttons on the // drawn shell light up while pressed. No effect unless sixel mode is enabled. void render_set_chrome(bool on); |
