nx_png_write.nx source
↩ module page · 111 lines · 4497 B
1// nx_png_write.nx -- sovereign PNG ENCODER so the Nishi ecosystem can output a render that any viewer
2// (and Claude's image reader) can SEE -- closing the "can't inspect our own output" gap. 8-bit RGB,
3// filter 0, zlib STORED blocks (no compression -> trivial + correct; a real DEFLATE pass is a perf
4// rung). Self-contained: inline CRC-32 (IEEE) for chunks + Adler-32 for the zlib stream. license_tier: ORIGINAL
5import "nx_syscalls.nx"
6const K_MAGIC_65521: i64 = 65521
7const K_MAGIC_65534: i64 = 65534
8const K_MAGIC_65535: i64 = 65535
9
10func pw_u32be(fd: i64, v: i64) -> i64 {
11 let b: *u8 = sys_mmap(4)
12 b[0]=((v>>24)&255) as u8; b[1]=((v>>16)&255) as u8; b[2]=((v>>8)&255) as u8; b[3]=(v&255) as u8
13 sys_write(fd, b, 4); return 0
14}
15// CRC-32 (IEEE) over buf[off..off+len), continuing from `crc` (NOT finalized).
16func pw_crc_block(crc: i64, buf: *u8, off: i64, len: i64) -> i64 {
17 var c: i64 = crc
18 var i: i64 = 0
19 while i < len {
20 c = c ^ (buf[off+i] & 0xff)
21 var k: i64 = 0
22 while k < 8 {
23 let m: i64 = c & 1
24 c = c >> 1
25 if m == 1 { c = c ^ 0xEDB88320 }
26 k = k + 1
27 }
28 i = i + 1
29 }
30 return c
31}
32func pw_adler32(buf: *u8, off: i64, len: i64) -> i64 {
33 var a: i64 = 1
34 var b: i64 = 0
35 var i: i64 = 0
36 while i < len {
37 a = (a + (buf[off+i] & 0xff)) % K_MAGIC_65521
38 b = (b + a) % K_MAGIC_65521
39 i = i + 1
40 }
41 return (b << 16) | a
42}
43// write a chunk (type 4 bytes + data) with length + CRC.
44func pw_chunk(fd: i64, typ: *u8, data: *u8, dlen: i64) -> i64 {
45 pw_u32be(fd, dlen)
46 sys_write(fd, typ, 4)
47 if dlen > 0 { sys_write(fd, data, dlen) }
48 var crc: i64 = pw_crc_block(0xFFFFFFFF, typ, 0, 4)
49 if dlen > 0 { crc = pw_crc_block(crc, data, 0, dlen) }
50 pw_u32be(fd, crc ^ 0xFFFFFFFF)
51 return 0
52}
53
54// Write an 8-bit RGB image (rgb = w*h*3 bytes, row-major top-down) as a PNG file. Returns 0 ok.
55func nx_png_write_rgb(path: *u8, rgb: *u8, w: i64, h: i64) -> i64 {
56 let fd: i64 = sys_openat_wr(path, 420)
57 if fd < 0 { return 0 - 1 }
58 // signature
59 let sig: *u8 = sys_mmap(8)
60 sig[0]=137 as u8; sig[1]=80 as u8; sig[2]=78 as u8; sig[3]=71 as u8; sig[4]=13 as u8; sig[5]=10 as u8; sig[6]=26 as u8; sig[7]=10 as u8
61 sys_write(fd, sig, 8)
62 // IHDR
63 let ihdr: *u8 = sys_mmap(13)
64 ihdr[0]=((w>>24)&255) as u8; ihdr[1]=((w>>16)&255) as u8; ihdr[2]=((w>>8)&255) as u8; ihdr[3]=(w&255) as u8
65 ihdr[4]=((h>>24)&255) as u8; ihdr[5]=((h>>16)&255) as u8; ihdr[6]=((h>>8)&255) as u8; ihdr[7]=(h&255) as u8
66 ihdr[8]=8 as u8 // bit depth
67 ihdr[9]=2 as u8 // color type 2 = truecolor RGB
68 ihdr[10]=0 as u8 // compression
69 ihdr[11]=0 as u8 // filter
70 ihdr[12]=0 as u8 // interlace
71 pw_chunk(fd, "IHDR\x00" as *u8, ihdr, 13)
72 // filtered scanlines: each row = filter(0) + w*3 RGB
73 let rowbytes: i64 = w * 3
74 let raw_len: i64 = h * (1 + rowbytes)
75 let raw: *u8 = sys_mmap(raw_len + 16)
76 var y: i64 = 0
77 var rp: i64 = 0
78 while y < h {
79 raw[rp] = 0 as u8; rp = rp + 1
80 var x: i64 = 0
81 let srow: i64 = y * rowbytes
82 while x < rowbytes { raw[rp] = rgb[srow + x]; rp = rp + 1; x = x + 1 }
83 y = y + 1
84 }
85 // zlib STORED stream: 0x78 0x01 + blocks(<=65535) + adler32
86 let nblk: i64 = (raw_len + K_MAGIC_65534) / K_MAGIC_65535
87 let zlen: i64 = 2 + nblk * 5 + raw_len + 4
88 let z: *u8 = sys_mmap(zlen + 16)
89 z[0]=0x78 as u8; z[1]=0x01 as u8
90 var zp: i64 = 2
91 var pos: i64 = 0
92 while pos < raw_len {
93 var blen: i64 = raw_len - pos
94 if blen > K_MAGIC_65535 { blen = K_MAGIC_65535 }
95 var last: i64 = 0
96 if pos + blen >= raw_len { last = 1 }
97 z[zp] = last as u8; zp = zp + 1 // BFINAL/BTYPE=00
98 z[zp] = (blen & 255) as u8; z[zp+1] = ((blen>>8)&255) as u8; zp = zp + 2 // LEN (LE)
99 let nl: i64 = (blen ^ 0xFFFF) & 0xFFFF
100 z[zp] = (nl & 255) as u8; z[zp+1] = ((nl>>8)&255) as u8; zp = zp + 2 // NLEN
101 var i: i64 = 0
102 while i < blen { z[zp] = raw[pos + i]; zp = zp + 1; i = i + 1 }
103 pos = pos + blen
104 }
105 let ad: i64 = pw_adler32(raw, 0, raw_len)
106 z[zp]=((ad>>24)&255) as u8; z[zp+1]=((ad>>16)&255) as u8; z[zp+2]=((ad>>8)&255) as u8; z[zp+3]=(ad&255) as u8; zp = zp + 4
107 pw_chunk(fd, "IDAT\x00" as *u8, z, zp)
108 pw_chunk(fd, "IEND\x00" as *u8, sig, 0)
109 sys_close(fd)
110 return 0
111}