code wiki / (root) / nx_img_scale.nx

nx_img_scale.nx source

↩ module page · 91 lines · 3864 B

1// nx_img_scale.nx -- sovereign image resampler, hardware-rung-up. 2// 3// Bilinear resize of an N-channel u8 image: ANY src WxH -> ANY dst WxH (both 4// UP- and DOWN-scale, the general "convert to size Y" primitive). Built from 5// the hardware rung UP: pure i64 Q16.16 fixed-point -- no float, no libc, no 6// SIMD dependency, only integer multiply / add / divide-by-power-of-two. All 7// intermediates are kept NON-NEGATIVE (source coord clamped into range) so the 8// code never relies on signed-shift semantics. 9// 10// Convention: half-pixel centers, sx = (x+0.5)*sw/dw - 0.5, edge pixels 11// repeated -- the SAME convention OpenCV INTER_LINEAR / Pillow / ffmpeg-swscale 12// use, so the planned differential gate can prove byte-parity vs a 3rd-party 13// reference (non-novel-capability proving doctrine). 14// 15// nx_scale.nx is Gaussian-pyramid DOWN-scale only; this is the general 16// up/down resampler the media convert path needs. 17// 18// RUNG 1 (this file): bilinear, correctness via KAT (hand-computed from the 19// standard formula = the independent oracle). NEXT: (a) differential gate vs 20// swscale/Pillow; (b) bicubic / Lanczos as selectable vtable transforms; (c) 21// wire into nx_uxf_negotiate's convert path; (d) MEASURED S-class exceed 22// census (organ-graded, never self-scored). 23// license_tier: ORIGINAL 24 25import "nx_syscalls.nx" 26 27const NX_IMG_Q: i64 = 65536 // Q16.16 one 28const NX_IMG_HALF: i64 = 32768 // 0.5 in Q16.16 29const NX_IMG_Q2: i64 = 4294967296 // 2^32 (Q16 * Q16) 30const NX_IMG_RND: i64 = 2147483648 // 2^31 (round-to-nearest for the 2^32 divide) 31 32func _img_clamp(v: i64, lo: i64, hi: i64) -> i64 { 33 if v < lo { return lo } 34 if v > hi { return hi } 35 return v 36} 37 38// Source coordinate (Q16) for dst index d, half-pixel centered, clamped into 39// [0, (ssize-1)*Q] so edges repeat the border pixel AND every intermediate 40// stays non-negative. 41func _img_srccoord(d: i64, ssize: i64, dsize: i64) -> i64 { 42 let num: i64 = (2 * d + 1) * ssize * NX_IMG_Q 43 let sx: i64 = num / (2 * dsize) - NX_IMG_HALF 44 let hi: i64 = (ssize - 1) * NX_IMG_Q 45 return _img_clamp(sx, 0, hi) 46} 47 48// Bilinear resample. src/dst are row-major, channel-interleaved u8 buffers. 49// Returns 0 on success, -1 on bad args. 50func nx_img_scale_bilinear(src: *u8, sw: i64, sh: i64, nc: i64, 51 dst: *u8, dw: i64, dh: i64) -> i64 { 52 if sw <= 0 { return -1 } 53 if sh <= 0 { return -1 } 54 if dw <= 0 { return -1 } 55 if dh <= 0 { return -1 } 56 if nc <= 0 { return -1 } 57 var y: i64 = 0 58 while y < dh { 59 let sy: i64 = _img_srccoord(y, sh, dh) 60 let y0: i64 = sy / NX_IMG_Q 61 let fy: i64 = sy - y0 * NX_IMG_Q 62 let y1: i64 = _img_clamp(y0 + 1, 0, sh - 1) 63 var x: i64 = 0 64 while x < dw { 65 let sx: i64 = _img_srccoord(x, sw, dw) 66 let x0: i64 = sx / NX_IMG_Q 67 let fx: i64 = sx - x0 * NX_IMG_Q 68 let x1: i64 = _img_clamp(x0 + 1, 0, sw - 1) 69 let base00: i64 = (y0 * sw + x0) * nc 70 let base01: i64 = (y0 * sw + x1) * nc 71 let base10: i64 = (y1 * sw + x0) * nc 72 let base11: i64 = (y1 * sw + x1) * nc 73 let dbase: i64 = (y * dw + x) * nc 74 var c: i64 = 0 75 while c < nc { 76 let p00: i64 = src[base00 + c] as i64 77 let p01: i64 = src[base01 + c] as i64 78 let p10: i64 = src[base10 + c] as i64 79 let p11: i64 = src[base11 + c] as i64 80 let top_q: i64 = (NX_IMG_Q - fx) * p00 + fx * p01 81 let bot_q: i64 = (NX_IMG_Q - fx) * p10 + fx * p11 82 let out_q: i64 = ((NX_IMG_Q - fy) * top_q + fy * bot_q + NX_IMG_RND) / NX_IMG_Q2 83 dst[dbase + c] = out_q as u8 84 c = c + 1 85 } 86 x = x + 1 87 } 88 y = y + 1 89 } 90 return 0 91}