nx_rle.nx source
↩ module page · 136 lines · 4836 B
1// nx_rle.nx -- run-length encoding for sparse coefficient streams.
2//
3// Encoding format for one 8x8 block (64 entries):
4// Stream is a sequence of (run, level) pairs. Each pair is:
5// [run_byte] number of preceding zeros (0..63 inclusive)
6// [level_int16] the non-zero coefficient that follows the run
7// The stream is terminated by an End-Of-Block marker = (0, 0).
8//
9// A run of >63 zeros wraps via a special "zero-run-only" pair
10// (run=63, level=0) that consumes 63 zeros and contributes none;
11// caller continues from there. Since one block has 64 coefficients,
12// 64 zeros total means we emit (0, 0) immediately (EOB).
13//
14// genealogy_id: jpeg_iso_10918 + mpeg_iso_11172 + h264_cavlc +
15// nx_zigzag_q10
16// lineage_id: nishi_rle_block_q10
17
18// nx_safety_envelope:
19// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
20// sil_target: SIL1
21// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
22// verdict: NOT_YET_EVALUATED
23
24import "nx_syscalls.nx"
25
26const NX_RLE_VERDICT_UNKNOWN: i64 = 0
27const NX_RLE_VERDICT_OK: i64 = 1
28const NX_RLE_VERDICT_BUF_TOO_SMALL: i64 = 2
29const NX_RLE_VERDICT_TRUNCATED: i64 = 3
30const NX_RLE_VERDICT_BAD_PARAMS: i64 = 4
31const NX_RLE_VERDICT_N: i64 = 5
32
33const NX_RLE_BLOCK_LEN: i64 = 64
34
35// Write int16 little-endian. Returns off+2.
36func _rle_w16(buf: *u8, off: i64, v: i64) -> i64 {
37 var vv: i64 = v
38 if vv < 0 { vv = vv + 65536 }
39 buf[off] = vv & 0xff
40 buf[off + 1] = (vv >> 8) & 0xff
41 return off + 2
42}
43
44// Read int16 little-endian sign-extended.
45func _rle_r16(buf: *u8, off: i64) -> i64 {
46 var v: i64 = buf[off] | (buf[off + 1] << 8)
47 if v >= 32768 { v = v - 65536 }
48 return v
49}
50
51// Encode one 64-entry stream (already in zigzag order) into out_buf.
52// Returns bytes written, or -1 on overflow.
53func nx_rle_encode_block(
54 stream: *i64,
55 out_buf: *u8, out_cap: i64,
56 out_n: *i64
57) -> i64 {
58 var off: i64 = 0
59 var run: i64 = 0
60 var i: i64 = 0
61 while i < NX_RLE_BLOCK_LEN {
62 let v: i64 = stream[i]
63 if v == 0 {
64 // Within a 64-element block run never exceeds 63, so it
65 // always fits the 1-byte run field -- no long-run flush is
66 // needed (the old flush emitted a redundant (63,0) pair,
67 // wasting 3 bytes on every flat block).
68 run = run + 1
69 } else {
70 if off + 3 > out_cap { return NX_RLE_VERDICT_BUF_TOO_SMALL }
71 out_buf[off] = run & 0xff
72 off = _rle_w16(out_buf, off + 1, v)
73 run = 0
74 }
75 i = i + 1
76 }
77 // End-of-Block: (0, 0).
78 if off + 3 > out_cap { return NX_RLE_VERDICT_BUF_TOO_SMALL }
79 out_buf[off] = 0
80 off = _rle_w16(out_buf, off + 1, 0)
81 *out_n = off
82 return NX_RLE_VERDICT_OK
83}
84
85// Decode one block. Reads consecutive (run, level) pairs from in_buf
86// until an EOB (run=0, level=0) is hit or 64 entries have been
87// written. Writes the reconstructed 64-entry stream to `stream`.
88// Returns sealed verdict; writes consumed bytes to *consumed.
89func nx_rle_decode_block(
90 in_buf: *u8, in_len: i64,
91 stream: *i64,
92 consumed: *i64
93) -> i64 {
94 var off: i64 = 0
95 var pos: i64 = 0
96 // Zero-fill the output up front.
97 var z: i64 = 0
98 while z < NX_RLE_BLOCK_LEN { stream[z] = 0; z = z + 1 }
99
100 // Read (run, level) pairs until the EOB marker (0,0). The encoder
101 // ALWAYS emits an EOB after the 64 coefficients, so we must consume
102 // it even when `pos` has already reached 64 (a dense block, or a
103 // 63-zero long-run flush that lands pos on 64). Stopping at pos<64
104 // left the EOB unconsumed -> `consumed` 3 bytes short -> every later
105 // block decoded from a misaligned offset (the entropy round-trip bug).
106 var done: i64 = 0
107 while done == 0 {
108 if off + 3 > in_len { return NX_RLE_VERDICT_TRUNCATED }
109 let run: i64 = in_buf[off]
110 let level: i64 = _rle_r16(in_buf, off + 1)
111 off = off + 3
112 if run == 0 {
113 if level == 0 {
114 done = 1 // EOB -> consume it, then stop
115 } else {
116 // run = 0, level != 0 -> write level immediately.
117 if pos < NX_RLE_BLOCK_LEN { stream[pos] = level; pos = pos + 1 }
118 }
119 } else {
120 // run zeros, then level (which may be 0 for long-run flush).
121 pos = pos + run
122 if pos > NX_RLE_BLOCK_LEN { return NX_RLE_VERDICT_TRUNCATED }
123 if level != 0 {
124 if pos < NX_RLE_BLOCK_LEN { stream[pos] = level; pos = pos + 1 }
125 }
126 }
127 }
128 *consumed = off
129 return NX_RLE_VERDICT_OK
130}
131
132func nx_rle_verdict_is_valid(v: i64) -> i64 {
133 if v < 0 { return 0 }
134 if v >= NX_RLE_VERDICT_N { return 0 }
135 return 1
136}