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}