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}