code wiki / (root) / nx_image_gray.nx

nx_image_gray.nx source

↩ module page · 74 lines · 3432 B

1// nx_image_gray.nx -- SHARED: decode an encoded image file -> row-major u8 Rec.709 grayscale. 2// ONE source of truth so the INDEXER (nx_image_index) and the QUERY client (nx_image_search) 3// fingerprint pixels IDENTICALLY -- any divergence here would silently break image<->image matching. 4// 5// ARCHITECTURE (2026-08-05): this is a PROJECTION of the single decode chokepoint, not a second 6// decoder. Two fast paths keep their native planes (JPEG -> luma without chroma work; GIF -> the 7// LZW decoder's own Rec.709 gray, de-interlaced in gif_decode_core); EVERYTHING ELSE routes through 8// nx_img_bytes_to_rgb -- so PNG (palette/Adam7/sub-byte included), BMP, TIFF, WebP-lossless and every 9// FUTURE decoder added at the chokepoint become fingerprintable here in the same act, permanently. 10// Returns gray buffer + sets out_wh[0]=w, out_wh[1]=h; 0 on failure/unsupported. license_tier: ORIGINAL 11import "nx_jpeg_ascii.nx" 12import "nx_gif_decode.nx" 13import "nx_img_bytes_to_rgb.nx" 14 15func nx_img_to_gray(path: *u8, out_wh: *i64) -> *u8 { 16 let box: *i64=sys_mmap(16) as *i64 17 let raw: *u8=sys_read_file(path, box) 18 if raw==(0 as *u8) { return 0 as *u8 } 19 // JPEG (magic FF D8 FF) -> sovereign decoder's luma plane (MCU-strided) -> packed gray. 20 // Fast path: the Y component IS Rec.709-weighted luminance at full resolution. 21 if box[0]>=3 { if raw[0]==(255 as u8) { if raw[1]==(216 as u8) { if raw[2]==(255 as u8) { 22 let jp: *i64=sys_mmap(8) as *i64 23 let jw: *i64=sys_mmap(8) as *i64 24 let jh: *i64=sys_mmap(8) as *i64 25 let js: *i64=sys_mmap(8) as *i64 26 if nx_jpeg_decode_luma(raw, box[0], jp, jw, jh, js)!=0 { return 0 as *u8 } 27 let jww: i64=jw[0] 28 let jhh: i64=jh[0] 29 let jst: i64=js[0] 30 let jpl: *u8=jp[0] as *u8 31 let jgray: *u8=sys_mmap(jww*jhh) 32 var jy: i64=0 33 while jy<jhh { var jx: i64=0; while jx<jww { jgray[jy*jww+jx]=jpl[jy*jst+jx]; jx=jx+1 } jy=jy+1 } 34 out_wh[0]=jww; out_wh[1]=jhh 35 return jgray 36 } } } } 37 // GIF87a/89a -> the LZW decoder's native Rec.709 gray (de-interlaced in the core; 38 // same 54/183/19 weights as the collapse below, so the projections agree). 39 if box[0]>=6 { if raw[0]==(71 as u8) { if raw[1]==(73 as u8) { if raw[2]==(70 as u8) { if raw[3]==(56 as u8) { 40 let gwh: *i64=sys_mmap(32) as *i64 41 gwh[0]=0 42 gwh[1]=0 43 let ggray: *u8=gif_decode(raw, box[0], gwh) 44 if ggray==(0 as *u8) { return 0 as *u8 } 45 if gwh[0]<=0 { return 0 as *u8 } 46 if gwh[1]<=0 { return 0 as *u8 } 47 out_wh[0]=gwh[0]; out_wh[1]=gwh[1] 48 return ggray 49 } } } } } 50 // EVERYTHING ELSE: the shared chokepoint (PNG incl palette/Adam7/sub-byte, BMP, 51 // TIFF, WebP-lossless, and whatever lands there next) -> Rec.709 collapse. 52 let cwh: *i64=sys_mmap(32) as *i64 53 cwh[0]=0 54 cwh[1]=0 55 let rgb: *u8=nx_img_bytes_to_rgb(raw, box[0], cwh) 56 if rgb==(0 as *u8) { return 0 as *u8 } 57 let w: i64=cwh[0] 58 let h: i64=cwh[1] 59 if w<=0 { return 0 as *u8 } 60 if h<=0 { return 0 as *u8 } 61 let gray: *u8=sys_mmap(w*h+16) 62 var p: i64=0 63 while p<w*h { 64 let r: i64=rgb[p*3] as i64 65 let g: i64=rgb[p*3+1] as i64 66 let b: i64=rgb[p*3+2] as i64 67 var gv: i64=(54*r+183*g+19*b)>>8 68 if gv>255 { gv=255 } 69 gray[p]=gv as u8 70 p=p+1 71 } 72 out_wh[0]=w; out_wh[1]=h 73 return gray 74}