code wiki / (root) / nx_rle.nx

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}