code wiki / (root) / nx_quant_table.nx

nx_quant_table.nx source

↩ module page · 136 lines · 5167 B

1// nx_quant_table.nx -- perceptual quantisation matrices. The two 2// 64-entry tables from ITU-T Recommendation T.81 (JPEG) Annex K -- 3// fully public since 1992 -- give a 30-50% rate-distortion advantage 4// over uniform quantisation at moderate quality on natural images. 5// 6// Math: each post-DCT coefficient at zig-zag position (r,c) is 7// divided by qt[r*8+c], where qt is the quant matrix. Higher 8// divisors for high-frequency coefficients aggressively discard 9// the perceptually-unimportant detail; small divisors for the 10// low-frequency corner preserve structure. 11// 12// Quality factor Q in 1..100 maps to a scale factor: 13// Q >= 50: scale = 200 - 2 * Q (Q=50 -> 100; Q=100 -> 0) 14// Q < 50: scale = 5000 / Q (Q=1 -> 5000; Q=49 -> 102) 15// Then qt[i] = clamp((base[i] * scale + 50) / 100, 1, 255). 16// 17// genealogy_id: itu_t_t_81_1992_annex_k + jpeg_iso_10918_1992 + 18// nx_dct8_q10 19// lineage_id: nishi_quant_table_q10 20 21// nx_safety_envelope: 22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 23// sil_target: SIL1 24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 25// verdict: NOT_YET_EVALUATED 26 27import "nx_syscalls.nx" 28const NX_MAGIC_5000: i64 = 5000 29 30const NX_QT_VERDICT_UNKNOWN: i64 = 0 31const NX_QT_VERDICT_OK: i64 = 1 32const NX_QT_VERDICT_BAD_PARAMS: i64 = 2 33const NX_QT_VERDICT_N: i64 = 3 34 35// Annex K.1 (luma) base matrix. 36func _qt_luma_base(out: *i64) -> i64 { 37 out[ 0]=16; out[ 1]=11; out[ 2]=10; out[ 3]=16 38 out[ 4]=24; out[ 5]=40; out[ 6]=51; out[ 7]=61 39 out[ 8]=12; out[ 9]=12; out[10]=14; out[11]=19 40 out[12]=26; out[13]=58; out[14]=60; out[15]=55 41 out[16]=14; out[17]=13; out[18]=16; out[19]=24 42 out[20]=40; out[21]=57; out[22]=69; out[23]=56 43 out[24]=14; out[25]=17; out[26]=22; out[27]=29 44 out[28]=51; out[29]=87; out[30]=80; out[31]=62 45 out[32]=18; out[33]=22; out[34]=37; out[35]=56 46 out[36]=68; out[37]=109; out[38]=103; out[39]=77 47 out[40]=24; out[41]=35; out[42]=55; out[43]=64 48 out[44]=81; out[45]=104; out[46]=113; out[47]=92 49 out[48]=49; out[49]=64; out[50]=78; out[51]=87 50 out[52]=103; out[53]=121; out[54]=120; out[55]=101 51 out[56]=72; out[57]=92; out[58]=95; out[59]=98 52 out[60]=112; out[61]=100; out[62]=103; out[63]=99 53 return NX_QT_VERDICT_OK 54} 55 56// Annex K.2 (chroma) base matrix. 57func _qt_chroma_base(out: *i64) -> i64 { 58 out[ 0]=17; out[ 1]=18; out[ 2]=24; out[ 3]=47 59 out[ 4]=99; out[ 5]=99; out[ 6]=99; out[ 7]=99 60 out[ 8]=18; out[ 9]=21; out[10]=26; out[11]=66 61 out[12]=99; out[13]=99; out[14]=99; out[15]=99 62 out[16]=24; out[17]=26; out[18]=56; out[19]=99 63 out[20]=99; out[21]=99; out[22]=99; out[23]=99 64 out[24]=47; out[25]=66; out[26]=99; out[27]=99 65 out[28]=99; out[29]=99; out[30]=99; out[31]=99 66 out[32]=99; out[33]=99; out[34]=99; out[35]=99 67 out[36]=99; out[37]=99; out[38]=99; out[39]=99 68 out[40]=99; out[41]=99; out[42]=99; out[43]=99 69 out[44]=99; out[45]=99; out[46]=99; out[47]=99 70 out[48]=99; out[49]=99; out[50]=99; out[51]=99 71 out[52]=99; out[53]=99; out[54]=99; out[55]=99 72 out[56]=99; out[57]=99; out[58]=99; out[59]=99 73 out[60]=99; out[61]=99; out[62]=99; out[63]=99 74 return NX_QT_VERDICT_OK 75} 76 77// Scale a base matrix by quality factor. qt[i] = clamp((base[i] * s + 50) / 100, 1, 255). 78func _qt_scale(base: *i64, scale: i64, qt_out: *i64) -> i64 { 79 var i: i64 = 0 80 while i < 64 { 81 var v: i64 = (base[i] * scale + 50) / 100 82 if v < 1 { v = 1 } 83 if v > 255 { v = 255 } 84 qt_out[i] = v 85 i = i + 1 86 } 87 return NX_QT_VERDICT_OK 88} 89 90// Compute scale factor from a 1..100 quality. Returns the scale. 91func _qt_quality_to_scale(q: i64) -> i64 { 92 var qq: i64 = q 93 if qq < 1 { qq = 1 } 94 if qq > 100 { qq = 100 } 95 if qq >= 50 { return 200 - 2 * qq } 96 return NX_MAGIC_5000 / qq 97} 98 99// Build the luma perceptual quant table for quality factor `q` (1..100). 100// Caller pre-allocates `qt_out` (64 i64). 101func nx_qt_luma(qt_out: *i64, quality: i64) -> i64 { 102 if quality < 1 { return NX_QT_VERDICT_BAD_PARAMS } 103 if quality > 100 { return NX_QT_VERDICT_BAD_PARAMS } 104 let base: *i64 = sys_mmap(64 * 8) as *i64 105 _qt_luma_base(base) 106 let scale: i64 = _qt_quality_to_scale(quality) 107 return _qt_scale(base, scale, qt_out) 108} 109 110// Same for chroma. 111func nx_qt_chroma(qt_out: *i64, quality: i64) -> i64 { 112 if quality < 1 { return NX_QT_VERDICT_BAD_PARAMS } 113 if quality > 100 { return NX_QT_VERDICT_BAD_PARAMS } 114 let base: *i64 = sys_mmap(64 * 8) as *i64 115 _qt_chroma_base(base) 116 let scale: i64 = _qt_quality_to_scale(quality) 117 return _qt_scale(base, scale, qt_out) 118} 119 120// Uniform table for fallback / comparison. 121func nx_qt_uniform(qt_out: *i64, step: i64) -> i64 { 122 if step < 1 { return NX_QT_VERDICT_BAD_PARAMS } 123 if step > 255 { return NX_QT_VERDICT_BAD_PARAMS } 124 var i: i64 = 0 125 while i < 64 { 126 qt_out[i] = step 127 i = i + 1 128 } 129 return NX_QT_VERDICT_OK 130} 131 132func nx_qt_verdict_is_valid(v: i64) -> i64 { 133 if v < 0 { return 0 } 134 if v >= NX_QT_VERDICT_N { return 0 } 135 return 1 136}