code wiki / (root) / nx_apng.nx

nx_apng.nx source

↩ module page · 162 lines · 7301 B

1// nx_apng.nx -- SOVEREIGN ANIMATED PNG (APNG) encoder: our PNG codec extended with the animation-control chunks 2// (acTL / fcTL / fdAT) so a sequence of framebuffers becomes ONE browser-native looping "video" file -- no 3// 3rd-party video encoder. Each frame is filtered + DEFLATE-compressed exactly like a PNG IDAT (reuses the 4// nx_deflate_enc + the standard PNG scanline filters). This is the swap+motion S3 "show it in a VIDEO" surface; 5// the real inter-frame vcodec (nx_video_codec_wasm) is the production rung. Incremental API: apng_open -> 6// apng_frame x N -> apng_close. All integer, deterministic. license_tier: ORIGINAL 7import "nx_syscalls.nx" 8import "nx_deflate_enc.nx" 9const K_MAGIC_65521: i64 = 65521 10const K_MAGIC_65536: i64 = 65536 11 12func ap_crc32(buf: *u8, off: i64, len: i64) -> i64 { 13 var crc: i64 = 0xFFFFFFFF 14 var i: i64 = 0 15 while i < len { 16 crc = crc ^ (buf[off + i] as i64) 17 var k: i64 = 0 18 while k < 8 { let m: i64 = 0 - (crc & 1); crc = ((crc >> 1) ^ (0xEDB88320 & m)) & 0xFFFFFFFF; k = k + 1 } 19 i = i + 1 20 } 21 return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF 22} 23func ap_adler32(buf: *u8, off: i64, len: i64) -> i64 { 24 var s1: i64 = 1; var s2: i64 = 0; var i: i64 = 0 25 while i < len { s1 = (s1 + (buf[off + i] as i64)) % K_MAGIC_65521; s2 = (s2 + s1) % K_MAGIC_65521; i = i + 1 } 26 return s2 * K_MAGIC_65536 + s1 27} 28func ap_be32(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>24)&0xff) as u8; b[o+1]=((v>>16)&0xff) as u8; b[o+2]=((v>>8)&0xff) as u8; b[o+3]=(v&0xff) as u8; return 0 } 29func ap_be16(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>8)&0xff) as u8; b[o+1]=(v&0xff) as u8; return 0 } 30func ap_paeth(a: i64, b: i64, c: i64) -> i64 { 31 let p: i64 = a + b - c 32 var pa: i64 = p - a; if pa < 0 { pa = 0 - pa } 33 var pb: i64 = p - b; if pb < 0 { pb = 0 - pb } 34 var pc: i64 = p - c; if pc < 0 { pc = 0 - pc } 35 if pa <= pb { if pa <= pc { return a } } 36 if pb <= pc { return b } 37 return c 38} 39func ap_sabs(v: i64) -> i64 { var x: i64 = v & 0xff; if x > 127 { x = 256 - x } return x } 40func ap_g(img: *u8, w3: i64, r: i64, j: i64) -> i64 { if r < 0 { return 0 } if j < 0 { return 0 } return img[r * w3 + j] as i64 } 41 42// framebuffer (w*h packed RGB in i64) -> zlib stream into z. Returns zlen. Mirrors nx_png write_png steps 1-3 43// (the sovereign PNG path); kept local so nx_png's file writer stays untouched (2 copies, under the DRY-extract 44// threshold). z must be sized >= h*(1+w*3)*3/2 + 256. 45func ap_fb_to_zlib(fb: *i64, w: i64, h: i64, z: *u8) -> i64 { 46 let w3: i64 = w * 3 47 let img: *u8 = sys_mmap(h * w3) as *u8 48 var y: i64 = 0 49 while y < h { 50 var x: i64 = 0 51 while x < w { 52 let c: i64 = fb[y * w + x] 53 img[y * w3 + x * 3] = (c & 0xff) as u8 54 img[y * w3 + x * 3 + 1] = ((c >> 8) & 0xff) as u8 55 img[y * w3 + x * 3 + 2] = ((c >> 16) & 0xff) as u8 56 x = x + 1 57 } 58 y = y + 1 59 } 60 let rowbytes: i64 = 1 + w3 61 let rawlen: i64 = h * rowbytes 62 let raw: *u8 = sys_mmap(rawlen) as *u8 63 var o: i64 = 0 64 y = 0 65 while y < h { 66 var bestft: i64 = 0 67 var bestcost: i64 = 0 - 1 68 var ft: i64 = 0 69 while ft < 5 { 70 var cost: i64 = 0 71 var j: i64 = 0 72 while j < w3 { 73 let xb: i64 = ap_g(img, w3, y, j) 74 let left: i64 = ap_g(img, w3, y, j - 3) 75 let up: i64 = ap_g(img, w3, y - 1, j) 76 let ul: i64 = ap_g(img, w3, y - 1, j - 3) 77 var res: i64 = xb 78 if ft == 1 { res = xb - left } 79 if ft == 2 { res = xb - up } 80 if ft == 3 { res = xb - (left + up) / 2 } 81 if ft == 4 { res = xb - ap_paeth(left, up, ul) } 82 cost = cost + ap_sabs(res) 83 j = j + 1 84 } 85 if bestcost < 0 { bestcost = cost; bestft = ft } else { if cost < bestcost { bestcost = cost; bestft = ft } } 86 ft = ft + 1 87 } 88 raw[o] = bestft as u8; o = o + 1 89 var j2: i64 = 0 90 while j2 < w3 { 91 let xb: i64 = ap_g(img, w3, y, j2) 92 let left: i64 = ap_g(img, w3, y, j2 - 3) 93 let up: i64 = ap_g(img, w3, y - 1, j2) 94 let ul: i64 = ap_g(img, w3, y - 1, j2 - 3) 95 var res: i64 = xb 96 if bestft == 1 { res = xb - left } 97 if bestft == 2 { res = xb - up } 98 if bestft == 3 { res = xb - (left + up) / 2 } 99 if bestft == 4 { res = xb - ap_paeth(left, up, ul) } 100 raw[o] = (res & 0xff) as u8; o = o + 1 101 j2 = j2 + 1 102 } 103 y = y + 1 104 } 105 z[0] = 0x78 as u8; z[1] = 0x01 as u8 106 let body: *u8 = (z as i64 + 2) as *u8 107 let clen: i64 = dfe_compress(raw, rawlen, body) 108 var zo: i64 = 2 + clen 109 ap_be32(z, zo, ap_adler32(raw, 0, rawlen)); zo = zo + 4 110 return zo 111} 112 113// write a chunk: len + type(a,b,c,d) + data + crc. Returns new offset. 114func ap_chunk(p: *u8, o: i64, a: i64, b: i64, c: i64, d: i64, data: *u8, dlen: i64) -> i64 { 115 ap_be32(p, o, dlen); var w: i64 = o + 4 116 let ts: i64 = w 117 p[w] = a as u8; p[w+1] = b as u8; p[w+2] = c as u8; p[w+3] = d as u8; w = w + 4 118 var i: i64 = 0 119 while i < dlen { p[w + i] = data[i]; i = i + 1 } 120 w = w + dlen 121 ap_be32(p, w, ap_crc32(p, ts, 4 + dlen)); w = w + 4 122 return w 123} 124 125// sig + IHDR + acTL. Returns offset. num_plays=0 => loop forever. 126func apng_open(p: *u8, w: i64, h: i64, nframes: i64) -> i64 { 127 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 128 var o: i64 = 8 129 let ihdr: *u8 = sys_mmap(16) 130 ap_be32(ihdr, 0, w); ap_be32(ihdr, 4, h); 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 131 o = ap_chunk(p, o, 73, 72, 68, 82, ihdr, 13) // IHDR 132 let actl: *u8 = sys_mmap(16) 133 ap_be32(actl, 0, nframes); ap_be32(actl, 4, 0) 134 o = ap_chunk(p, o, 97, 99, 84, 76, actl, 8) // acTL 135 return o 136} 137// one frame: fcTL (+ IDAT if first, else fdAT). seqp = running sequence counter. delay_cs = frame delay in 1/100s. 138func apng_frame(p: *u8, o: i64, seqp: *i64, fb: *i64, w: i64, h: i64, delay_cs: i64, first: i64) -> i64 { 139 let fctl: *u8 = sys_mmap(32) 140 ap_be32(fctl, 0, seqp[0]); ap_be32(fctl, 4, w); ap_be32(fctl, 8, h); ap_be32(fctl, 12, 0); ap_be32(fctl, 16, 0) 141 ap_be16(fctl, 20, delay_cs); ap_be16(fctl, 22, 100); fctl[24]=0 as u8; fctl[25]=0 as u8 // dispose=none, blend=source 142 var oo: i64 = ap_chunk(p, o, 102, 99, 84, 76, fctl, 26) // fcTL 143 seqp[0] = seqp[0] + 1 144 let zcap: i64 = h * (1 + w * 3) * 3 / 2 + 256 145 let z: *u8 = sys_mmap(zcap) 146 let zlen: i64 = ap_fb_to_zlib(fb, w, h, z) 147 if first == 1 { 148 oo = ap_chunk(p, oo, 73, 68, 65, 84, z, zlen) // IDAT (default image = frame 0) 149 } else { 150 let fd: *u8 = sys_mmap(zlen + 16) 151 ap_be32(fd, 0, seqp[0]) 152 var i: i64 = 0 153 while i < zlen { fd[4 + i] = z[i]; i = i + 1 } 154 oo = ap_chunk(p, oo, 102, 100, 65, 84, fd, zlen + 4) // fdAT 155 seqp[0] = seqp[0] + 1 156 } 157 return oo 158} 159func apng_close(p: *u8, o: i64) -> i64 { 160 let e: *u8 = sys_mmap(8) 161 return ap_chunk(p, o, 73, 69, 78, 68, e, 0) // IEND 162}