1#![forbid(unsafe_code)]
25#![allow(dead_code)]
26#![allow(clippy::doc_markdown)]
27#![allow(clippy::unnecessary_min_or_max)]
28#![allow(clippy::unused_self)]
29#![allow(clippy::cast_possible_truncation)]
30#![allow(clippy::trivially_copy_pass_by_ref)]
31#![allow(clippy::missing_errors_doc)]
32
33use super::sequence::SequenceHeader;
34use crate::error::{CodecError, CodecResult};
35use oximedia_io::BitReader;
36
37pub const MAX_Q_INDEX: u8 = 255;
43
44pub const MIN_Q_INDEX: u8 = 0;
46
47pub const QINDEX_RANGE: usize = 256;
49
50pub const MAX_DELTA_Q: i8 = 63;
52
53pub const MIN_DELTA_Q: i8 = -64;
55
56pub const DELTA_Q_BITS: u8 = 6;
58
59pub const NUM_QM_LEVELS: usize = 16;
61
62pub const DC_QLOOKUP: [i16; QINDEX_RANGE] = [
68 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27,
69 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48,
70 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67,
71 68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87,
72 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117,
73 118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154,
74 156, 158, 161, 164, 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205,
75 208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269,
76 272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349,
77 354, 359, 364, 369, 374, 379, 384, 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454,
78 461, 467, 475, 482, 489, 497, 505, 513, 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626,
79 640, 654, 668, 684, 700, 717, 736, 755, 775, 796, 819, 843, 869, 896, 925, 955, 988, 1022,
80 1058, 1098, 1139, 1184, 1232, 1282, 1336,
81];
82
83pub const AC_QLOOKUP: [i16; QINDEX_RANGE] = [
85 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
86 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
87 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
88 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
89 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138,
90 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188,
91 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260,
92 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366,
93 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520,
94 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743,
95 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046,
96 1066, 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423,
97 1451, 1479, 1508, 1537, 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
98];
99
100pub const DC_QLOOKUP_10: [i16; QINDEX_RANGE] = [
102 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75,
103 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, 132, 136, 140, 143, 147, 151,
104 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, 204, 208, 212, 215, 219, 223,
105 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276, 280, 283, 287, 290,
106 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, 337, 343, 350, 356, 362, 369,
107 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, 454, 460, 466, 472, 478, 484,
108 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592, 601, 609, 617, 625, 634, 644,
109 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782, 795, 807, 819, 831, 844, 856,
110 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, 1045, 1061, 1076, 1090,
111 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361,
112 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692,
113 1717, 1741, 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159,
114 2197, 2236, 2276, 2319, 2363, 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020,
115 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130,
116 5347,
117];
118
119pub const AC_QLOOKUP_10: [i16; QINDEX_RANGE] = [
121 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83,
122 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, 149, 154, 158, 163, 168,
123 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, 235, 240, 244, 249, 253,
124 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324, 328, 332, 337,
125 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, 401, 409, 417, 425, 433,
126 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, 555, 563, 571, 579, 588,
127 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749, 761, 773, 785, 797, 809,
128 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, 1058, 1078, 1098,
129 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463,
130 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967,
131 2003, 2039, 2079, 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651,
132 2703, 2755, 2807, 2859, 2915, 2971, 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591,
133 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, 4348, 4432, 4516, 4604, 4692, 4784, 4876,
134 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, 6268, 6388, 6512, 6640,
135 6768, 6900, 7036, 7172, 7312,
136];
137
138pub const DC_QLOOKUP_12: [i16; QINDEX_RANGE] = [
140 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208, 222,
141 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, 405, 421, 437, 453, 469, 484, 500, 516,
142 532, 548, 564, 580, 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814,
143 829, 844, 859, 874, 889, 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080,
144 1094, 1108, 1122, 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302,
145 1315, 1329, 1342, 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668,
146 1692, 1717, 1741, 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096,
147 2130, 2165, 2199, 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661,
148 2704, 2746, 2788, 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373,
149 3421, 3469, 3517, 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241,
150 4301, 4361, 4420, 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291,
151 5367, 5442, 5517, 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587,
152 6678, 6769, 6867, 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352,
153 8492, 8635, 8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210,
154 11482, 11776, 12081, 12409, 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356,
155 16943, 17575, 18237, 18949, 19718, 20521, 21387,
156];
157
158pub const AC_QLOOKUP_12: [i16; QINDEX_RANGE] = [
160 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230, 247,
161 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, 456, 475, 493, 511, 530, 548, 567, 586,
162 604, 623, 642, 660, 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939,
163 957, 976, 994, 1012, 1030, 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229,
164 1246, 1264, 1282, 1299, 1317, 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509,
165 1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954,
166 1987, 2020, 2052, 2085, 2118, 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508,
167 2556, 2605, 2653, 2701, 2750, 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298,
168 3362, 3426, 3491, 3555, 3619, 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390,
169 4470, 4550, 4631, 4711, 4791, 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849,
170 5961, 6073, 6185, 6297, 6410, 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867,
171 8011, 8155, 8315, 8475, 8635, 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412,
172 10604, 10812, 11020, 11228, 11437, 11661, 11885, 12109, 12333, 12573, 12813, 13053, 13309,
173 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054,
174 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, 21902,
175 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143,
176 28687, 29247,
177];
178
179#[derive(Clone, Debug, Default)]
185pub struct QuantizationParams {
186 pub base_q_idx: u8,
188 pub delta_q_y_dc: i8,
190 pub delta_q_u_dc: i8,
192 pub delta_q_u_ac: i8,
194 pub delta_q_v_dc: i8,
196 pub delta_q_v_ac: i8,
198 pub using_qmatrix: bool,
200 pub qm_y: u8,
202 pub qm_u: u8,
204 pub qm_v: u8,
206 pub delta_q_present: bool,
208 pub delta_q_res: u8,
210}
211
212impl QuantizationParams {
213 #[must_use]
215 pub fn new() -> Self {
216 Self::default()
217 }
218
219 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
225 pub fn parse(reader: &mut BitReader<'_>, seq: &SequenceHeader) -> CodecResult<Self> {
226 let mut qp = Self::new();
227
228 qp.base_q_idx = reader.read_bits(8).map_err(CodecError::Core)? as u8;
230
231 qp.delta_q_y_dc = Self::read_delta_q(reader)?;
233
234 let num_planes = if seq.color_config.mono_chrome { 1 } else { 3 };
236
237 if num_planes > 1 {
238 let diff_uv_delta = if seq.color_config.separate_uv_delta_q {
239 reader.read_bit().map_err(CodecError::Core)? != 0
240 } else {
241 false
242 };
243
244 qp.delta_q_u_dc = Self::read_delta_q(reader)?;
245 qp.delta_q_u_ac = Self::read_delta_q(reader)?;
246
247 if diff_uv_delta {
248 qp.delta_q_v_dc = Self::read_delta_q(reader)?;
249 qp.delta_q_v_ac = Self::read_delta_q(reader)?;
250 } else {
251 qp.delta_q_v_dc = qp.delta_q_u_dc;
252 qp.delta_q_v_ac = qp.delta_q_u_ac;
253 }
254 }
255
256 qp.using_qmatrix = reader.read_bit().map_err(CodecError::Core)? != 0;
258
259 if qp.using_qmatrix {
260 qp.qm_y = reader.read_bits(4).map_err(CodecError::Core)? as u8;
261 qp.qm_u = reader.read_bits(4).map_err(CodecError::Core)? as u8;
262
263 if seq.color_config.separate_uv_delta_q {
264 qp.qm_v = reader.read_bits(4).map_err(CodecError::Core)? as u8;
265 } else {
266 qp.qm_v = qp.qm_u;
267 }
268 }
269
270 Ok(qp)
271 }
272
273 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
275 fn read_delta_q(reader: &mut BitReader<'_>) -> CodecResult<i8> {
276 let delta_coded = reader.read_bit().map_err(CodecError::Core)? != 0;
277
278 if delta_coded {
279 let abs_value = reader.read_bits(DELTA_Q_BITS).map_err(CodecError::Core)? as i8;
280 let sign = reader.read_bit().map_err(CodecError::Core)? != 0;
281 if sign {
282 Ok(-abs_value)
283 } else {
284 Ok(abs_value)
285 }
286 } else {
287 Ok(0)
288 }
289 }
290
291 #[must_use]
293 pub fn y_dc_qindex(&self) -> u8 {
294 self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_y_dc))
295 }
296
297 #[must_use]
299 pub const fn y_ac_qindex(&self) -> u8 {
300 self.base_q_idx
301 }
302
303 #[must_use]
305 pub fn u_dc_qindex(&self) -> u8 {
306 self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_u_dc))
307 }
308
309 #[must_use]
311 pub fn u_ac_qindex(&self) -> u8 {
312 self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_u_ac))
313 }
314
315 #[must_use]
317 pub fn v_dc_qindex(&self) -> u8 {
318 self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_v_dc))
319 }
320
321 #[must_use]
323 pub fn v_ac_qindex(&self) -> u8 {
324 self.clamp_qindex(i16::from(self.base_q_idx) + i16::from(self.delta_q_v_ac))
325 }
326
327 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
329 fn clamp_qindex(&self, q: i16) -> u8 {
330 q.clamp(i16::from(MIN_Q_INDEX), i16::from(MAX_Q_INDEX)) as u8
331 }
332
333 #[must_use]
335 pub fn get_y_dc_dequant(&self, bit_depth: u8) -> i16 {
336 let qindex = self.y_dc_qindex();
337 get_dc_dequant(qindex, bit_depth)
338 }
339
340 #[must_use]
342 pub fn get_y_ac_dequant(&self, bit_depth: u8) -> i16 {
343 let qindex = self.y_ac_qindex();
344 get_ac_dequant(qindex, bit_depth)
345 }
346
347 #[must_use]
349 pub fn get_u_dc_dequant(&self, bit_depth: u8) -> i16 {
350 let qindex = self.u_dc_qindex();
351 get_dc_dequant(qindex, bit_depth)
352 }
353
354 #[must_use]
356 pub fn get_u_ac_dequant(&self, bit_depth: u8) -> i16 {
357 let qindex = self.u_ac_qindex();
358 get_ac_dequant(qindex, bit_depth)
359 }
360
361 #[must_use]
363 pub fn get_v_dc_dequant(&self, bit_depth: u8) -> i16 {
364 let qindex = self.v_dc_qindex();
365 get_dc_dequant(qindex, bit_depth)
366 }
367
368 #[must_use]
370 pub fn get_v_ac_dequant(&self, bit_depth: u8) -> i16 {
371 let qindex = self.v_ac_qindex();
372 get_ac_dequant(qindex, bit_depth)
373 }
374
375 #[must_use]
377 pub fn is_lossless(&self) -> bool {
378 self.base_q_idx == 0
379 && self.delta_q_y_dc == 0
380 && self.delta_q_u_dc == 0
381 && self.delta_q_u_ac == 0
382 && self.delta_q_v_dc == 0
383 && self.delta_q_v_ac == 0
384 }
385
386 #[must_use]
388 pub fn has_uv_delta(&self) -> bool {
389 self.delta_q_u_dc != 0
390 || self.delta_q_u_ac != 0
391 || self.delta_q_v_dc != 0
392 || self.delta_q_v_ac != 0
393 }
394
395 #[must_use]
397 pub const fn get_qm_level(&self, plane: usize) -> u8 {
398 match plane {
399 0 => self.qm_y,
400 1 => self.qm_u,
401 _ => self.qm_v,
402 }
403 }
404
405 #[must_use]
407 pub fn get_dc_quant(&self, plane: usize, bit_depth: u8) -> i16 {
408 match plane {
409 0 => self.get_y_dc_dequant(bit_depth),
410 1 => self.get_u_dc_dequant(bit_depth),
411 2 => self.get_v_dc_dequant(bit_depth),
412 _ => self.get_y_dc_dequant(bit_depth),
413 }
414 }
415
416 #[must_use]
418 pub fn get_ac_quant(&self, plane: usize, bit_depth: u8) -> i16 {
419 match plane {
420 0 => self.get_y_ac_dequant(bit_depth),
421 1 => self.get_u_ac_dequant(bit_depth),
422 2 => self.get_v_ac_dequant(bit_depth),
423 _ => self.get_y_ac_dequant(bit_depth),
424 }
425 }
426}
427
428#[derive(Clone, Debug, Default)]
430pub struct DeltaQState {
431 pub delta_q: i16,
433 pub resolution: u8,
435}
436
437impl DeltaQState {
438 #[must_use]
440 pub const fn new(resolution: u8) -> Self {
441 Self {
442 delta_q: 0,
443 resolution,
444 }
445 }
446
447 #[must_use]
449 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
450 pub fn apply(&self, base_q_idx: u8) -> u8 {
451 let q = i16::from(base_q_idx) + self.delta_q;
452 q.clamp(0, i16::from(MAX_Q_INDEX)) as u8
453 }
454
455 #[allow(clippy::cast_possible_wrap)]
457 pub fn update(&mut self, delta: i16) {
458 self.delta_q += delta * ((1i16) << self.resolution);
459 self.delta_q = self
460 .delta_q
461 .clamp(i16::from(MIN_DELTA_Q), i16::from(MAX_DELTA_Q));
462 }
463
464 pub fn reset(&mut self) {
466 self.delta_q = 0;
467 }
468}
469
470#[must_use]
476pub fn get_dc_dequant(qindex: u8, bit_depth: u8) -> i16 {
477 let table = match bit_depth {
478 10 => &DC_QLOOKUP_10,
479 12 => &DC_QLOOKUP_12,
480 _ => &DC_QLOOKUP,
481 };
482 table[qindex as usize]
483}
484
485#[must_use]
487pub fn get_ac_dequant(qindex: u8, bit_depth: u8) -> i16 {
488 let table = match bit_depth {
489 10 => &AC_QLOOKUP_10,
490 12 => &AC_QLOOKUP_12,
491 _ => &AC_QLOOKUP,
492 };
493 table[qindex as usize]
494}
495
496#[must_use]
498pub fn qindex_to_qp(qindex: u8) -> f32 {
499 let q = f32::from(qindex);
501 if q < 1.0 {
502 0.0
503 } else {
504 (q.log2() * 6.0) + 4.0
505 }
506}
507
508#[must_use]
510#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
511pub fn qp_to_qindex(qp: f32) -> u8 {
512 if qp < 4.0 {
513 0
514 } else {
515 let q = 2.0f32.powf((qp - 4.0) / 6.0);
516 (q.round() as u8).min(MAX_Q_INDEX)
517 }
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
526pub enum QmContentType {
527 Flat,
529 Texture,
531 Detail,
533 Screen,
535}
536
537#[derive(Clone, Debug)]
539pub struct AdaptiveQmSelection {
540 pub qm_y: Option<u8>,
542 pub qm_u: Option<u8>,
544 pub qm_v: Option<u8>,
546 pub content_type: QmContentType,
548 pub base_q_idx: u8,
550}
551
552#[must_use]
557pub fn select_adaptive_qm(content_type: QmContentType, base_q_idx: u8) -> AdaptiveQmSelection {
558 if base_q_idx < 64 {
559 return AdaptiveQmSelection {
560 qm_y: None,
561 qm_u: None,
562 qm_v: None,
563 content_type,
564 base_q_idx,
565 };
566 }
567 let base_qm: u8 = match base_q_idx {
568 64..=127 => 4 + (u16::from(base_q_idx - 64) * 4 / 63) as u8,
569 128..=191 => 8 + (u16::from(base_q_idx - 128) * 4 / 63) as u8,
570 _ => (12 + (u16::from(base_q_idx - 192) * 3 / 63)).min(15) as u8,
571 };
572 let (y_adj, uv_adj): (i8, i8) = match content_type {
573 QmContentType::Flat => (-2, -1),
574 QmContentType::Texture => (0, 0),
575 QmContentType::Detail => (2, 1),
576 QmContentType::Screen => (-3, -2),
577 };
578 let c = |b: u8, a: i8| Some((i16::from(b) + i16::from(a)).clamp(0, 15) as u8);
579 AdaptiveQmSelection {
580 qm_y: c(base_qm, y_adj),
581 qm_u: c(base_qm, uv_adj),
582 qm_v: c(base_qm, uv_adj),
583 content_type,
584 base_q_idx,
585 }
586}
587
588pub fn apply_adaptive_qm(qp: &mut QuantizationParams, sel: &AdaptiveQmSelection) {
590 match (sel.qm_y, sel.qm_u, sel.qm_v) {
591 (None, None, None) => {
592 qp.using_qmatrix = false;
593 qp.qm_y = 0;
594 qp.qm_u = 0;
595 qp.qm_v = 0;
596 }
597 _ => {
598 qp.using_qmatrix = true;
599 qp.qm_y = sel.qm_y.unwrap_or(0);
600 qp.qm_u = sel.qm_u.unwrap_or(0);
601 qp.qm_v = sel.qm_v.unwrap_or(0);
602 }
603 }
604}
605
606#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn test_quantization_params_default() {
616 let qp = QuantizationParams::default();
617 assert_eq!(qp.base_q_idx, 0);
618 assert_eq!(qp.delta_q_y_dc, 0);
619 assert!(!qp.using_qmatrix);
620 }
621
622 #[test]
623 fn test_qindex_accessors() {
624 let mut qp = QuantizationParams::default();
625 qp.base_q_idx = 100;
626 qp.delta_q_y_dc = -10;
627 qp.delta_q_u_dc = 5;
628 qp.delta_q_u_ac = -5;
629 qp.delta_q_v_dc = 10;
630 qp.delta_q_v_ac = -10;
631
632 assert_eq!(qp.y_dc_qindex(), 90);
633 assert_eq!(qp.y_ac_qindex(), 100);
634 assert_eq!(qp.u_dc_qindex(), 105);
635 assert_eq!(qp.u_ac_qindex(), 95);
636 assert_eq!(qp.v_dc_qindex(), 110);
637 assert_eq!(qp.v_ac_qindex(), 90);
638 }
639
640 #[test]
641 fn test_qindex_clamping() {
642 let mut qp = QuantizationParams::default();
643 qp.base_q_idx = 250;
644 qp.delta_q_y_dc = 20;
645
646 assert_eq!(qp.y_dc_qindex(), 255);
648
649 qp.base_q_idx = 10;
650 qp.delta_q_y_dc = -20;
651
652 assert_eq!(qp.y_dc_qindex(), 0);
654 }
655
656 #[test]
657 fn test_is_lossless() {
658 let mut qp = QuantizationParams::default();
659 assert!(qp.is_lossless());
660
661 qp.base_q_idx = 1;
662 assert!(!qp.is_lossless());
663
664 qp.base_q_idx = 0;
665 qp.delta_q_y_dc = 1;
666 assert!(!qp.is_lossless());
667 }
668
669 #[test]
670 fn test_has_uv_delta() {
671 let mut qp = QuantizationParams::default();
672 assert!(!qp.has_uv_delta());
673
674 qp.delta_q_u_dc = 5;
675 assert!(qp.has_uv_delta());
676
677 qp.delta_q_u_dc = 0;
678 qp.delta_q_v_ac = -3;
679 assert!(qp.has_uv_delta());
680 }
681
682 #[test]
683 fn test_get_qm_level() {
684 let mut qp = QuantizationParams::default();
685 qp.qm_y = 5;
686 qp.qm_u = 7;
687 qp.qm_v = 9;
688
689 assert_eq!(qp.get_qm_level(0), 5);
690 assert_eq!(qp.get_qm_level(1), 7);
691 assert_eq!(qp.get_qm_level(2), 9);
692 }
693
694 #[test]
695 fn test_dc_dequant_8bit() {
696 assert_eq!(get_dc_dequant(0, 8), 4);
698 assert_eq!(get_dc_dequant(255, 8), 1336);
699 }
700
701 #[test]
702 fn test_ac_dequant_8bit() {
703 assert_eq!(get_ac_dequant(0, 8), 4);
704 assert_eq!(get_ac_dequant(255, 8), 1828);
705 }
706
707 #[test]
708 fn test_dc_dequant_10bit() {
709 assert_eq!(get_dc_dequant(0, 10), 4);
710 assert_eq!(get_dc_dequant(255, 10), 5347);
711 }
712
713 #[test]
714 fn test_ac_dequant_10bit() {
715 assert_eq!(get_ac_dequant(0, 10), 4);
716 assert_eq!(get_ac_dequant(255, 10), 7312);
717 }
718
719 #[test]
720 fn test_dc_dequant_12bit() {
721 assert_eq!(get_dc_dequant(0, 12), 4);
722 assert_eq!(get_dc_dequant(255, 12), 21387);
723 }
724
725 #[test]
726 fn test_ac_dequant_12bit() {
727 assert_eq!(get_ac_dequant(0, 12), 4);
728 assert_eq!(get_ac_dequant(255, 12), 29247);
729 }
730
731 #[test]
732 fn test_dequant_methods() {
733 let mut qp = QuantizationParams::default();
734 qp.base_q_idx = 128;
735
736 let y_dc = qp.get_y_dc_dequant(8);
737 let y_ac = qp.get_y_ac_dequant(8);
738
739 assert!(y_dc > 0);
740 assert!(y_ac > 0);
741
742 assert_eq!(y_dc, DC_QLOOKUP[128]);
744 assert_eq!(y_ac, AC_QLOOKUP[128]);
745 }
746
747 #[test]
748 fn test_delta_q_state() {
749 let mut state = DeltaQState::new(2);
750 assert_eq!(state.delta_q, 0);
751
752 state.update(5);
753 assert_eq!(state.delta_q, 20); let q = state.apply(100);
756 assert_eq!(q, 120);
757
758 state.reset();
759 assert_eq!(state.delta_q, 0);
760 }
761
762 #[test]
763 fn test_delta_q_state_clamping() {
764 let mut state = DeltaQState::new(0);
765 state.delta_q = 50;
766
767 let q = state.apply(220);
768 assert_eq!(q, 255); }
770
771 #[test]
772 fn test_qindex_to_qp_conversion() {
773 let qp_0 = qindex_to_qp(0);
775 assert!(qp_0 < 1.0);
776
777 let qp_128 = qindex_to_qp(128);
779 let qp_64 = qindex_to_qp(64);
780 assert!(qp_128 > qp_64);
781 }
782
783 #[test]
784 fn test_qp_to_qindex_conversion() {
785 let q_low = qp_to_qindex(4.0);
787 assert!(q_low <= 10);
788
789 let q_high = qp_to_qindex(50.0);
791 assert!(q_high > q_low);
792 }
793
794 #[test]
795 fn test_constants() {
796 assert_eq!(MAX_Q_INDEX, 255);
797 assert_eq!(MIN_Q_INDEX, 0);
798 assert_eq!(QINDEX_RANGE, 256);
799 assert_eq!(NUM_QM_LEVELS, 16);
800 }
801
802 #[test]
803 fn test_lookup_table_lengths() {
804 assert_eq!(DC_QLOOKUP.len(), QINDEX_RANGE);
805 assert_eq!(AC_QLOOKUP.len(), QINDEX_RANGE);
806 assert_eq!(DC_QLOOKUP_10.len(), QINDEX_RANGE);
807 assert_eq!(AC_QLOOKUP_10.len(), QINDEX_RANGE);
808 assert_eq!(DC_QLOOKUP_12.len(), QINDEX_RANGE);
809 assert_eq!(AC_QLOOKUP_12.len(), QINDEX_RANGE);
810 }
811
812 #[test]
813 fn test_lookup_table_monotonic() {
814 for i in 1..QINDEX_RANGE {
816 assert!(DC_QLOOKUP[i] >= DC_QLOOKUP[i - 1]);
817 assert!(AC_QLOOKUP[i] >= AC_QLOOKUP[i - 1]);
818 }
819 }
820
821 #[test]
826 fn test_adaptive_qm_disabled_below_q64() {
827 for q in [0u8, 32, 63] {
828 let sel = select_adaptive_qm(QmContentType::Texture, q);
829 assert!(sel.qm_y.is_none(), "QM must be disabled for q_idx={q}");
830 assert!(sel.qm_u.is_none());
831 assert!(sel.qm_v.is_none());
832 }
833 }
834
835 #[test]
836 fn test_adaptive_qm_enabled_above_q64() {
837 for q in [64u8, 128, 191, 255] {
838 let sel = select_adaptive_qm(QmContentType::Texture, q);
839 assert!(sel.qm_y.is_some(), "QM must be enabled for q_idx={q}");
840 }
841 }
842
843 #[test]
844 fn test_adaptive_qm_flat_uses_lower_level_than_detail() {
845 let q = 128u8;
846 let flat = select_adaptive_qm(QmContentType::Flat, q);
847 let detail = select_adaptive_qm(QmContentType::Detail, q);
848 let flat_y = flat.qm_y.unwrap_or(0);
849 let detail_y = detail.qm_y.unwrap_or(0);
850 assert!(
851 flat_y < detail_y || flat_y == detail_y,
852 "Flat should use QM level <= Detail: flat={flat_y}, detail={detail_y}"
853 );
854 }
855
856 #[test]
857 fn test_adaptive_qm_screen_content_low_level() {
858 let q = 128u8;
860 let screen = select_adaptive_qm(QmContentType::Screen, q);
861 let texture = select_adaptive_qm(QmContentType::Texture, q);
862 let screen_y = screen.qm_y.unwrap_or(0);
863 let texture_y = texture.qm_y.unwrap_or(0);
864 assert!(
865 screen_y <= texture_y,
866 "Screen content must use QM level <= Texture: screen={screen_y}, texture={texture_y}"
867 );
868 }
869
870 #[test]
871 fn test_adaptive_qm_levels_in_valid_range() {
872 for q in [64u8, 96, 128, 160, 192, 220, 255] {
873 for ct in [
874 QmContentType::Flat,
875 QmContentType::Texture,
876 QmContentType::Detail,
877 QmContentType::Screen,
878 ] {
879 let sel = select_adaptive_qm(ct, q);
880 if let Some(y) = sel.qm_y {
881 assert!(y <= 15, "qm_y={y} must be in [0,15]");
882 }
883 if let Some(u) = sel.qm_u {
884 assert!(u <= 15, "qm_u={u} must be in [0,15]");
885 }
886 if let Some(v) = sel.qm_v {
887 assert!(v <= 15, "qm_v={v} must be in [0,15]");
888 }
889 }
890 }
891 }
892
893 #[test]
894 fn test_apply_adaptive_qm_enables_matrix() {
895 let mut qp = QuantizationParams::default();
896 let sel = select_adaptive_qm(QmContentType::Texture, 128);
897 apply_adaptive_qm(&mut qp, &sel);
898 assert!(
899 qp.using_qmatrix,
900 "apply_adaptive_qm must set using_qmatrix=true for q>=64"
901 );
902 }
903
904 #[test]
905 fn test_apply_adaptive_qm_disables_for_low_q() {
906 let mut qp = QuantizationParams::default();
907 let sel = select_adaptive_qm(QmContentType::Flat, 32);
908 apply_adaptive_qm(&mut qp, &sel);
909 assert!(
910 !qp.using_qmatrix,
911 "apply_adaptive_qm must set using_qmatrix=false for q<64"
912 );
913 }
914
915 #[test]
916 fn test_adaptive_qm_content_type_preserved() {
917 let ct = QmContentType::Screen;
918 let sel = select_adaptive_qm(ct, 200);
919 assert_eq!(
920 sel.content_type, ct,
921 "Content type must be preserved in selection result"
922 );
923 assert_eq!(sel.base_q_idx, 200);
924 }
925
926 #[test]
927 fn test_adaptive_qm_monotone_with_q() {
928 let ct = QmContentType::Texture;
930 let low_q = select_adaptive_qm(ct, 80);
931 let high_q = select_adaptive_qm(ct, 240);
932 let low_y = low_q.qm_y.unwrap_or(0);
933 let high_y = high_q.qm_y.unwrap_or(0);
934 assert!(
935 high_y >= low_y,
936 "Higher Q should give >= QM level: q=80 → {low_y}, q=240 → {high_y}"
937 );
938 }
939}