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}