code wiki / (root) / nx_imgxform.nx

nx_imgxform.nx source

↩ module page · 303 lines · 11274 B

1// nx_imgxform.nx -- DETERMINISTIC IMAGE-TRANSFORMATION BATTERY: the adversary set a reverse-image 2// engine must survive. TinEye's product claim is that it finds "modified versions" of an image -- 3// resized, cropped, edited, colour-adjusted, watermarked. You cannot honestly say how close we are to 4// that without APPLYING those modifications and MEASURING what survives. This organ is that adversary: 5// 16 named transformation classes, pure integer, zero float, bit-reproducible (fixed LCG seed), each a 6// pure function grayscale(w,h) -> grayscale(w2,h2). 7// 8// It deliberately holds NO ranking or hashing logic -- it only DEFORMS. nx_imgbench does the measuring, 9// nx_phash/nx_visdesc do the describing. Single responsibility: this file's one job is "make a modified 10// copy of an image, the way the real world modifies images". 11// 12// The classes are chosen from what actually happens to an image on the open web (and therefore what 13// TinEye/Google/Yandex are judged on): CDN rescaling, thumbnail generation, re-encoding artifacts, 14// editorial cropping, letterboxing into a fixed aspect, mirroring, EXIF-rotation, brightness/contrast 15// "enhancement", additive sensor/compression noise, and watermark/caption bars. 16// license_tier: ORIGINAL 17import "syscalls.nx" 18 19const XF_IDENT: i64 = 0 20const XF_SCALE50: i64 = 1 21const XF_SCALE200: i64 = 2 22const XF_BRIGHT_UP: i64 = 3 23const XF_BRIGHT_DN: i64 = 4 24const XF_CONTRAST: i64 = 5 25const XF_CROP10: i64 = 6 26const XF_CROP20: i64 = 7 27const XF_CROP30: i64 = 8 28const XF_PAD10: i64 = 9 29const XF_FLIPH: i64 = 10 30const XF_ROT90: i64 = 11 31const XF_ROT180: i64 = 12 32const XF_NOISE: i64 = 13 33const XF_WATERMARK: i64 = 14 34const XF_BLOCKQ: i64 = 15 35const XF_CROPSCALE: i64 = 16 // crop the centre THEN rescale it back to full size = occlusion + SCALE 36const XF_COUNT: i64 = 17 37 38const XF_NOISE_AMP: i64 = 12 // +/- grey levels of additive noise (heavy-recompression scale) 39const XF_BRIGHT_D: i64 = 40 // brightness offset applied by XF_BRIGHT_UP / XF_BRIGHT_DN 40const XF_WM_ROWPCT: i64 = 85 // watermark/caption bar starts at this percent of image height 41const XF_WM_LEVEL: i64 = 235 // watermark bar grey level (near-white caption bar) 42const XF_QSTEP: i64 = 16 // XF_BLOCKQ quantisation step (coarse-JPEG proxy) 43 44func xf_clamp(v: i64) -> i64 { if v < 0 { return 0 } if v > 255 { return 255 } return v } 45 46// MMIX LCG (Knuth) -- deterministic 64-bit stream so every transformation is bit-reproducible. 47func xf_rng(s: *i64) -> i64 { let x: i64 = s[0] * 0x5851F42D4C957F2D + 0x14057B7EF767814F; s[0] = x; return x } 48 49// human-readable class name (used by the ruler's report rows) 50func xf_name(kind: i64) -> *u8 { 51 if kind == XF_IDENT { return "identity " as *u8 } 52 if kind == XF_SCALE50 { return "rescale-50pct " as *u8 } 53 if kind == XF_SCALE200 { return "rescale-200pct" as *u8 } 54 if kind == XF_BRIGHT_UP { return "brightness+40 " as *u8 } 55 if kind == XF_BRIGHT_DN { return "brightness-40 " as *u8 } 56 if kind == XF_CONTRAST { return "contrast+25pct" as *u8 } 57 if kind == XF_CROP10 { return "crop-10pct " as *u8 } 58 if kind == XF_CROP20 { return "crop-20pct " as *u8 } 59 if kind == XF_CROP30 { return "crop-30pct " as *u8 } 60 if kind == XF_PAD10 { return "letterbox-10 " as *u8 } 61 if kind == XF_FLIPH { return "mirror-horiz " as *u8 } 62 if kind == XF_ROT90 { return "rotate-90 " as *u8 } 63 if kind == XF_ROT180 { return "rotate-180 " as *u8 } 64 if kind == XF_NOISE { return "noise-pm12 " as *u8 } 65 if kind == XF_WATERMARK { return "watermark-bar " as *u8 } 66 if kind == XF_BLOCKQ { return "recompress-8x8" as *u8 } 67 if kind == XF_CROPSCALE { return "crop+rescale " as *u8 } 68 return "unknown " as *u8 69} 70 71// nearest-neighbour resample (the CDN/thumbnailer operation) into a caller-sized buffer 72func xf_resample(gray: *u8, w: i64, h: i64, out: *u8, dw: i64, dh: i64) -> i64 { 73 var oy: i64 = 0 74 while oy < dh { 75 let sy: i64 = (oy * h) / dh 76 var ox: i64 = 0 77 while ox < dw { 78 let sx: i64 = (ox * w) / dw 79 out[oy * dw + ox] = gray[sy * w + sx] 80 ox = ox + 1 81 } 82 oy = oy + 1 83 } 84 return 0 85} 86 87// centre crop keeping keep_pct percent of EACH dimension (so crop-30 discards ~51 percent of the pixels) 88func xf_crop(gray: *u8, w: i64, h: i64, keep_pct: i64, outwh: *i64) -> *u8 { 89 var nw: i64 = w * keep_pct / 100 90 var nh: i64 = h * keep_pct / 100 91 if nw < 1 { nw = 1 } 92 if nh < 1 { nh = 1 } 93 let x0: i64 = (w - nw) / 2 94 let y0: i64 = (h - nh) / 2 95 let out: *u8 = sys_mmap(nw * nh) 96 var y: i64 = 0 97 while y < nh { 98 var x: i64 = 0 99 while x < nw { out[y * nw + x] = gray[(y0 + y) * w + (x0 + x)]; x = x + 1 } 100 y = y + 1 101 } 102 outwh[0] = nw 103 outwh[1] = nh 104 return out 105} 106 107// letterbox: surround the image with a black border of pct percent per side (fixed-aspect embedding) 108func xf_pad(gray: *u8, w: i64, h: i64, pct: i64, outwh: *i64) -> *u8 { 109 let bx: i64 = w * pct / 100 110 let by: i64 = h * pct / 100 111 let nw: i64 = w + 2 * bx 112 let nh: i64 = h + 2 * by 113 let out: *u8 = sys_mmap(nw * nh) 114 var i: i64 = 0 115 let tot: i64 = nw * nh 116 while i < tot { out[i] = 0 as u8; i = i + 1 } 117 var y: i64 = 0 118 while y < h { 119 var x: i64 = 0 120 while x < w { out[(by + y) * nw + (bx + x)] = gray[y * w + x]; x = x + 1 } 121 y = y + 1 122 } 123 outwh[0] = nw 124 outwh[1] = nh 125 return out 126} 127 128// coarse block quantisation: pull each pixel halfway to its 8x8 block mean, then snap to XF_QSTEP. 129// A float-free stand-in for aggressive JPEG re-encoding (the dominant real-web degradation). 130func xf_blockq(gray: *u8, w: i64, h: i64, out: *u8) -> i64 { 131 var by: i64 = 0 132 while by < h { 133 var bx: i64 = 0 134 while bx < w { 135 var sum: i64 = 0 136 var cnt: i64 = 0 137 var y: i64 = by 138 while y < by + 8 { 139 if y < h { 140 var x: i64 = bx 141 while x < bx + 8 { 142 if x < w { sum = sum + (gray[y * w + x] as i64); cnt = cnt + 1 } 143 x = x + 1 144 } 145 } 146 y = y + 1 147 } 148 var m: i64 = 0 149 if cnt > 0 { m = sum / cnt } 150 y = by 151 while y < by + 8 { 152 if y < h { 153 var x: i64 = bx 154 while x < bx + 8 { 155 if x < w { 156 let p: i64 = gray[y * w + x] as i64 157 var v: i64 = m + (p - m) / 2 158 v = (v / XF_QSTEP) * XF_QSTEP 159 out[y * w + x] = xf_clamp(v) as u8 160 } 161 x = x + 1 162 } 163 } 164 y = y + 1 165 } 166 bx = bx + 8 167 } 168 by = by + 8 169 } 170 return 0 171} 172 173// THE BATTERY. Returns a FRESH buffer holding the transformed image; outwh[0]=width, outwh[1]=height. 174// Never returns 0 for a valid kind, so callers do not need a null path for in-range classes. 175func xf_apply(kind: i64, gray: *u8, w: i64, h: i64, outwh: *i64) -> *u8 { 176 let n: i64 = w * h 177 178 if kind == XF_SCALE50 { 179 var dw: i64 = w / 2 180 var dh: i64 = h / 2 181 if dw < 1 { dw = 1 } 182 if dh < 1 { dh = 1 } 183 let o: *u8 = sys_mmap(dw * dh) 184 xf_resample(gray, w, h, o, dw, dh) 185 outwh[0] = dw; outwh[1] = dh 186 return o 187 } 188 if kind == XF_SCALE200 { 189 let dw: i64 = w * 2 190 let dh: i64 = h * 2 191 let o: *u8 = sys_mmap(dw * dh) 192 xf_resample(gray, w, h, o, dw, dh) 193 outwh[0] = dw; outwh[1] = dh 194 return o 195 } 196 if kind == XF_CROP10 { return xf_crop(gray, w, h, 90, outwh) } 197 if kind == XF_CROP20 { return xf_crop(gray, w, h, 80, outwh) } 198 if kind == XF_CROP30 { return xf_crop(gray, w, h, 70, outwh) } 199 if kind == XF_PAD10 { return xf_pad(gray, w, h, 10, outwh) } 200 if kind == XF_CROPSCALE { 201 // the real-web cropped-thumbnail: keep the centre 70%, then RESAMPLE it back to the original 202 // w x h. The surviving content is the same pixels shown at ~1.43x scale + a translated origin -- 203 // a similarity (scale+translation), which a translation-only matcher structurally cannot verify. 204 let cwh: *i64 = sys_mmap(16) as *i64 205 let c: *u8 = xf_crop(gray, w, h, 70, cwh) 206 let o2: *u8 = sys_mmap(w * h) 207 xf_resample(c, cwh[0], cwh[1], o2, w, h) 208 outwh[0] = w; outwh[1] = h 209 return o2 210 } 211 212 if kind == XF_ROT90 { 213 let nw: i64 = h 214 let nh: i64 = w 215 let o: *u8 = sys_mmap(nw * nh) 216 var y: i64 = 0 217 while y < nh { 218 var x: i64 = 0 219 while x < nw { o[y * nw + x] = gray[(h - 1 - x) * w + y]; x = x + 1 } 220 y = y + 1 221 } 222 outwh[0] = nw; outwh[1] = nh 223 return o 224 } 225 226 // every remaining class is same-size 227 let o: *u8 = sys_mmap(n) 228 outwh[0] = w 229 outwh[1] = h 230 231 if kind == XF_IDENT { 232 var i: i64 = 0 233 while i < n { o[i] = gray[i]; i = i + 1 } 234 return o 235 } 236 if kind == XF_BRIGHT_UP { 237 var i: i64 = 0 238 while i < n { o[i] = xf_clamp((gray[i] as i64) + XF_BRIGHT_D) as u8; i = i + 1 } 239 return o 240 } 241 if kind == XF_BRIGHT_DN { 242 var i: i64 = 0 243 while i < n { o[i] = xf_clamp((gray[i] as i64) - XF_BRIGHT_D) as u8; i = i + 1 } 244 return o 245 } 246 if kind == XF_CONTRAST { 247 var i: i64 = 0 248 while i < n { o[i] = xf_clamp(((gray[i] as i64) - 128) * 5 / 4 + 128) as u8; i = i + 1 } 249 return o 250 } 251 if kind == XF_FLIPH { 252 var y: i64 = 0 253 while y < h { 254 var x: i64 = 0 255 while x < w { o[y * w + x] = gray[y * w + (w - 1 - x)]; x = x + 1 } 256 y = y + 1 257 } 258 return o 259 } 260 if kind == XF_ROT180 { 261 var y: i64 = 0 262 while y < h { 263 var x: i64 = 0 264 while x < w { o[y * w + x] = gray[(h - 1 - y) * w + (w - 1 - x)]; x = x + 1 } 265 y = y + 1 266 } 267 return o 268 } 269 if kind == XF_NOISE { 270 let s: *i64 = sys_mmap(8) as *i64 271 s[0] = 0x2545F4914F6CDD1D 272 var i: i64 = 0 273 while i < n { 274 var r: i64 = xf_rng(s) >> 17 275 if r < 0 { r = 0 - r } 276 let d: i64 = (r % (2 * XF_NOISE_AMP + 1)) - XF_NOISE_AMP 277 o[i] = xf_clamp((gray[i] as i64) + d) as u8 278 i = i + 1 279 } 280 return o 281 } 282 if kind == XF_WATERMARK { 283 var i: i64 = 0 284 while i < n { o[i] = gray[i]; i = i + 1 } 285 let y0: i64 = h * XF_WM_ROWPCT / 100 286 var y: i64 = y0 287 while y < h { 288 var x: i64 = 0 289 while x < w { o[y * w + x] = XF_WM_LEVEL as u8; x = x + 1 } 290 y = y + 1 291 } 292 return o 293 } 294 if kind == XF_BLOCKQ { 295 xf_blockq(gray, w, h, o) 296 return o 297 } 298 299 // unknown class -> identity copy (fail-safe: never a null deref, never silent garbage) 300 var i: i64 = 0 301 while i < n { o[i] = gray[i]; i = i + 1 } 302 return o 303}