code wiki / _hdl_build / nx_png_textw.nx

nx_png_textw.nx source

↩ module page · 85 lines · 4568 B

1// nx_png_textw.nx -- sovereign PNG tEXt-chunk WRITER. The reader (_pe_pngtext_locate) already 2// existed; this is its missing twin. Inserts GENREC provenance tEXt chunks (seed / model / sampler 3// / steps / generator_host / prompt) into an existing complete PNG, just before its IEND chunk, 4// each with a correct CRC32 -> the image self-describes its generation factors AND nx_store_ingest 5// harvests them into the content-addressed gallery store. Used by nx_gen_orchestrator. 6// license_tier: ORIGINAL 7import "nx_syscalls.nx" 8 9func pw_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 10 11// write ONE tEXt chunk at b[off]: len(4 BE) "tEXt" keyword \0 value, then CRC32(type+data). returns new off. 12func pw_text(b: *u8, off: i64, kw: *u8, val: *u8) -> i64 { 13 let kwlen: i64 = pw_slen(kw) 14 let vallen: i64 = pw_slen(val) 15 let dlen: i64 = kwlen + 1 + vallen 16 b[off] = ((dlen >> 24) & 0xff) as u8 17 b[off+1] = ((dlen >> 16) & 0xff) as u8 18 b[off+2] = ((dlen >> 8) & 0xff) as u8 19 b[off+3] = (dlen & 0xff) as u8 20 b[off+4]=116 as u8; b[off+5]=69 as u8; b[off+6]=88 as u8; b[off+7]=116 as u8 21 var i: i64 = 0 22 while i < kwlen { b[off+8+i] = kw[i]; i = i + 1 } 23 b[off+8+kwlen] = 0 as u8 24 var j: i64 = 0 25 while j < vallen { b[off+8+kwlen+1+j] = val[j]; j = j + 1 } 26 var cc: i64 = 0xffffffff 27 var ci: i64 = off + 4 28 let ce: i64 = off + 8 + dlen 29 while ci < ce { 30 cc = cc ^ (b[ci] & 0xff) 31 var cb: i64 = 0 32 while cb < 8 { let cm: i64 = 0 - (cc & 1); cc = (cc >> 1) ^ (0xedb88320 & cm); cb = cb + 1 } 33 ci = ci + 1 34 } 35 let cv: i64 = (cc ^ 0xffffffff) & 0xffffffff 36 b[ce] = ((cv >> 24) & 0xff) as u8 37 b[ce+1] = ((cv >> 16) & 0xff) as u8 38 b[ce+2] = ((cv >> 8) & 0xff) as u8 39 b[ce+3] = (cv & 0xff) as u8 40 return ce + 4 41} 42 43// find the IEND chunk by walking chunks from offset 8. returns offset of IEND's length field, or -1. 44func pw_find_iend(src: *u8, srclen: i64) -> i64 { 45 var off: i64 = 8 46 while off + 12 <= srclen { 47 let clen: i64 = (((src[off]&0xff)<<24)|((src[off+1]&0xff)<<16))|(((src[off+2]&0xff)<<8)|(src[off+3]&0xff)) 48 if (src[off+4]&0xff)==73 { if (src[off+5]&0xff)==69 { if (src[off+6]&0xff)==78 { if (src[off+7]&0xff)==68 { return off } } } } 49 off = off + 12 + clen 50 } 51 return 0 - 1 52} 53 54// Insert the 6 GENREC tEXt chunks into `src` (a complete PNG) just before IEND -> dst. 55// returns new total length, or -1 if src has no IEND (not a PNG). dst must be >= srclen + sum of chunk sizes. 56// `size` ("WxH") added 2026-08-04 for the CID-COLLISION DATA-LOSS fix (debt 1785881333): the gallery 57// CID is a hash of THESE canonical tEXt fields, not of the pixels, so two renders that differed ONLY 58// in resolution produced the SAME cid and the second SILENTLY OVERWROTE the first blob (reproduced: 59// 512x512 md5 a9fa671a -> re-rendered at 768x1024, same cid, blob became md5 02242acd; the 512 image 60// was gone). Recording size here is what makes the canon distinguish them. 61// ★BACKWARD-COMPATIBLE BY CONSTRUCTION: an existing PNG carries no `size` chunk, so its canonical 62// form -- and therefore its CID -- is byte-identical to before. Nothing already in the store moves. 63func png_insert_genrec(src: *u8, srclen: i64, seed: *u8, model: *u8, sampler: *u8, steps: *u8, host: *u8, prompt: *u8, size: *u8, args: *u8, dst: *u8) -> i64 { 64 let ie: i64 = pw_find_iend(src, srclen) 65 if ie < 0 { return 0 - 1 } 66 var o: i64 = 0 67 while o < ie { dst[o] = src[o]; o = o + 1 } 68 o = pw_text(dst, o, "seed" as *u8, seed) 69 o = pw_text(dst, o, "model" as *u8, model) 70 o = pw_text(dst, o, "sampler" as *u8, sampler) 71 o = pw_text(dst, o, "steps" as *u8, steps) 72 o = pw_text(dst, o, "generator_host" as *u8, host) 73 o = pw_text(dst, o, "prompt" as *u8, prompt) 74 o = pw_text(dst, o, "size" as *u8, size) 75 // `args` = the EXACT string handed to the generator (user prompt + LoRA tags + the 76 // sd_cpp_extra_args blob carrying steps/cfg/seed/negative). Recording it closes the CID 77 // collision class GENERICALLY instead of one knob at a time: cfg was still colliding after the 78 // `size` fix (cfg 1.0 vs 5.0 = visibly different images, md5 f10849c2 vs 6471d466, SAME cid => 79 // the second overwrote the first). Any future knob that actually reaches the engine lands in 80 // this string, so it lands in the canon -- no whack-a-mole per parameter. 81 o = pw_text(dst, o, "args" as *u8, args) 82 var k: i64 = 0 83 while k < 12 { dst[o + k] = src[ie + k]; k = k + 1 } 84 return o + 12 85}