nx_apng_write.nx source
↩ module page · 119 lines · 4861 B
1// nx_apng_write.nx -- sovereign APNG (animated PNG) ENCODER: the test-evidence VIDEO-CLIP container
2// (operator: "video clips of the functionality being tested"). APNG plays natively in every browser
3// (grounded: knowledge/fetched/ev_apng.raw), so an evidence page just embeds the .png and it MOVES.
4// Built ON nx_png_write's proven chunk/CRC/adler primitives (imported, not duplicated). Full-frame
5// animation (blend SOURCE, dispose NONE), 8-bit RGB, zlib STORED (correct-first; deflate = a perf rung).
6// Structure: sig + IHDR + acTL(nframes,loops) + [fcTL + IDAT] (frame0) + [fcTL + fdAT]* + IEND, with the
7// shared fcTL/fdAT sequence counter the spec requires. license_tier: ORIGINAL
8import "nx_png_write.nx"
9const NX_MAGIC_65535: i64 = 65535
10
11struct ApngState {
12 fd: i64,
13 w: i64,
14 h: i64,
15 seq: i64, // shared fcTL/fdAT sequence counter
16 frame_idx: i64,
17 delay_num: i64,
18 delay_den: i64
19}
20const NX_APNG_STATE_BYTES: i64 = 56
21
22func ap_be32(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>24)&255) as u8; b[o+1]=((v>>16)&255) as u8; b[o+2]=((v>>8)&255) as u8; b[o+3]=(v&255) as u8; return 0 }
23func ap_be16(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>8)&255) as u8; b[o+1]=(v&255) as u8; return 0 }
24
25// open the clip: writes signature + IHDR + acTL. delay = delay_num/delay_den seconds per frame.
26func apng_open(st: *ApngState, path: *u8, w: i64, h: i64, nframes: i64, delay_num: i64, delay_den: i64) -> i64 {
27 let fd: i64 = sys_openat_wr(path, 420)
28 if fd < 0 { return 0 - 1 }
29 st.fd = fd
30 st.w = w
31 st.h = h
32 st.seq = 0
33 st.frame_idx = 0
34 st.delay_num = delay_num
35 st.delay_den = delay_den
36 let sig: *u8 = sys_mmap(8)
37 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
38 sys_write(fd, sig, 8)
39 let ihdr: *u8 = sys_mmap(13)
40 ap_be32(ihdr, 0, w)
41 ap_be32(ihdr, 4, h)
42 ihdr[8]=8 as u8; ihdr[9]=2 as u8; ihdr[10]=0 as u8; ihdr[11]=0 as u8; ihdr[12]=0 as u8
43 pw_chunk(fd, "IHDR\x00" as *u8, ihdr, 13)
44 let actl: *u8 = sys_mmap(8)
45 ap_be32(actl, 0, nframes)
46 ap_be32(actl, 4, 0) // num_plays 0 = loop forever
47 pw_chunk(fd, "acTL\x00" as *u8, actl, 8)
48 return 0
49}
50
51// build the zlib STORED stream for an RGB frame into z; returns zlen. (filter-0 rows, same as nx_png_write.)
52func ap_zstream(rgb: *u8, w: i64, h: i64, z: *u8) -> i64 {
53 let rowbytes: i64 = w * 3
54 let raw_len: i64 = h * (1 + rowbytes)
55 let raw: *u8 = sys_mmap(raw_len + 16)
56 var y: i64 = 0
57 var rp: i64 = 0
58 while y < h {
59 raw[rp] = 0 as u8; rp = rp + 1
60 var x: i64 = 0
61 let srow: i64 = y * rowbytes
62 while x < rowbytes { raw[rp] = rgb[srow + x]; rp = rp + 1; x = x + 1 }
63 y = y + 1
64 }
65 z[0]=0x78 as u8; z[1]=0x01 as u8
66 var zp: i64 = 2
67 var pos: i64 = 0
68 while pos < raw_len {
69 var blen: i64 = raw_len - pos
70 if blen > NX_MAGIC_65535 { blen = NX_MAGIC_65535 }
71 var last: i64 = 0
72 if pos + blen >= raw_len { last = 1 }
73 z[zp] = last as u8; zp = zp + 1
74 z[zp] = (blen & 255) as u8; z[zp+1] = ((blen>>8)&255) as u8; zp = zp + 2
75 let nl: i64 = (blen ^ 0xFFFF) & 0xFFFF
76 z[zp] = (nl & 255) as u8; z[zp+1] = ((nl>>8)&255) as u8; zp = zp + 2
77 var i: i64 = 0
78 while i < blen { z[zp] = raw[pos + i]; zp = zp + 1; i = i + 1 }
79 pos = pos + blen
80 }
81 let ad: i64 = pw_adler32(raw, 0, raw_len)
82 ap_be32(z, zp, ad)
83 return zp + 4
84}
85
86// append one full frame (rgb = w*h*3 top-down). Frame 0 -> fcTL+IDAT; later -> fcTL+fdAT(seq||zstream).
87func apng_frame(st: *ApngState, rgb: *u8) -> i64 {
88 let fctl: *u8 = sys_mmap(26)
89 ap_be32(fctl, 0, st.seq); st.seq = st.seq + 1
90 ap_be32(fctl, 4, st.w)
91 ap_be32(fctl, 8, st.h)
92 ap_be32(fctl, 12, 0)
93 ap_be32(fctl, 16, 0)
94 ap_be16(fctl, 20, st.delay_num)
95 ap_be16(fctl, 22, st.delay_den)
96 fctl[24]=0 as u8 // dispose NONE
97 fctl[25]=0 as u8 // blend SOURCE
98 pw_chunk(st.fd, "fcTL\x00" as *u8, fctl, 26)
99 let zcap: i64 = st.h * (1 + st.w*3) + (st.h * (1 + st.w*3))/NX_MAGIC_65535*5 + 64
100 if st.frame_idx == 0 {
101 let z: *u8 = sys_mmap(zcap)
102 let zl: i64 = ap_zstream(rgb, st.w, st.h, z)
103 pw_chunk(st.fd, "IDAT\x00" as *u8, z, zl)
104 } else {
105 let zf: *u8 = sys_mmap(zcap + 8)
106 ap_be32(zf, 0, st.seq); st.seq = st.seq + 1
107 let zl2: i64 = ap_zstream(rgb, st.w, st.h, ((zf as i64)+4) as *u8)
108 pw_chunk(st.fd, "fdAT\x00" as *u8, zf, zl2 + 4)
109 }
110 st.frame_idx = st.frame_idx + 1
111 return 0
112}
113
114func apng_close(st: *ApngState) -> i64 {
115 let e: *u8 = sys_mmap(4)
116 pw_chunk(st.fd, "IEND\x00" as *u8, e, 0)
117 sys_close(st.fd)
118 return 0
119}