nx_f32_image_to_bmp.nx source
↩ module page · 128 lines · 5311 B
1// nx_f32_image_to_bmp.nx -- sovereign 24-bit BMP writer: f32 CHW RGB [0,1] -> a Windows-viewable .bmp file.
2//
3// The pipeline's missing last mile: every media path (image/video-frame/try-on/decode) ends in an f32 RGB
4// buffer that nothing turned into a viewable file. This writes a standard 24-bit BMP (BM header + BGR,
5// bottom-up, rows padded to 4B). f32->byte is done sovereignly (no f32->int intrinsic exists): clamp[0,1],
6// *255, +0.5, then 128..1 binary decomposition via nx_f32_lt. Gated by re-reading the written file.
7// license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_le.nx"
10import "nx_f32.nx"
11import "nx_f32_div.nx"
12import "nx_f32_cvt.nx"
13const K_MAGIC_2835: i64 = 2835
14
15// f32 in [0,1] -> byte in [0,255] (clamped, rounded). No f32->int intrinsic; decompose the scaled value.
16func img_f32_to_byte(v: i64) -> i64 {
17 if (v & 0x80000000) != 0 { return 0 } // negative -> 0
18 var x: i64 = nx_f32_add(nx_f32_mul(v, nx_i32_to_f32(255)), 0x3F000000) // v*255 + 0.5
19 var b: i64 = 0
20 var p: i64 = 128
21 while p >= 1 {
22 let pf: i64 = nx_i32_to_f32(p)
23 if nx_f32_lt(x, pf) == 0 { // x >= p
24 x = nx_f32_sub(x, pf)
25 b = b + p
26 }
27 p = p / 2
28 }
29 if b > 255 { b = 255 }
30 return b
31}
32
33// rgb: f32 CHW [3,H,W] in [0,1]. Writes a 24-bit BMP to path. Returns 0 ok.
34func nx_f32_chw_to_bmp(rgb: *i64, H: i64, W: i64, path: *u8) -> i64 {
35 let plane: i64 = H * W
36 let row_bytes: i64 = W * 3
37 let pad: i64 = (4 - (row_bytes - (row_bytes / 4) * 4)) - (((4 - (row_bytes - (row_bytes / 4) * 4)) / 4) * 4)
38 let row_padded: i64 = row_bytes + pad
39 let img_size: i64 = row_padded * H
40 let file_size: i64 = 54 + img_size
41
42 let hdr: *u8 = sys_mmap(54)
43 hdr[0] = 0x42 // 'B'
44 hdr[1] = 0x4D // 'M'
45 nx_le_write_u32(hdr, 2, file_size)
46 nx_le_write_u32(hdr, 6, 0)
47 nx_le_write_u32(hdr, 10, 54) // pixel data offset
48 nx_le_write_u32(hdr, 14, 40) // info header size
49 nx_le_write_u32(hdr, 18, W)
50 nx_le_write_u32(hdr, 22, H)
51 nx_le_write_u16(hdr, 26, 1) // planes
52 nx_le_write_u16(hdr, 28, 24) // bpp
53 nx_le_write_u32(hdr, 30, 0) // compression = BI_RGB
54 nx_le_write_u32(hdr, 34, img_size)
55 nx_le_write_u32(hdr, 38, K_MAGIC_2835) // x pixels/meter (~72dpi)
56 nx_le_write_u32(hdr, 42, K_MAGIC_2835)
57 nx_le_write_u32(hdr, 46, 0)
58 nx_le_write_u32(hdr, 50, 0)
59
60 let body: *u8 = sys_mmap(img_size)
61 var y: i64 = 0
62 while y < H {
63 let src_y: i64 = H - 1 - y // BMP is bottom-up
64 var x: i64 = 0
65 while x < W {
66 let r: i64 = img_f32_to_byte(rgb[0 * plane + src_y * W + x])
67 let g: i64 = img_f32_to_byte(rgb[1 * plane + src_y * W + x])
68 let bl: i64 = img_f32_to_byte(rgb[2 * plane + src_y * W + x])
69 let off: i64 = y * row_padded + x * 3
70 body[off] = bl // BGR order
71 body[off + 1] = g
72 body[off + 2] = r
73 x = x + 1
74 }
75 y = y + 1
76 }
77
78 let fd: i64 = sys_openat_wr(path, 0x1a4)
79 if fd < 0 { return 1 }
80 sys_write(fd, hdr, 54)
81 sys_write(fd, body, img_size)
82 sys_close(fd)
83 return 0
84}
85
86func main() -> i64 {
87 // gate: 2x2 known image -> write -> re-read, verify magic + size + a pixel.
88 let g: *i64 = sys_mmap(3 * 4 * 8) as *i64
89 let one: i64 = nx_i32_to_f32(1)
90 // pixel (0,0) pure red: R=1,G=0,B=0
91 g[0 * 4 + 0] = one; g[1 * 4 + 0] = 0; g[2 * 4 + 0] = 0
92 var i: i64 = 1
93 while i < 4 { g[0 * 4 + i] = 0; g[1 * 4 + i] = 0; g[2 * 4 + i] = 0; i = i + 1 }
94 if nx_f32_chw_to_bmp(g, 2, 2, "/tmp/nx_bmp_gate.bmp" as *u8) != 0 { return 10 }
95 // re-read: file should be 54 + row_padded(8)*2 = 70 bytes; magic 'BM'; bottom row first -> (src_y=1) is black, top row (0,0) red is at the SECOND row in file
96 let rfd: i64 = sys_openat_rd("/tmp/nx_bmp_gate.bmp" as *u8)
97 if rfd < 0 { return 11 }
98 let rb: *u8 = sys_mmap(128)
99 let n: i64 = sys_read(rfd, rb, 128)
100 sys_close(rfd)
101 if n != 70 { return 12 }
102 if rb[0] != 0x42 { return 13 }
103 if rb[1] != 0x4D { return 14 }
104 if nx_le_read_u32(rb, 2) != 70 { return 15 }
105 // top-left red pixel is in the LAST row of pixel data (bottom-up): row1 starts at 54+8=62; B,G,R = 0,0,255
106 if rb[62] != 0 { return 16 }
107 if rb[63] != 0 { return 17 }
108 if rb[64] != 255 { return 18 }
109
110 // deliverable: a real 160x120 gradient image the operator can open
111 let Wd: i64 = 160
112 let Hd: i64 = 120
113 let img: *i64 = sys_mmap(3 * Wd * Hd * 8) as *i64
114 let plane: i64 = Wd * Hd
115 var y: i64 = 0
116 while y < Hd {
117 var x: i64 = 0
118 while x < Wd {
119 img[0 * plane + y * Wd + x] = nx_f32_div(nx_i32_to_f32(x), nx_i32_to_f32(Wd - 1)) // R ramp ->x
120 img[1 * plane + y * Wd + x] = nx_f32_div(nx_i32_to_f32(y), nx_i32_to_f32(Hd - 1)) // G ramp ->y
121 img[2 * plane + y * Wd + x] = nx_f32_div(nx_i32_to_f32(1), nx_i32_to_f32(2)) // B = 0.5
122 x = x + 1
123 }
124 y = y + 1
125 }
126 if nx_f32_chw_to_bmp(img, Hd, Wd, "/mnt/c/Users/elder/nishi_first_image.bmp" as *u8) != 0 { return 20 }
127 return 0
128}