nx_png.nx source
↩ module page · 268 lines · 10261 B
1// nx_png.nx -- SOVEREIGN PNG (RGB8) encoder in NishiLang. No zlib/libpng/python: adaptive per-scanline
2// FILTERING (None/Sub/Up/Avg/Paeth, min-abs-sum heuristic) + REAL DEFLATE compression (nx_deflate_enc, LZ77 +
3// fixed Huffman) -- so renders ship compact + full-resolution. CRC32 (chunk) + Adler32 (zlib) pure integer.
4// Upgraded 2026-07-04 from stored (uncompressed) blocks; same write_png() contract, valid PNG out, much smaller.
5// license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_deflate_enc.nx"
8const K_MAGIC_65521: i64 = 65521
9const K_MAGIC_65536: i64 = 65536
10
11func png_crc32(buf: *u8, off: i64, len: i64) -> i64 {
12 var crc: i64 = 0xFFFFFFFF
13 var i: i64 = 0
14 while i < len {
15 crc = crc ^ (buf[off + i] as i64)
16 var k: i64 = 0
17 while k < 8 {
18 let m: i64 = 0 - (crc & 1)
19 crc = ((crc >> 1) ^ (0xEDB88320 & m)) & 0xFFFFFFFF
20 k = k + 1
21 }
22 i = i + 1
23 }
24 return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF
25}
26func png_adler32(buf: *u8, off: i64, len: i64) -> i64 {
27 var s1: i64 = 1
28 var s2: i64 = 0
29 var i: i64 = 0
30 while i < len {
31 s1 = (s1 + (buf[off + i] as i64)) % K_MAGIC_65521
32 s2 = (s2 + s1) % K_MAGIC_65521
33 i = i + 1
34 }
35 return s2 * K_MAGIC_65536 + s1
36}
37func png_be32(b: *u8, o: i64, v: i64) -> i64 {
38 b[o] = ((v >> 24) & 0xff) as u8
39 b[o + 1] = ((v >> 16) & 0xff) as u8
40 b[o + 2] = ((v >> 8) & 0xff) as u8
41 b[o + 3] = (v & 0xff) as u8
42 return 0
43}
44func png_paeth(a: i64, b: i64, c: i64) -> i64 { // a=left b=up c=upleft
45 let p: i64 = a + b - c
46 var pa: i64 = p - a
47 if pa < 0 { pa = 0 - pa }
48 var pb: i64 = p - b
49 if pb < 0 { pb = 0 - pb }
50 var pc: i64 = p - c
51 if pc < 0 { pc = 0 - pc }
52 if pa <= pb { if pa <= pc { return a } }
53 if pb <= pc { return b }
54 return c
55}
56func png_sabs(v: i64) -> i64 { var x: i64 = v & 0xff; if x > 127 { x = 256 - x } return x }
57func png_g(img: *u8, w3: i64, r: i64, j: i64) -> i64 { // safe fetch, 0 outside bounds
58 if r < 0 { return 0 }
59 if j < 0 { return 0 }
60 return img[r * w3 + j] as i64
61}
62
63// fb = w*h packed R + G*256 + B*65536. Writes a compressed RGB8 PNG to path.
64func write_png(fb: *i64, w: i64, h: i64, path: *u8) -> i64 {
65 let w3: i64 = w * 3
66 // 1) unfiltered RGB image bytes
67 let img: *u8 = sys_mmap(h * w3) as *u8
68 var y: i64 = 0
69 while y < h {
70 var x: i64 = 0
71 while x < w {
72 let c: i64 = fb[y * w + x]
73 img[y * w3 + x * 3] = (c & 0xff) as u8
74 img[y * w3 + x * 3 + 1] = ((c >> 8) & 0xff) as u8
75 img[y * w3 + x * 3 + 2] = ((c >> 16) & 0xff) as u8
76 x = x + 1
77 }
78 y = y + 1
79 }
80 // 2) adaptive per-row filtering -> raw (filter byte + residuals per row)
81 let rowbytes: i64 = 1 + w3
82 let rawlen: i64 = h * rowbytes
83 let raw: *u8 = sys_mmap(rawlen) as *u8
84 var o: i64 = 0
85 y = 0
86 while y < h {
87 // pick the filter (0..4) with least sum of signed-abs residuals
88 var bestft: i64 = 0
89 var bestcost: i64 = 0 - 1
90 var ft: i64 = 0
91 while ft < 5 {
92 var cost: i64 = 0
93 var j: i64 = 0
94 while j < w3 {
95 let xb: i64 = png_g(img, w3, y, j)
96 let left: i64 = png_g(img, w3, y, j - 3)
97 let up: i64 = png_g(img, w3, y - 1, j)
98 let ul: i64 = png_g(img, w3, y - 1, j - 3)
99 var res: i64 = xb
100 if ft == 1 { res = xb - left }
101 if ft == 2 { res = xb - up }
102 if ft == 3 { res = xb - (left + up) / 2 }
103 if ft == 4 { res = xb - png_paeth(left, up, ul) }
104 cost = cost + png_sabs(res)
105 j = j + 1
106 }
107 if bestcost < 0 { bestcost = cost; bestft = ft } else { if cost < bestcost { bestcost = cost; bestft = ft } }
108 ft = ft + 1
109 }
110 raw[o] = bestft as u8
111 o = o + 1
112 var j2: i64 = 0
113 while j2 < w3 {
114 let xb: i64 = png_g(img, w3, y, j2)
115 let left: i64 = png_g(img, w3, y, j2 - 3)
116 let up: i64 = png_g(img, w3, y - 1, j2)
117 let ul: i64 = png_g(img, w3, y - 1, j2 - 3)
118 var res: i64 = xb
119 if bestft == 1 { res = xb - left }
120 if bestft == 2 { res = xb - up }
121 if bestft == 3 { res = xb - (left + up) / 2 }
122 if bestft == 4 { res = xb - png_paeth(left, up, ul) }
123 raw[o] = (res & 0xff) as u8
124 o = o + 1
125 j2 = j2 + 1
126 }
127 y = y + 1
128 }
129 // 3) zlib: header (0x78 0x01) + DEFLATE(raw) + adler32(raw)
130 let zcap: i64 = rawlen + rawlen / 2 + 128
131 let z: *u8 = sys_mmap(zcap) as *u8
132 z[0] = 0x78 as u8
133 z[1] = 0x01 as u8
134 let body: *u8 = (z as i64 + 2) as *u8
135 let clen: i64 = dfe_compress(raw, rawlen, body)
136 var zo: i64 = 2 + clen
137 png_be32(z, zo, png_adler32(raw, 0, rawlen))
138 zo = zo + 4
139 let zlen: i64 = zo
140 // PNG file: sig + IHDR + IDAT + IEND, each chunk CRC32'd over type+data.
141 let pcap: i64 = 8 + 25 + 12 + zlen + 32
142 let p: *u8 = sys_mmap(pcap) as *u8
143 p[0] = 137 as u8; p[1] = 80 as u8; p[2] = 78 as u8; p[3] = 71 as u8
144 p[4] = 13 as u8; p[5] = 10 as u8; p[6] = 26 as u8; p[7] = 10 as u8
145 var po: i64 = 8
146 png_be32(p, po, 13)
147 po = po + 4
148 let ih: i64 = po
149 p[po] = 73 as u8; p[po + 1] = 72 as u8; p[po + 2] = 68 as u8; p[po + 3] = 82 as u8 // IHDR
150 po = po + 4
151 png_be32(p, po, w); po = po + 4
152 png_be32(p, po, h); po = po + 4
153 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
154 po = po + 5
155 png_be32(p, po, png_crc32(p, ih, 17)); po = po + 4
156 png_be32(p, po, zlen); po = po + 4
157 let id: i64 = po
158 p[po] = 73 as u8; p[po + 1] = 68 as u8; p[po + 2] = 65 as u8; p[po + 3] = 84 as u8 // IDAT
159 po = po + 4
160 var j: i64 = 0
161 while j < zlen { p[po + j] = z[j]; j = j + 1 }
162 po = po + zlen
163 png_be32(p, po, png_crc32(p, id, 4 + zlen)); po = po + 4
164 png_be32(p, po, 0); po = po + 4
165 let ie: i64 = po
166 p[po] = 73 as u8; p[po + 1] = 69 as u8; p[po + 2] = 78 as u8; p[po + 3] = 68 as u8 // IEND
167 po = po + 4
168 png_be32(p, po, png_crc32(p, ie, 4)); po = po + 4
169 let fd: i64 = sys_openat_wr(path, 0x1a4)
170 if fd < 0 { return 0 - 1 }
171 sys_write(fd, p, po)
172 sys_close(fd)
173 return 0
174}
175
176// same encoder as write_png but into a caller buffer `out` (size >= h*(1+w*3)*3/2 + 256); returns byte length.
177// For serving a PNG over HTTP without a temp file. Additive; write_png unchanged.
178func write_png_buf(fb: *i64, w: i64, h: i64, out: *u8) -> i64 {
179 let w3: i64 = w * 3
180 let img: *u8 = sys_mmap(h * w3) as *u8
181 var y: i64 = 0
182 while y < h {
183 var x: i64 = 0
184 while x < w {
185 let c: i64 = fb[y * w + x]
186 img[y * w3 + x * 3] = (c & 0xff) as u8
187 img[y * w3 + x * 3 + 1] = ((c >> 8) & 0xff) as u8
188 img[y * w3 + x * 3 + 2] = ((c >> 16) & 0xff) as u8
189 x = x + 1
190 }
191 y = y + 1
192 }
193 let rowbytes: i64 = 1 + w3
194 let rawlen: i64 = h * rowbytes
195 let raw: *u8 = sys_mmap(rawlen) as *u8
196 var o: i64 = 0
197 y = 0
198 while y < h {
199 var bestft: i64 = 0
200 var bestcost: i64 = 0 - 1
201 var ft: i64 = 0
202 while ft < 5 {
203 var cost: i64 = 0
204 var j: i64 = 0
205 while j < w3 {
206 let xb: i64 = png_g(img, w3, y, j)
207 let left: i64 = png_g(img, w3, y, j - 3)
208 let up: i64 = png_g(img, w3, y - 1, j)
209 let ul: i64 = png_g(img, w3, y - 1, j - 3)
210 var res: i64 = xb
211 if ft == 1 { res = xb - left }
212 if ft == 2 { res = xb - up }
213 if ft == 3 { res = xb - (left + up) / 2 }
214 if ft == 4 { res = xb - png_paeth(left, up, ul) }
215 cost = cost + png_sabs(res)
216 j = j + 1
217 }
218 if bestcost < 0 { bestcost = cost; bestft = ft } else { if cost < bestcost { bestcost = cost; bestft = ft } }
219 ft = ft + 1
220 }
221 raw[o] = bestft as u8; o = o + 1
222 var j2: i64 = 0
223 while j2 < w3 {
224 let xb: i64 = png_g(img, w3, y, j2)
225 let left: i64 = png_g(img, w3, y, j2 - 3)
226 let up: i64 = png_g(img, w3, y - 1, j2)
227 let ul: i64 = png_g(img, w3, y - 1, j2 - 3)
228 var res: i64 = xb
229 if bestft == 1 { res = xb - left }
230 if bestft == 2 { res = xb - up }
231 if bestft == 3 { res = xb - (left + up) / 2 }
232 if bestft == 4 { res = xb - png_paeth(left, up, ul) }
233 raw[o] = (res & 0xff) as u8; o = o + 1
234 j2 = j2 + 1
235 }
236 y = y + 1
237 }
238 let zcap: i64 = rawlen + rawlen / 2 + 128
239 let z: *u8 = sys_mmap(zcap) as *u8
240 z[0] = 0x78 as u8; z[1] = 0x01 as u8
241 let body: *u8 = (z as i64 + 2) as *u8
242 let clen: i64 = dfe_compress(raw, rawlen, body)
243 var zo: i64 = 2 + clen
244 png_be32(z, zo, png_adler32(raw, 0, rawlen)); zo = zo + 4
245 let zlen: i64 = zo
246 out[0]=137 as u8; out[1]=80 as u8; out[2]=78 as u8; out[3]=71 as u8
247 out[4]=13 as u8; out[5]=10 as u8; out[6]=26 as u8; out[7]=10 as u8
248 var po: i64 = 8
249 png_be32(out, po, 13); po = po + 4
250 let ih: i64 = po
251 out[po]=73 as u8; out[po+1]=72 as u8; out[po+2]=68 as u8; out[po+3]=82 as u8; po = po + 4
252 png_be32(out, po, w); po = po + 4
253 png_be32(out, po, h); po = po + 4
254 out[po]=8 as u8; out[po+1]=2 as u8; out[po+2]=0 as u8; out[po+3]=0 as u8; out[po+4]=0 as u8; po = po + 5
255 png_be32(out, po, png_crc32(out, ih, 17)); po = po + 4
256 png_be32(out, po, zlen); po = po + 4
257 let id: i64 = po
258 out[po]=73 as u8; out[po+1]=68 as u8; out[po+2]=65 as u8; out[po+3]=84 as u8; po = po + 4
259 var j: i64 = 0
260 while j < zlen { out[po + j] = z[j]; j = j + 1 }
261 po = po + zlen
262 png_be32(out, po, png_crc32(out, id, 4 + zlen)); po = po + 4
263 png_be32(out, po, 0); po = po + 4
264 let ie: i64 = po
265 out[po]=73 as u8; out[po+1]=69 as u8; out[po+2]=78 as u8; out[po+3]=68 as u8; po = po + 4
266 png_be32(out, po, png_crc32(out, ie, 4)); po = po + 4
267 return po
268}