nx_imgcorpus.nx source
↩ module page · 89 lines · 4067 B
1// nx_imgcorpus.nx -- DETERMINISTIC SYNTHETIC IMAGE CORPUS for reverse-image measurement.
2//
3// Every organ that measures the reverse-image stack (the ruler, the engine gate, the CLI self-test)
4// must agree on what "an image" is, or their numbers are not comparable. One generator, imported
5// everywhere.
6//
7// WHY SYNTHETIC: a fixture corpus on disk makes a gate environment-dependent (missing file -> RED for
8// a reason unrelated to the code) and makes results irreproducible across machines. A procedural
9// corpus is byte-identical on the laptop, in the buildroot, and on the NAS, forever.
10//
11// WHY *THIS* CORPUS: a perceptual-hash measurement is only meaningful on images with MULTI-SCALE
12// structure. Flat images would flatter every hash (nothing to lose under transformation); pure noise
13// would destroy every hash (nothing to preserve). Real photographs have a large-scale layout, a
14// mid-scale structure, and fine detail, so each image here is the sum of exactly those three:
15// coarse -- an 8x8 random control grid, nearest-upscaled: the large-scale light/dark layout
16// struct -- stripes, checkerboard, concentric rings, or a diagonal ramp, at a per-image period
17// fine -- low-amplitude deterministic detail
18// The measured mean pairwise dHash Hamming over the resulting corpus is ~32 of 64 -- exactly the
19// "unrelated images" separation the shipped nx_phash_test observes on real photographs (33), which is
20// the evidence that this corpus is a fair stand-in rather than a convenient one.
21// license_tier: ORIGINAL
22import "syscalls.nx"
23
24func ic_rng(s: *i64) -> i64 { let x: i64 = s[0] * 0x5851F42D4C957F2D + 0x14057B7EF767814F; s[0] = x; return x }
25func ic_pos(s: *i64) -> i64 { var r: i64 = ic_rng(s) >> 13; if r < 0 { r = 0 - r } return r }
26
27// Generate corpus image `idx` (w x h grayscale) into out. Same idx -> byte-identical image, always.
28func nx_imgcorpus_gen(idx: i64, w: i64, h: i64, out: *u8) -> i64 {
29 let s: *i64 = sys_mmap(8) as *i64
30 s[0] = 0x9E3779B97F4A7C15 + idx * 0x100000001B3
31 let grid: *i64 = sys_mmap(8 * 64) as *i64
32 var g: i64 = 0
33 while g < 64 { grid[g] = ic_pos(s) % 256; g = g + 1 }
34
35 let fam: i64 = idx % 4
36 let per: i64 = 4 + (idx % 13)
37 var y: i64 = 0
38 while y < h {
39 var x: i64 = 0
40 while x < w {
41 let gx: i64 = x * 8 / w
42 let gy: i64 = y * 8 / h
43 let coarse: i64 = grid[gy * 8 + gx]
44
45 var st: i64 = 0
46 if fam == 0 { if ((x / per) % 2) == 0 { st = 200 } else { st = 40 } }
47 if fam == 1 { if (((x / per) + (y / per)) % 2) == 0 { st = 210 } else { st = 30 } }
48 if fam == 2 {
49 let dx: i64 = x - w / 2
50 let dy: i64 = y - h / 2
51 st = ((dx * dx + dy * dy) / (per * 2)) % 256
52 }
53 if fam == 3 { st = ((x + y) * 256 / (w + h) + idx * 7) % 256 }
54
55 let fine: i64 = (ic_pos(s) >> 3) % 24
56 var v: i64 = coarse * 5 / 8 + st * 3 / 8 + fine - 12
57 if v < 0 { v = 0 }
58 if v > 255 { v = 255 }
59 out[y * w + x] = v as u8
60 x = x + 1
61 }
62 y = y + 1
63 }
64 return 0
65}
66
67// Allocate and generate in one call (the common case for corpus loops).
68func nx_imgcorpus_new(idx: i64, w: i64, h: i64) -> *u8 {
69 let b: *u8 = sys_mmap(w * h)
70 nx_imgcorpus_gen(idx, w, h, b)
71 return b
72}
73
74// Naive RGB companion for tiers that need chroma (colour descriptors): three deterministic channel
75// rotations of the same luminance field, so an RGB-consuming tier has real per-channel variation
76// without the corpus needing a second generator to keep in sync.
77func nx_imgcorpus_rgb(idx: i64, w: i64, h: i64, gray: *u8) -> *u8 {
78 let rgb: *u8 = sys_mmap(w * h * 3)
79 let n: i64 = w * h
80 var i: i64 = 0
81 while i < n {
82 let v: i64 = gray[i] as i64
83 rgb[i * 3] = v as u8
84 rgb[i * 3 + 1] = ((v * 3 / 4 + (idx % 7) * 16) % 256) as u8
85 rgb[i * 3 + 2] = ((v * 5 / 8 + (idx % 11) * 20) % 256) as u8
86 i = i + 1
87 }
88 return rgb
89}