nx_deblock.nx source
↩ module page · 148 lines · 5135 B
1// nx_deblock.nx -- in-loop deblocking filter for 8x8 block-grid
2// reconstructions. Substrate primitive that smooths the quantisation
3// discontinuities at block boundaries without touching real edges.
4//
5// Algorithm (simple, public): for each boundary (horizontal between
6// rows 7-8, 15-16, ...; vertical between cols 7-8, 15-16, ...) take
7// the four pixels straddling the line:
8// p1, p0 | q0, q1
9// If the local variation is small (|p1-p0| < T && |q0-q1| < T && |p0-q0|
10// < T2), apply a low-pass filter that pulls p0 and q0 toward the
11// average across the boundary. Otherwise leave the pixels alone
12// (preserves real edges).
13//
14// This is structurally what H.263 Annex J and H.264's deblocking
15// filter do; the simple variant here re-derives from the underlying
16// public idea (filter only smooth regions) without copying their
17// boundary-strength tables or per-block-mode dispatch. Quality is
18// modest; the row goes from LOSE to WIN on the substrate axis once
19// the feature exists. Stronger variants are named improvements.
20//
21// genealogy_id: itu_t_h_263_annex_j + h264_8_7_deblock + jpeg2000_visual +
22// nx_video_codec_v1_q10
23// lineage_id: nishi_deblock_q10
24
25// nx_safety_envelope:
26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
27// sil_target: SIL1
28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
29// verdict: NOT_YET_EVALUATED
30
31import "nx_syscalls.nx"
32
33const NX_DB_VERDICT_UNKNOWN: i64 = 0
34const NX_DB_VERDICT_OK: i64 = 1
35const NX_DB_VERDICT_BAD_PARAMS: i64 = 2
36const NX_DB_VERDICT_N: i64 = 3
37
38const NX_DB_BLOCK: i64 = 8
39const NX_DB_T1: i64 = 16 // |p1-p0| threshold
40const NX_DB_T2: i64 = 32 // |p0-q0| threshold
41
42func _db_abs(x: i64) -> i64 {
43 if x < 0 { return -x }
44 return x
45}
46
47// Apply the boundary filter on one 4-pixel sample (p1, p0, q0, q1).
48// Writes new p0' and q0' to *p0_out and *q0_out.
49//
50// Conditions for filtering:
51// |p1 - p0| < T1
52// |q0 - q1| < T1
53// |p0 - q0| < T2
54// Filter (when conditions met):
55// delta = (q0 - p0) / 4
56// p0' = clamp(p0 + delta, 0, 255)
57// q0' = clamp(q0 - delta, 0, 255)
58//
59// When NOT filtering, the function returns the original values.
60func _db_filter4(p1: i64, p0: i64, q0: i64, q1: i64,
61 p0_out: *i64, q0_out: *i64) -> i64 {
62 if _db_abs(p1 - p0) >= NX_DB_T1 {
63 *p0_out = p0; *q0_out = q0
64 return 0
65 }
66 if _db_abs(q0 - q1) >= NX_DB_T1 {
67 *p0_out = p0; *q0_out = q0
68 return 0
69 }
70 if _db_abs(p0 - q0) >= NX_DB_T2 {
71 *p0_out = p0; *q0_out = q0
72 return 0
73 }
74 let delta: i64 = (q0 - p0) / 4
75 var new_p0: i64 = p0 + delta
76 var new_q0: i64 = q0 - delta
77 if new_p0 < 0 { new_p0 = 0 }
78 if new_p0 > 255 { new_p0 = 255 }
79 if new_q0 < 0 { new_q0 = 0 }
80 if new_q0 > 255 { new_q0 = 255 }
81 *p0_out = new_p0
82 *q0_out = new_q0
83 return 1
84}
85
86// Filter horizontal block boundaries (between rows ...7,8 ...15,16).
87// `img` is W*H, row-major. In-place.
88func nx_deblock_horizontal(img: *i64, W: i64, H: i64) -> i64 {
89 if W < 1 { return NX_DB_VERDICT_BAD_PARAMS }
90 if H < 16 { return NX_DB_VERDICT_OK } // no inner boundary to filter
91 var by: i64 = 1
92 while by * NX_DB_BLOCK < H {
93 let row: i64 = by * NX_DB_BLOCK
94 var x: i64 = 0
95 while x < W {
96 let p1: i64 = img[(row - 2) * W + x]
97 let p0: i64 = img[(row - 1) * W + x]
98 let q0: i64 = img[(row ) * W + x]
99 let q1: i64 = img[(row + 1) * W + x]
100 let p0_slot: *i64 = sys_mmap(16) as *i64
101 let q0_slot: *i64 = sys_mmap(16) as *i64
102 _db_filter4(p1, p0, q0, q1, p0_slot, q0_slot)
103 img[(row - 1) * W + x] = *p0_slot
104 img[(row ) * W + x] = *q0_slot
105 x = x + 1
106 }
107 by = by + 1
108 }
109 return NX_DB_VERDICT_OK
110}
111
112// Filter vertical block boundaries (between cols 7,8 ...15,16).
113func nx_deblock_vertical(img: *i64, W: i64, H: i64) -> i64 {
114 if W < 16 { return NX_DB_VERDICT_OK }
115 if H < 1 { return NX_DB_VERDICT_BAD_PARAMS }
116 var bx: i64 = 1
117 while bx * NX_DB_BLOCK < W {
118 let col: i64 = bx * NX_DB_BLOCK
119 var y: i64 = 0
120 while y < H {
121 let p1: i64 = img[y * W + (col - 2)]
122 let p0: i64 = img[y * W + (col - 1)]
123 let q0: i64 = img[y * W + (col )]
124 let q1: i64 = img[y * W + (col + 1)]
125 let p0_slot: *i64 = sys_mmap(16) as *i64
126 let q0_slot: *i64 = sys_mmap(16) as *i64
127 _db_filter4(p1, p0, q0, q1, p0_slot, q0_slot)
128 img[y * W + (col - 1)] = *p0_slot
129 img[y * W + (col )] = *q0_slot
130 y = y + 1
131 }
132 bx = bx + 1
133 }
134 return NX_DB_VERDICT_OK
135}
136
137// One-shot: filter both horizontal then vertical.
138func nx_deblock(img: *i64, W: i64, H: i64) -> i64 {
139 nx_deblock_horizontal(img, W, H)
140 nx_deblock_vertical(img, W, H)
141 return NX_DB_VERDICT_OK
142}
143
144func nx_deblock_verdict_is_valid(v: i64) -> i64 {
145 if v < 0 { return 0 }
146 if v >= NX_DB_VERDICT_N { return 0 }
147 return 1
148}