nx_paint_fb_ascii_dump.nx source
↩ module page · 88 lines · 3529 B
1// nx_paint_fb_ascii_dump.nx -- dump a Framebuffer to ASCII-art bytes
2// in a caller-supplied output buffer. Phase 5 first primitive of
3// NISHI_BROWSER_ROADMAP. Lets the bits-up substrate produce
4// VISIBLE output we can read in a terminal -- closes the demo gap.
5//
6// Char selection (5-level luminance bucket per pixel):
7// alpha == 0 -> ' ' (transparent)
8// luminance 0..63 (with alpha) -> '.'
9// luminance 64..127 -> '+'
10// luminance 128..191 -> 'o'
11// luminance 192..255 -> '#'
12//
13// Luminance computed as a simple sum of the three channels divided
14// by 3 (close-enough for ASCII art; not the perceptual Rec.601
15// weights). Integer-only; no FP.
16//
17// Output layout: WIDTH chars per row followed by '\n', repeated for
18// every row. Total output bytes = (width + 1) * height.
19//
20// What it does NOT handle yet (Phase 5b queued, explicit in header):
21// - Unicode block characters (▀ ▄ █ ░ ▒ ▓) for richer 2:1 ratio
22// - ANSI color escape codes for true-color terminals
23// - Multi-pixel-per-cell averaging (Phase 5b "half-block" mode
24// gets twice the vertical resolution by using ▀ + bg/fg color)
25// - Perceptual Rec.601 luminance weights (0.299, 0.587, 0.114)
26//
27// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims:
28// gap list is EXACT in header.
29//
30// genealogy_id: classical_ascii_art_dither + nishi_substrate_phase_5
31// lineage_id: nishi_browser_fb_ascii_dump_v1
32
33import "nx_syscalls.nx"
34import "nx_paint_solid_rect.nx"
35
36// ---- helpers ----
37
38// Map a 4-byte RGBA pixel to a single ASCII char.
39func _fb_ascii_char_for_pixel(r: i64, g: i64, b: i64, a: i64) -> i64 {
40 if a == 0 { return 32 } // ' '
41 let lum: i64 = (r + g + b) / 3
42 if lum < 64 { return 46 } // '.'
43 if lum < 128 { return 43 } // '+'
44 if lum < 192 { return 111 } // 'o'
45 return 35 // '#'
46}
47
48// ---- public API ----
49
50// Write the framebuffer to `out_buf` as ASCII art. Returns the
51// number of bytes written, or -1 if `max_out_bytes` is too small.
52// Required output size = (fb.width + 1) * fb.height.
53func nx_paint_fb_ascii_dump(fb: *Framebuffer,
54 out_buf: *u8,
55 max_out_bytes: i64) -> i64 {
56 let needed: i64 = (fb.width + 1) * fb.height
57 if needed > max_out_bytes { return -1 }
58 var out_pos: i64 = 0
59 var y: i64 = 0
60 while y < fb.height {
61 var x: i64 = 0
62 while x < fb.width {
63 let off: i64 = (y * fb.width + x) * 4
64 let r: i64 = (fb.pixels[off + 0] as i64) & 255
65 let g: i64 = (fb.pixels[off + 1] as i64) & 255
66 let b: i64 = (fb.pixels[off + 2] as i64) & 255
67 let a: i64 = (fb.pixels[off + 3] as i64) & 255
68 out_buf[out_pos] = _fb_ascii_char_for_pixel(r, g, b, a) as u8
69 out_pos = out_pos + 1
70 x = x + 1
71 }
72 out_buf[out_pos] = 10 as u8 // '\n'
73 out_pos = out_pos + 1
74 y = y + 1
75 }
76 return out_pos
77}
78
79// Convenience: dump framebuffer DIRECTLY to stdout via sys_write.
80// Returns bytes written or -1 on error.
81func nx_paint_fb_ascii_write(fb: *Framebuffer) -> i64 {
82 let needed: i64 = (fb.width + 1) * fb.height
83 let scratch: *u8 = (sys_mmap(needed)) as *u8
84 let n: i64 = nx_paint_fb_ascii_dump(fb, scratch, needed)
85 if n < 0 { return -1 }
86 sys_write(1, scratch, n)
87 return n
88}