code wiki / _hdl_build / nx_zpng.nx
nx_zpng.nx source
↩ module page · 113 lines · 6110 B
1// nx_zpng.nx -- a COMPRESSING sovereign PNG encoder (the unblocker for shipping generated art at size). Replaces
2// nx_png's STORED (uncompressed ~3B/px) zlib body with a real DEFLATE: PNG "Sub" row filter (flat horizontal
3// spans -> 0x00) + fixed-Huffman blocks + LZ77 RLE (dist=1 back-refs collapse the 0-runs). Reuses nx_png's proven
4// CRC32/Adler32/be32 + PNG framing. Output is standard zlib/DEFLATE -> any browser decodes it. No zlib/libpng.
5// Our generated images (gradients + flat fills) compress massively. license_tier: ORIGINAL
6import "nx_png.nx"
7import "nx_syscalls.nx"
8const K_MAGIC_1024: i64 = 1024
9
10// ---- bit writer: stream packed LSB-first into bytes; st[0]=zpos st[1]=bitbuf st[2]=bitcnt ----
11func zp_putbit(z: *u8, st: *i64, b: i64) -> i64 {
12 st[1] = st[1] | ((b & 1) << st[2]); st[2] = st[2] + 1
13 if st[2] == 8 { z[st[0]] = (st[1] & 255) as u8; st[0] = st[0] + 1; st[1] = 0; st[2] = 0 }
14 return 0
15}
16func zp_lsb(z: *u8, st: *i64, val: i64, n: i64) -> i64 { var i: i64 = 0; while i < n { zp_putbit(z, st, (val >> i) & 1); i = i + 1 } return 0 } // extra bits / header (LSB first)
17func zp_huff(z: *u8, st: *i64, code: i64, n: i64) -> i64 { var i: i64 = n - 1; while i >= 0 { zp_putbit(z, st, (code >> i) & 1); i = i - 1 } return 0 } // Huffman codes (MSB first)
18func zp_flush(z: *u8, st: *i64) -> i64 { if st[2] > 0 { z[st[0]] = (st[1] & 255) as u8; st[0] = st[0] + 1; st[1] = 0; st[2] = 0 } return 0 }
19
20// fixed-Huffman literal/length symbol
21func zp_sym(z: *u8, st: *i64, s: i64) -> i64 {
22 if s <= 143 { zp_huff(z, st, 0x30 + s, 8) }
23 else { if s <= 255 { zp_huff(z, st, 0x190 + (s - 144), 9) }
24 else { if s <= 279 { zp_huff(z, st, s - 256, 7) }
25 else { zp_huff(z, st, 0xC0 + (s - 280), 8) } } }
26 return 0
27}
28// length -> (code, extra-bits, base) into out[0..2]
29func zp_lencode(len: i64, out: *i64) -> i64 {
30 if len <= 10 { out[0] = 257 + (len - 3); out[1] = 0; out[2] = len; return 0 }
31 if len <= 18 { let k: i64 = (len - 11)/2; out[0] = 265 + k; out[1] = 1; out[2] = 11 + k*2; return 0 }
32 if len <= 34 { let k: i64 = (len - 19)/4; out[0] = 269 + k; out[1] = 2; out[2] = 19 + k*4; return 0 }
33 if len <= 66 { let k: i64 = (len - 35)/8; out[0] = 273 + k; out[1] = 3; out[2] = 35 + k*8; return 0 }
34 if len <= 130 { let k: i64 = (len - 67)/16; out[0] = 277 + k; out[1] = 4; out[2] = 67 + k*16; return 0 }
35 if len <= 257 { let k: i64 = (len - 131)/32; out[0] = 281 + k; out[1] = 5; out[2] = 131 + k*32; return 0 }
36 out[0] = 285; out[1] = 0; out[2] = 258; return 0
37}
38// a dist=1 match of length `len` (3..258): length sym + extra, then distance code 0 (5 bits, 0 extra)
39func zp_match1(z: *u8, st: *i64, len: i64, lc: *i64) -> i64 {
40 zp_lencode(len, lc); zp_sym(z, st, lc[0]); zp_lsb(z, st, len - lc[2], lc[1]); zp_huff(z, st, 0, 5); return 0
41}
42// DEFLATE the raw bytes as ONE fixed-Huffman block with RLE (dist=1) for runs. st[0] = current z offset.
43func zp_deflate(raw: *u8, rawlen: i64, z: *u8, st: *i64) -> i64 {
44 let lc: *i64 = sys_mmap(32) as *i64
45 zp_lsb(z, st, 1, 1) // BFINAL = 1
46 zp_lsb(z, st, 1, 2) // BTYPE = 01 (fixed Huffman)
47 var i: i64 = 0
48 while i < rawlen {
49 var run: i64 = 1; var go: i64 = 1
50 while go == 1 { if i + run >= rawlen { go = 0 } else { if raw[i+run] != raw[i] { go = 0 } else { run = run + 1 } } }
51 zp_sym(z, st, raw[i] as i64) // one literal
52 var rem: i64 = run - 1
53 while rem >= 3 { var l: i64 = rem; if l > 258 { l = 258 } zp_match1(z, st, l, lc); rem = rem - l }
54 while rem > 0 { zp_sym(z, st, raw[i] as i64); rem = rem - 1 }
55 i = i + run
56 }
57 zp_sym(z, st, 256) // end of block
58 zp_flush(z, st)
59 return 0
60}
61
62// compressing RGB8 PNG writer (same interface as write_png).
63func write_png_z(fb: *i64, w: i64, h: i64, path: *u8) -> i64 {
64 let rowbytes: i64 = 1 + w * 3
65 let rawlen: i64 = h * rowbytes
66 let raw: *u8 = sys_mmap(rawlen) as *u8
67 var o: i64 = 0; var y: i64 = 0
68 while y < h {
69 raw[o] = 1 as u8; o = o + 1 // filter: Sub
70 var x: i64 = 0
71 while x < w {
72 let c: i64 = fb[y*w + x]
73 var lR: i64 = 0; var lG: i64 = 0; var lB: i64 = 0
74 if x > 0 { let pc: i64 = fb[y*w + x - 1]; lR = pc & 0xff; lG = (pc >> 8) & 0xff; lB = (pc >> 16) & 0xff }
75 raw[o] = ((c & 0xff) - lR) as u8
76 raw[o+1] = (((c >> 8) & 0xff) - lG) as u8
77 raw[o+2] = (((c >> 16) & 0xff) - lB) as u8
78 o = o + 3; x = x + 1
79 }
80 y = y + 1
81 }
82 let zcap: i64 = rawlen * 2 + K_MAGIC_1024
83 let z: *u8 = sys_mmap(zcap) as *u8
84 z[0] = 0x78 as u8; z[1] = 0x01 as u8
85 let st: *i64 = sys_mmap(32) as *i64; st[0] = 2; st[1] = 0; st[2] = 0
86 zp_deflate(raw, rawlen, z, st)
87 var zo: i64 = st[0]
88 png_be32(z, zo, png_adler32(raw, 0, rawlen)); zo = zo + 4
89 let zlen: i64 = zo
90 let pcap: i64 = 8 + 25 + 12 + zlen + 32
91 let p: *u8 = sys_mmap(pcap) as *u8
92 p[0]=137 as u8; p[1]=80 as u8; p[2]=78 as u8; p[3]=71 as u8; p[4]=13 as u8; p[5]=10 as u8; p[6]=26 as u8; p[7]=10 as u8
93 var po: i64 = 8
94 png_be32(p, po, 13); po = po + 4
95 let ih: i64 = po
96 p[po]=73 as u8; p[po+1]=72 as u8; p[po+2]=68 as u8; p[po+3]=82 as u8; po = po + 4
97 png_be32(p, po, w); po = po + 4; png_be32(p, po, h); po = po + 4
98 p[po]=8 as u8; p[po+1]=2 as u8; p[po+2]=0 as u8; p[po+3]=0 as u8; p[po+4]=0 as u8; po = po + 5
99 png_be32(p, po, png_crc32(p, ih, 17)); po = po + 4
100 png_be32(p, po, zlen); po = po + 4
101 let id: i64 = po
102 p[po]=73 as u8; p[po+1]=68 as u8; p[po+2]=65 as u8; p[po+3]=84 as u8; po = po + 4
103 var j: i64 = 0; while j < zlen { p[po + j] = z[j]; j = j + 1 } po = po + zlen
104 png_be32(p, po, png_crc32(p, id, 4 + zlen)); po = po + 4
105 png_be32(p, po, 0); po = po + 4
106 let ie: i64 = po
107 p[po]=73 as u8; p[po+1]=69 as u8; p[po+2]=78 as u8; p[po+3]=68 as u8; po = po + 4
108 png_be32(p, po, png_crc32(p, ie, 4)); po = po + 4
109 let fd: i64 = sys_openat_wr(path, 0x1a4)
110 if fd < 0 { return 0 - 1 }
111 sys_write(fd, p, po); sys_close(fd)
112 return po
113}