code wiki / (root) / nx_paint_fb_ascii_dump.nx

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}