nx_av1_intra.nx source
↩ module page · 240 lines · 7304 B
1// nx_av1_intra.nx -- AV1/AV2 non-directional intra prediction.
2//
3// Part of the residual path: before any coefficient is added, a block is
4// PREDICTED from its already-decoded above row and left column. These are the
5// modes that need no angle -- DC, vertical, horizontal, Paeth and the three
6// smooth variants -- and together they cover most real intra blocks.
7//
8// AVAILABILITY CHANGES THE FORMULA, NOT JUST THE INPUT. DC has FOUR distinct
9// cases: both edges present, only above, only left, and neither. With neither
10// it is not zero and not an average of nothing -- it is the mid-grey
11// 1 << (bitdepth - 1). A decoder that treats a missing edge as zeros predicts
12// black along the top and left of every frame, which looks like a vignette
13// rather than a bug.
14//
15// THE SMOOTH WEIGHTS SUM TO 256, NOT 255. Each smooth predictor blends a
16// weight w against (256 - w) and rounds with +128 >> 8 (or +256 >> 9 for the
17// two-axis form). Using 255 as the complement makes constant input predict
18// slightly-wrong constant output -- a fraction of a level per pixel, which
19// accumulates across a frame of intra blocks and shows as a gradient.
20//
21// PAETH PICKS, IT DOES NOT AVERAGE. It selects whichever of left, above or
22// above-left is nearest the linear estimate. The tie order matters: left
23// beats above, and above beats above-left.
24//
25// EVERY LOCAL IS DECLARED ONCE. nx_cc uses FLAT FUNCTION SCOPE -- re-declaring
26// a name inside a sibling branch desyncs its parser (proven here 2026-07-31:
27// a `let below` in two exclusive branches produced 'unexpected operator token
28// at expression start'). Params also travel in an array rather than as ten
29// positional arguments.
30//
31// genealogy_id: av1_spec_7_11_2_intra_prediction
32// lineage_id: nx_av1_intra_v1
33// license_tier: ORIGINAL
34
35import "nx_syscalls.nx"
36
37const NX_INTRA_DC: i64 = 0
38const NX_INTRA_V: i64 = 1
39const NX_INTRA_H: i64 = 2
40const NX_INTRA_PAETH: i64 = 3
41const NX_INTRA_SMOOTH: i64 = 4
42const NX_INTRA_SMOOTH_V: i64 = 5
43const NX_INTRA_SMOOTH_H: i64 = 6
44
45const NX_INTRA_P_ABOVELEFT: i64 = 0
46const NX_INTRA_P_W: i64 = 1
47const NX_INTRA_P_H: i64 = 2
48const NX_INTRA_P_HAVE_A: i64 = 3
49const NX_INTRA_P_HAVE_L: i64 = 4
50const NX_INTRA_P_BITDEPTH: i64 = 5
51
52func nx_intra_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
53
54func nx_intra_sm_weight(size: i64, i: i64) -> i64 {
55 if i < 0 { return 0 - 1 }
56 if i >= size { return 0 - 1 }
57 if size == 4 {
58 if i == 0 { return 255 }
59 if i == 1 { return 149 }
60 if i == 2 { return 85 }
61 return 64
62 }
63 if size == 8 {
64 if i == 0 { return 255 }
65 if i == 1 { return 197 }
66 if i == 2 { return 146 }
67 if i == 3 { return 105 }
68 if i == 4 { return 73 }
69 if i == 5 { return 50 }
70 if i == 6 { return 37 }
71 return 32
72 }
73 if size == 16 {
74 if i == 0 { return 255 }
75 if i == 1 { return 225 }
76 if i == 2 { return 196 }
77 if i == 3 { return 170 }
78 if i == 4 { return 145 }
79 if i == 5 { return 123 }
80 if i == 6 { return 102 }
81 if i == 7 { return 84 }
82 if i == 8 { return 68 }
83 if i == 9 { return 54 }
84 if i == 10 { return 43 }
85 if i == 11 { return 33 }
86 if i == 12 { return 26 }
87 if i == 13 { return 20 }
88 if i == 14 { return 17 }
89 return 16
90 }
91 return 0 - 1
92}
93
94func nx_intra_dc_value(above: *i64, left: *i64, p: *i64) -> i64 {
95 let w: i64 = p[NX_INTRA_P_W]
96 let h: i64 = p[NX_INTRA_P_H]
97 let have_a: i64 = p[NX_INTRA_P_HAVE_A]
98 let have_l: i64 = p[NX_INTRA_P_HAVE_L]
99 let bd: i64 = p[NX_INTRA_P_BITDEPTH]
100 if w <= 0 { return 0 - 1 }
101 if h <= 0 { return 0 - 1 }
102 if bd < 8 { return 0 - 1 }
103
104 var sum: i64 = 0
105 var i: i64 = 0
106
107 if have_a == 1 {
108 if have_l == 1 {
109 while i < w { sum = sum + above[i]; i = i + 1 }
110 i = 0
111 while i < h { sum = sum + left[i]; i = i + 1 }
112 return (sum + ((w + h) >> 1)) / (w + h)
113 }
114 while i < w { sum = sum + above[i]; i = i + 1 }
115 return (sum + (w >> 1)) / w
116 }
117 if have_l == 1 {
118 while i < h { sum = sum + left[i]; i = i + 1 }
119 return (sum + (h >> 1)) / h
120 }
121 return 1 << (bd - 1)
122}
123
124func nx_intra_paeth_pick(left: i64, above: i64, aboveleft: i64) -> i64 {
125 let base: i64 = left + above - aboveleft
126 let p_left: i64 = nx_intra_abs(base - left)
127 let p_above: i64 = nx_intra_abs(base - above)
128 let p_al: i64 = nx_intra_abs(base - aboveleft)
129 if p_left <= p_above {
130 if p_left <= p_al { return left }
131 }
132 if p_above <= p_al { return above }
133 return aboveleft
134}
135
136func nx_intra_predict(mode: i64, out: *i64, above: *i64, left: *i64, p: *i64) -> i64 {
137 let w: i64 = p[NX_INTRA_P_W]
138 let h: i64 = p[NX_INTRA_P_H]
139 let aboveleft: i64 = p[NX_INTRA_P_ABOVELEFT]
140 if w <= 0 { return 0 }
141 if h <= 0 { return 0 }
142 if mode < 0 { return 0 }
143 if mode > NX_INTRA_SMOOTH_H { return 0 }
144
145 var r: i64 = 0
146 var c: i64 = 0
147 var wh: i64 = 0
148 var ww: i64 = 0
149 var acc: i64 = 0
150 var dc: i64 = 0
151 let below: i64 = left[h - 1]
152 let right: i64 = above[w - 1]
153
154 if mode == NX_INTRA_DC {
155 dc = nx_intra_dc_value(above, left, p)
156 if dc < 0 { return 0 }
157 r = 0
158 while r < w * h { out[r] = dc; r = r + 1 }
159 return 1
160 }
161
162 if mode == NX_INTRA_V {
163 r = 0
164 while r < h {
165 c = 0
166 while c < w { out[r * w + c] = above[c]; c = c + 1 }
167 r = r + 1
168 }
169 return 1
170 }
171
172 if mode == NX_INTRA_H {
173 r = 0
174 while r < h {
175 c = 0
176 while c < w { out[r * w + c] = left[r]; c = c + 1 }
177 r = r + 1
178 }
179 return 1
180 }
181
182 if mode == NX_INTRA_PAETH {
183 r = 0
184 while r < h {
185 c = 0
186 while c < w {
187 out[r * w + c] = nx_intra_paeth_pick(left[r], above[c], aboveleft)
188 c = c + 1
189 }
190 r = r + 1
191 }
192 return 1
193 }
194
195 if mode == NX_INTRA_SMOOTH_V {
196 r = 0
197 while r < h {
198 wh = nx_intra_sm_weight(h, r)
199 if wh < 0 { return 0 }
200 c = 0
201 while c < w {
202 out[r * w + c] = (wh * above[c] + (256 - wh) * below + 128) >> 8
203 c = c + 1
204 }
205 r = r + 1
206 }
207 return 1
208 }
209
210 if mode == NX_INTRA_SMOOTH_H {
211 r = 0
212 while r < h {
213 c = 0
214 while c < w {
215 ww = nx_intra_sm_weight(w, c)
216 if ww < 0 { return 0 }
217 out[r * w + c] = (ww * left[r] + (256 - ww) * right + 128) >> 8
218 c = c + 1
219 }
220 r = r + 1
221 }
222 return 1
223 }
224
225 r = 0
226 while r < h {
227 wh = nx_intra_sm_weight(h, r)
228 if wh < 0 { return 0 }
229 c = 0
230 while c < w {
231 ww = nx_intra_sm_weight(w, c)
232 if ww < 0 { return 0 }
233 acc = wh * above[c] + (256 - wh) * below + ww * left[r] + (256 - ww) * right
234 out[r * w + c] = (acc + 256) >> 9
235 c = c + 1
236 }
237 r = r + 1
238 }
239 return 1
240}