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}