1use crate::consts::{
10 quality_to_distance, BASE_QUANT_MATRIX_STD, BASE_QUANT_MATRIX_XYB, BASE_QUANT_MATRIX_YCBCR,
11 DCT_BLOCK_SIZE, GLOBAL_SCALE_XYB, GLOBAL_SCALE_YCBCR,
12};
13use crate::types::ColorSpace;
14
15pub use crate::types::QuantTable;
17
18pub const FREQUENCY_EXPONENT: [f32; DCT_BLOCK_SIZE] = [
23 1.00, 0.51, 0.67, 0.74, 1.00, 1.00, 1.00, 1.00, 0.51, 0.66, 0.69, 0.87, 1.00, 1.00, 1.00, 1.00,
24 0.67, 0.69, 0.84, 0.83, 0.96, 1.00, 1.00, 1.00, 0.74, 0.87, 0.83, 1.00, 1.00, 0.91, 0.91, 1.00,
25 1.00, 1.00, 0.96, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.91, 1.00, 1.00, 1.00, 1.00,
26 1.00, 1.00, 1.00, 0.91, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00,
27];
28
29pub const DIST_THRESHOLD: f32 = 1.5;
31
32const DIST_HQ: f32 = 1.0;
34const DIST_LQ: f32 = 3.0;
35
36#[rustfmt::skip]
40pub const ZERO_BIAS_MUL_YCBCR_LQ: [f32; 192] = [
41 0.0000, 0.0568, 0.3880, 0.6190, 0.6190, 0.4490, 0.4490, 0.6187,
43 0.0568, 0.5829, 0.6189, 0.6190, 0.6190, 0.7190, 0.6190, 0.6189,
44 0.3880, 0.6189, 0.6190, 0.6190, 0.6190, 0.6190, 0.6187, 0.6100,
45 0.6190, 0.6190, 0.6190, 0.6190, 0.5890, 0.3839, 0.7160, 0.6190,
46 0.6190, 0.6190, 0.6190, 0.5890, 0.6190, 0.3880, 0.5860, 0.4790,
47 0.4490, 0.7190, 0.6190, 0.3839, 0.3880, 0.6190, 0.6190, 0.6190,
48 0.4490, 0.6190, 0.6187, 0.7160, 0.5860, 0.6190, 0.6204, 0.6190,
49 0.6187, 0.6189, 0.6100, 0.6190, 0.4790, 0.6190, 0.6190, 0.3480,
50 0.0000, 1.1640, 0.9373, 1.1319, 0.8016, 0.9136, 1.1530, 0.9430,
52 1.1640, 0.9188, 0.9160, 1.1980, 1.1830, 0.9758, 0.9430, 0.9430,
53 0.9373, 0.9160, 0.8430, 1.1720, 0.7083, 0.9430, 0.9430, 0.9430,
54 1.1319, 1.1980, 1.1720, 1.1490, 0.8547, 0.9430, 0.9430, 0.9430,
55 0.8016, 1.1830, 0.7083, 0.8547, 0.9430, 0.9430, 0.9430, 0.9430,
56 0.9136, 0.9758, 0.9430, 0.9430, 0.9430, 0.9430, 0.9430, 0.9430,
57 1.1530, 0.9430, 0.9430, 0.9430, 0.9430, 0.9430, 0.9430, 0.9480,
58 0.9430, 0.9430, 0.9430, 0.9430, 0.9430, 0.9430, 0.9480, 0.9430,
59 0.0000, 1.3190, 0.4308, 0.4460, 0.0661, 0.0660, 0.2660, 0.2960,
61 1.3190, 0.3280, 0.3093, 0.0750, 0.0505, 0.1594, 0.3060, 0.2113,
62 0.4308, 0.3093, 0.3060, 0.1182, 0.0500, 0.3060, 0.3915, 0.2426,
63 0.4460, 0.0750, 0.1182, 0.0512, 0.0500, 0.2130, 0.3930, 0.1590,
64 0.0661, 0.0505, 0.0500, 0.0500, 0.3055, 0.3360, 0.5148, 0.5403,
65 0.0660, 0.1594, 0.3060, 0.2130, 0.3360, 0.5060, 0.5874, 0.3060,
66 0.2660, 0.3060, 0.3915, 0.3930, 0.5148, 0.5874, 0.3060, 0.3060,
67 0.2960, 0.2113, 0.2426, 0.1590, 0.5403, 0.3060, 0.3060, 0.3060,
68];
69
70#[rustfmt::skip]
74pub const ZERO_BIAS_MUL_YCBCR_HQ: [f32; 192] = [
75 0.0000, 0.0044, 0.2521, 0.6547, 0.8161, 0.6130, 0.8841, 0.8155,
77 0.0044, 0.6831, 0.6553, 0.6295, 0.7848, 0.7843, 0.8474, 0.7836,
78 0.2521, 0.6553, 0.7834, 0.7829, 0.8161, 0.8072, 0.7743, 0.9242,
79 0.6547, 0.6295, 0.7829, 0.8654, 0.7829, 0.6986, 0.7818, 0.7726,
80 0.8161, 0.7848, 0.8161, 0.7829, 0.7471, 0.7827, 0.7843, 0.7653,
81 0.6130, 0.7843, 0.8072, 0.6986, 0.7827, 0.7848, 0.9508, 0.7653,
82 0.8841, 0.8474, 0.7743, 0.7818, 0.7843, 0.9508, 0.7839, 0.8437,
83 0.8155, 0.7836, 0.9242, 0.7726, 0.7653, 0.7653, 0.8437, 0.7819,
84 0.0000, 1.0816, 1.0556, 1.2876, 1.1554, 1.1567, 1.8851, 0.5488,
86 1.0816, 1.1537, 1.1850, 1.0712, 1.1671, 2.0719, 1.0544, 1.4764,
87 1.0556, 1.1850, 1.2870, 1.1981, 1.8181, 1.2618, 1.0564, 1.1191,
88 1.2876, 1.0712, 1.1981, 1.4753, 2.0609, 1.0564, 1.2645, 1.0564,
89 1.1554, 1.1671, 1.8181, 2.0609, 0.7324, 1.1163, 0.8464, 1.0564,
90 1.1567, 2.0719, 1.2618, 1.0564, 1.1163, 1.0040, 1.0564, 1.0564,
91 1.8851, 1.0544, 1.0564, 1.2645, 0.8464, 1.0564, 1.0564, 1.0564,
92 0.5488, 1.4764, 1.1191, 1.0564, 1.0564, 1.0564, 1.0564, 1.0564,
93 0.0000, 0.5392, 0.6659, 0.8968, 0.6829, 0.6328, 0.5802, 0.4836,
95 0.5392, 0.6746, 0.6760, 0.6102, 0.6015, 0.6958, 0.7327, 0.4897,
96 0.6659, 0.6760, 0.6957, 0.6543, 0.4396, 0.6330, 0.7081, 0.2583,
97 0.8968, 0.6102, 0.6543, 0.5913, 0.6457, 0.5828, 0.5139, 0.3565,
98 0.6829, 0.6015, 0.4396, 0.6457, 0.5633, 0.4263, 0.6371, 0.5949,
99 0.6328, 0.6958, 0.6330, 0.5828, 0.4263, 0.2847, 0.2909, 0.6629,
100 0.5802, 0.7327, 0.7081, 0.5139, 0.6371, 0.2909, 0.6644, 0.6644,
101 0.4836, 0.4897, 0.2583, 0.3565, 0.5949, 0.6629, 0.6644, 0.6644,
102];
103
104pub const ZERO_BIAS_OFFSET_YCBCR_DC: [f32; 3] = [0.0, 0.0, 0.0];
107
108pub const ZERO_BIAS_OFFSET_YCBCR_AC: [f32; 3] = [0.59082, 0.58146, 0.57988];
111
112#[derive(Debug, Clone)]
117pub struct ZeroBiasParams {
118 pub mul: [f32; DCT_BLOCK_SIZE],
120 pub offset: [f32; DCT_BLOCK_SIZE],
122}
123
124impl Default for ZeroBiasParams {
125 fn default() -> Self {
126 Self {
130 mul: [0.0; DCT_BLOCK_SIZE],
131 offset: [0.0; DCT_BLOCK_SIZE],
132 }
133 }
134}
135
136impl ZeroBiasParams {
137 #[must_use]
148 pub fn for_ycbcr(distance: f32, component: usize) -> Self {
149 let c = component.min(2);
150
151 let mix_lq = ((distance - DIST_HQ) / (DIST_LQ - DIST_HQ)).clamp(0.0, 1.0);
153 let mix_hq = 1.0 - mix_lq;
154
155 let mut mul = [0.0f32; DCT_BLOCK_SIZE];
156 let mut offset = [0.0f32; DCT_BLOCK_SIZE];
157
158 for k in 0..DCT_BLOCK_SIZE {
159 let lq = ZERO_BIAS_MUL_YCBCR_LQ[c * DCT_BLOCK_SIZE + k];
160 let hq = ZERO_BIAS_MUL_YCBCR_HQ[c * DCT_BLOCK_SIZE + k];
161 mul[k] = mix_lq * lq + mix_hq * hq;
162
163 offset[k] = if k == 0 {
164 ZERO_BIAS_OFFSET_YCBCR_DC[c]
165 } else {
166 ZERO_BIAS_OFFSET_YCBCR_AC[c]
167 };
168 }
169
170 Self { mul, offset }
171 }
172
173 #[must_use]
177 pub fn for_ycbcr_simple(component: usize) -> Self {
178 let c = component.min(2);
179
180 let mul = [0.0f32; DCT_BLOCK_SIZE]; let mut offset = [0.0f32; DCT_BLOCK_SIZE];
183 for k in 0..DCT_BLOCK_SIZE {
184 offset[k] = if k == 0 {
185 ZERO_BIAS_OFFSET_YCBCR_DC[c]
186 } else {
187 ZERO_BIAS_OFFSET_YCBCR_AC[c]
188 };
189 }
190
191 Self { mul, offset }
192 }
193
194 #[inline]
198 #[must_use]
199 pub fn apply(&self, coeff: f32, k: usize, quant: f32) -> f32 {
200 let threshold = (self.mul[k] + self.offset[k]) * quant;
201 if coeff.abs() < threshold {
202 0.0
203 } else {
204 coeff
205 }
206 }
207}
208
209#[inline]
222#[must_use]
223pub fn distance_to_scale(distance: f32, freq_idx: usize) -> f32 {
224 if distance < DIST_THRESHOLD {
225 return distance;
226 }
227 let exp = FREQUENCY_EXPONENT[freq_idx];
228 let mul = DIST_THRESHOLD.powf(1.0 - exp);
229 (0.5 * distance).max(mul * distance.powf(exp))
230}
231
232#[inline]
234#[must_use]
235pub fn scale_to_distance(scale: f32, freq_idx: usize) -> f32 {
236 if scale < DIST_THRESHOLD {
237 return scale;
238 }
239 let exp = 1.0 / FREQUENCY_EXPONENT[freq_idx];
240 let mul = DIST_THRESHOLD.powf(1.0 - exp);
241 (2.0 * scale).min(mul * scale.powf(exp))
242}
243
244#[must_use]
254pub fn quant_vals_to_distance(
255 y_quant: &QuantTable,
256 cb_quant: &QuantTable,
257 cr_quant: &QuantTable,
258) -> f32 {
259 use crate::consts::{BASE_QUANT_MATRIX_YCBCR, GLOBAL_SCALE_YCBCR};
260
261 const DIST_MAX: f32 = 10000.0;
262 const QUANT_MAX: u16 = 255; let global_scale = GLOBAL_SCALE_YCBCR;
265
266 let mut dist_min = 0.0f32;
267 let mut dist_max = DIST_MAX;
268
269 let quant_tables = [y_quant, cb_quant, cr_quant];
271
272 for (c, quant) in quant_tables.iter().enumerate() {
273 let base_idx = c * DCT_BLOCK_SIZE;
274 let base_qm = &BASE_QUANT_MATRIX_YCBCR[base_idx..base_idx + DCT_BLOCK_SIZE];
275
276 for k in 0..DCT_BLOCK_SIZE {
277 let mut dmin = 0.0f32;
278 let mut dmax = DIST_MAX;
279 let invq = 1.0 / base_qm[k] / global_scale;
280 let qval = quant.values[k];
281
282 if qval > 1 {
283 let scale_min = (qval as f32 - 0.5) * invq;
284 dmin = scale_to_distance(scale_min, k);
285 }
286 if qval < QUANT_MAX {
287 let scale_max = (qval as f32 + 0.5) * invq;
288 dmax = scale_to_distance(scale_max, k);
289 }
290
291 if dmin <= dist_max {
292 dist_min = dist_min.max(dmin);
293 }
294 if dmax >= dist_min {
295 dist_max = dist_max.min(dmax);
296 }
297 }
298 }
299
300 if dist_min == 0.0 {
302 dist_max
303 } else if dist_max >= DIST_MAX {
304 dist_min
305 } else {
306 0.5 * (dist_min + dist_max)
307 }
308}
309
310pub const STD_LUMINANCE_QUANT: [u16; DCT_BLOCK_SIZE] = [
313 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56,
314 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113,
315 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99,
316];
317
318pub const STD_CHROMINANCE_QUANT: [u16; DCT_BLOCK_SIZE] = [
321 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99,
322 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
323 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
324];
325
326#[derive(Debug, Clone, Copy, PartialEq)]
328#[non_exhaustive]
329pub enum Quality {
330 Traditional(f32),
332 Distance(f32),
335}
336
337impl Default for Quality {
338 fn default() -> Self {
339 Self::Traditional(90.0)
340 }
341}
342
343impl Quality {
344 #[must_use]
346 pub fn from_quality(q: f32) -> Self {
347 Self::Traditional(q.clamp(1.0, 100.0))
348 }
349
350 #[must_use]
352 pub fn from_distance(d: f32) -> Self {
353 Self::Distance(d.max(0.0))
354 }
355
356 #[must_use]
358 pub fn to_distance(self) -> f32 {
359 match self {
360 Self::Traditional(q) => quality_to_distance(q as i32),
361 Self::Distance(d) => d,
362 }
363 }
364
365 #[must_use]
367 pub fn to_quality(self) -> f32 {
368 match self {
369 Self::Traditional(q) => q,
370 Self::Distance(d) => distance_to_quality(d),
371 }
372 }
373
374 #[must_use]
376 pub fn to_linear(self) -> f32 {
377 let d = self.to_distance();
378 if d <= 0.1 {
380 1.0
381 } else {
382 (0.1 / d).min(1.0)
383 }
384 }
385}
386
387fn distance_to_quality(distance: f32) -> f32 {
389 if distance <= 0.0 {
391 100.0
392 } else if distance >= 15.0 {
393 1.0
394 } else {
395 100.0 - (distance * 6.6).min(99.0)
397 }
398}
399
400#[must_use]
409pub fn generate_quant_table(
410 quality: Quality,
411 component: usize,
412 color_space: ColorSpace,
413 use_xyb: bool,
414 is_420: bool,
415) -> QuantTable {
416 let distance = quality.to_distance();
417
418 if use_xyb {
419 generate_xyb_quant_table(distance, component)
420 } else {
421 generate_standard_quant_table(distance, component, color_space, is_420)
422 }
423}
424
425fn generate_xyb_quant_table(distance: f32, component: usize) -> QuantTable {
430 let mut values = [0u16; DCT_BLOCK_SIZE];
431
432 let base_idx = component.min(2) * DCT_BLOCK_SIZE;
434 let base = &BASE_QUANT_MATRIX_XYB[base_idx..base_idx + DCT_BLOCK_SIZE];
435
436 for (i, &base_val) in base.iter().enumerate() {
437 let scale = distance_to_scale(distance, i) * GLOBAL_SCALE_XYB;
439 let q = (base_val * scale).round();
440 values[i] = (q as u16).clamp(1, 255);
442 }
443
444 QuantTable {
445 values,
446 precision: 0, }
448}
449
450fn generate_standard_quant_table(
455 distance: f32,
456 component: usize,
457 color_space: ColorSpace,
458 is_420: bool,
459) -> QuantTable {
460 use crate::consts::{GLOBAL_SCALE_420, K420_RESCALE};
461
462 let mut values = [0u16; DCT_BLOCK_SIZE];
463
464 let (base, mut global_scale) = if color_space == ColorSpace::YCbCr {
466 let base_idx = component.min(2) * DCT_BLOCK_SIZE;
467 (
468 &BASE_QUANT_MATRIX_YCBCR[base_idx..base_idx + DCT_BLOCK_SIZE],
469 GLOBAL_SCALE_YCBCR,
470 )
471 } else {
472 let base_idx = if component == 0 { 0 } else { DCT_BLOCK_SIZE };
474 (
475 &BASE_QUANT_MATRIX_STD[base_idx..base_idx + DCT_BLOCK_SIZE],
476 1.0,
477 )
478 };
479
480 if is_420 && color_space == ColorSpace::YCbCr {
483 global_scale *= GLOBAL_SCALE_420;
484 }
485
486 let is_chroma_420 = is_420 && color_space == ColorSpace::YCbCr && component > 0;
488
489 for (i, &base_val) in base.iter().enumerate() {
490 let mut scale = distance_to_scale(distance, i) * global_scale;
492
493 if is_chroma_420 {
496 scale *= K420_RESCALE[i];
497 }
498
499 let q = (base_val * scale).round();
500 values[i] = (q as u16).clamp(1, 255);
501 }
502
503 QuantTable {
504 values,
505 precision: 0,
506 }
507}
508
509#[must_use]
515pub fn generate_standard_jpeg_table(quality: f32, is_chrominance: bool) -> QuantTable {
516 let base_table = if is_chrominance {
517 &STD_CHROMINANCE_QUANT
518 } else {
519 &STD_LUMINANCE_QUANT
520 };
521
522 let quality = quality.clamp(1.0, 100.0);
524 let scale = if quality < 50.0 {
525 5000.0 / quality
526 } else {
527 200.0 - quality * 2.0
528 };
529
530 let mut values = [0u16; DCT_BLOCK_SIZE];
531 for (i, &base) in base_table.iter().enumerate() {
532 let q = ((base as f32 * scale + 50.0) / 100.0).round();
533 values[i] = (q as u16).clamp(1, 255);
534 }
535
536 QuantTable {
537 values,
538 precision: 0,
539 }
540}
541
542#[inline]
544#[must_use]
545pub fn quantize(coeff: f32, quant: u16) -> i16 {
546 let q = quant as f32;
547 (coeff / q).round() as i16
548}
549
550#[inline]
552#[must_use]
553pub fn dequantize(quantized: i16, quant: u16) -> f32 {
554 quantized as f32 * quant as f32
555}
556
557pub fn quantize_block(
559 coeffs: &[f32; DCT_BLOCK_SIZE],
560 quant: &[u16; DCT_BLOCK_SIZE],
561) -> [i16; DCT_BLOCK_SIZE] {
562 let mut result = [0i16; DCT_BLOCK_SIZE];
563 for i in 0..DCT_BLOCK_SIZE {
564 result[i] = quantize(coeffs[i], quant[i]);
565 }
566 result
567}
568
569#[cfg(debug_assertions)]
581#[allow(dead_code)] static ZERO_BIAS_DEBUG: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
583#[cfg(debug_assertions)]
584#[allow(dead_code)] static ZERO_BIAS_ZEROS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
586
587pub fn quantize_block_with_zero_bias(
588 coeffs: &[f32; DCT_BLOCK_SIZE],
589 quant: &[u16; DCT_BLOCK_SIZE],
590 zero_bias: &ZeroBiasParams,
591 aq_strength: f32,
592) -> [i16; DCT_BLOCK_SIZE] {
593 let mut result = [0i16; DCT_BLOCK_SIZE];
594
595 for k in 0..DCT_BLOCK_SIZE {
596 let q = quant[k] as f32;
597 let qval = coeffs[k] / q;
598
599 let threshold = zero_bias.offset[k] + zero_bias.mul[k] * aq_strength;
604
605 if qval.abs() >= threshold {
606 result[k] = qval.round() as i16;
607 }
608 }
610 result
611}
612
613pub fn quantize_block_compare(
615 coeffs: &[f32; DCT_BLOCK_SIZE],
616 quant: &[u16; DCT_BLOCK_SIZE],
617 zero_bias: &ZeroBiasParams,
618 aq_strength: f32,
619) -> ([i16; DCT_BLOCK_SIZE], usize) {
620 let mut result = [0i16; DCT_BLOCK_SIZE];
621 let mut zeros_from_bias = 0usize;
622 for k in 0..DCT_BLOCK_SIZE {
623 let q = quant[k] as f32;
624 let qval = coeffs[k] / q;
625 let simple_result = qval.round() as i16;
626 let threshold = zero_bias.offset[k] + zero_bias.mul[k] * aq_strength;
627
628 if qval.abs() >= threshold {
629 result[k] = simple_result;
630 } else {
631 if simple_result != 0 {
633 zeros_from_bias += 1;
634 }
635 }
636 }
637 (result, zeros_from_bias)
638}
639
640pub fn dequantize_block(
642 quantized: &[i16; DCT_BLOCK_SIZE],
643 quant: &[u16; DCT_BLOCK_SIZE],
644) -> [f32; DCT_BLOCK_SIZE] {
645 let mut result = [0.0f32; DCT_BLOCK_SIZE];
646 for i in 0..DCT_BLOCK_SIZE {
647 result[i] = dequantize(quantized[i], quant[i]);
648 }
649 result
650}
651
652pub fn dequantize_block_with_bias(
662 quantized: &[i16; DCT_BLOCK_SIZE],
663 quant: &[u16; DCT_BLOCK_SIZE],
664 biases: &[f32; DCT_BLOCK_SIZE],
665) -> [f32; DCT_BLOCK_SIZE] {
666 let mut result = [0.0f32; DCT_BLOCK_SIZE];
667 for k in 0..DCT_BLOCK_SIZE {
668 let q = quantized[k];
669 if q == 0 {
670 result[k] = 0.0;
672 } else {
673 let bias = biases[k];
677 let biased_q = if q > 0 {
678 q as f32 - bias
679 } else {
680 q as f32 + bias
681 };
682 result[k] = biased_q * quant[k] as f32;
683 }
684 }
685 result
686}
687
688#[derive(Debug, Clone)]
690pub struct DequantBiasStats {
691 pub nonzeros: Vec<i32>,
693 pub sumabs: Vec<i32>,
695 pub num_blocks: Vec<usize>,
697}
698
699impl DequantBiasStats {
700 #[must_use]
702 pub fn new(num_components: usize) -> Self {
703 let size = num_components * DCT_BLOCK_SIZE;
704 Self {
705 nonzeros: vec![0; size],
706 sumabs: vec![0; size],
707 num_blocks: vec![0; num_components],
708 }
709 }
710
711 pub fn gather_block(&mut self, component: usize, coeffs: &[i16; DCT_BLOCK_SIZE]) {
713 let offset = component * DCT_BLOCK_SIZE;
714 for (k, &coeff) in coeffs.iter().enumerate() {
715 let abs_coeff = coeff.abs() as i32;
716 if abs_coeff > 0 {
717 self.nonzeros[offset + k] += 1;
718 self.sumabs[offset + k] += abs_coeff;
719 }
720 }
721 self.num_blocks[component] += 1;
722 }
723
724 #[must_use]
729 pub fn compute_biases(&self, component: usize) -> [f32; DCT_BLOCK_SIZE] {
730 let mut biases = [0.0f32; DCT_BLOCK_SIZE];
731 let offset = component * DCT_BLOCK_SIZE;
732 let num_blocks = self.num_blocks[component];
733
734 if num_blocks == 0 {
735 return biases;
736 }
737
738 biases[0] = 0.0;
740
741 for k in 1..DCT_BLOCK_SIZE {
743 let n1 = self.nonzeros[offset + k];
744 if n1 == 0 {
745 biases[k] = 0.5;
747 continue;
748 }
749
750 let n = num_blocks as f32;
754 let n1_f = n1 as f32;
755 let n0 = num_blocks as f32 - n1_f; let s = self.sumabs[offset + k] as f32;
757
758 let a: f64 = 4.0 * s as f64 + 2.0 * n as f64;
764 let b: f64 = 4.0 * s as f64 - 2.0 * n1_f as f64;
765 let n0_f64 = n0 as f64;
766
767 let gamma: f32 = ((-n0_f64 + (n0_f64 * n0_f64 + a * b).sqrt()) / a) as f32;
768 let gamma2: f32 = gamma * gamma;
769
770 let gamma2_f64 = gamma2 as f64;
773 let gamma_f64 = gamma as f64;
774 biases[k] =
775 (0.5 * (((1.0 + gamma2_f64) / (1.0 - gamma2_f64)) + 1.0 / gamma_f64.ln())) as f32;
776 }
777
778 biases
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785
786 #[test]
787 fn test_quality_conversion() {
788 let q = Quality::from_quality(90.0);
790 let d = q.to_distance();
791 assert!(d > 0.0 && d < 5.0);
792
793 let q2 = Quality::from_distance(1.0);
795 let d2 = q2.to_distance();
796 assert!((d2 - 1.0).abs() < 0.001);
797 }
798
799 #[test]
800 fn test_standard_table_generation() {
801 let table_q50 = generate_standard_jpeg_table(50.0, false);
802 let table_q90 = generate_standard_jpeg_table(90.0, false);
803
804 let sum_q50: u32 = table_q50.values.iter().map(|&v| v as u32).sum();
806 let sum_q90: u32 = table_q90.values.iter().map(|&v| v as u32).sum();
807 assert!(sum_q90 < sum_q50);
808 }
809
810 #[test]
811 fn test_quantize_dequantize() {
812 let coeff = 123.456f32;
813 let quant = 16;
814
815 let quantized = quantize(coeff, quant);
816 let recovered = dequantize(quantized, quant);
817
818 assert!((recovered - coeff).abs() < quant as f32);
820 }
821
822 #[test]
823 fn test_quant_values_in_range() {
824 for q in [10.0, 50.0, 90.0, 100.0] {
826 let table = generate_standard_jpeg_table(q, false);
827 for &v in &table.values {
828 assert!(v >= 1 && v <= 255);
829 }
830 }
831 }
832
833 #[test]
834 fn test_xyb_table_generation() {
835 let table =
836 generate_quant_table(Quality::from_distance(1.0), 0, ColorSpace::Xyb, true, false);
837
838 for &v in &table.values {
840 assert!(v >= 1 && v <= 255);
841 }
842 }
843
844 #[test]
845 fn test_quant_table_comparison() {
846 println!("\n=== Quant Table Comparison (Y channel) ===");
847 println!(
848 "{:>5} {:>8} {:>10} {:>10} {:>8} {:>8}",
849 "Q", "dist", "YCbCr_sum", "XYB_sum", "YCbCr[0]", "XYB[0]"
850 );
851
852 for q in [10, 20, 30, 40, 50, 60, 70, 80, 90] {
853 let quality = Quality::from_quality(q as f32);
854 let distance = quality.to_distance();
855
856 let ycbcr = generate_quant_table(quality, 0, ColorSpace::YCbCr, false, false);
857 let xyb = generate_quant_table(quality, 0, ColorSpace::Xyb, true, false);
858
859 let ycbcr_sum: u32 = ycbcr.values.iter().map(|&x| x as u32).sum();
860 let xyb_sum: u32 = xyb.values.iter().map(|&x| x as u32).sum();
861
862 println!(
863 "{:>5} {:>8.2} {:>10} {:>10} {:>8} {:>8}",
864 q, distance, ycbcr_sum, xyb_sum, ycbcr.values[0], xyb.values[0]
865 );
866 }
867 }
868
869 #[test]
870 fn test_distance_to_scale_linear_region() {
871 for distance in [0.1, 0.5, 1.0, 1.4] {
873 for freq_idx in 0..64 {
874 let scale = distance_to_scale(distance, freq_idx);
875 assert!(
876 (scale - distance).abs() < 1e-6,
877 "Linear region failed: d={}, k={}, scale={}",
878 distance,
879 freq_idx,
880 scale
881 );
882 }
883 }
884 }
885
886 #[test]
887 fn test_distance_to_scale_nonlinear_region() {
888 let distance = 3.0;
890
891 let scale_dc = distance_to_scale(distance, 0);
893 assert!((scale_dc - distance).abs() < 0.1, "DC scale: {}", scale_dc);
895
896 let scale_1 = distance_to_scale(distance, 1);
898 assert!(scale_1 > 1.5 && scale_1 < 3.0, "Index 1 scale: {}", scale_1);
901 }
902
903 #[test]
904 fn test_distance_to_scale_roundtrip() {
905 for distance in [0.5, 1.0, 2.0, 3.0, 5.0, 10.0] {
907 for freq_idx in 0..64 {
908 let scale = distance_to_scale(distance, freq_idx);
909 let recovered = scale_to_distance(scale, freq_idx);
910 assert!(
911 (recovered - distance).abs() < 0.01,
912 "Roundtrip failed: d={}, k={}, scale={}, recovered={}",
913 distance,
914 freq_idx,
915 scale,
916 recovered
917 );
918 }
919 }
920 }
921
922 #[test]
923 fn test_frequency_exponent_values() {
924 assert_eq!(FREQUENCY_EXPONENT.len(), 64);
926
927 assert!((FREQUENCY_EXPONENT[0] - 1.0).abs() < 1e-6);
929
930 assert!(FREQUENCY_EXPONENT[1] < 1.0); assert!(FREQUENCY_EXPONENT[8] < 1.0); assert!((FREQUENCY_EXPONENT[63] - 1.0).abs() < 1e-6);
936 assert!((FREQUENCY_EXPONENT[62] - 1.0).abs() < 1e-6);
937 }
938
939 #[test]
942 fn test_distance_to_scale_cpp_reference() {
943 let test_cases = [
946 (1.0_f32, 0_usize, 1.0_f32),
948 (1.0, 1, 1.0),
949 (1.0, 63, 1.0),
950 (2.0, 0, 2.0), (3.0, 0, 3.0), (5.0, 0, 5.0), ];
955
956 for (distance, freq_idx, expected) in test_cases {
957 let actual = distance_to_scale(distance, freq_idx);
958 assert!(
959 (actual - expected).abs() < 0.01,
960 "Mismatch: distance_to_scale({}, {}) = {}, expected {}",
961 distance,
962 freq_idx,
963 actual,
964 expected
965 );
966 }
967 }
968
969 #[test]
970 fn test_zero_bias_table_sizes() {
971 assert_eq!(ZERO_BIAS_MUL_YCBCR_LQ.len(), 192);
973 assert_eq!(ZERO_BIAS_MUL_YCBCR_HQ.len(), 192);
974 assert_eq!(ZERO_BIAS_OFFSET_YCBCR_DC.len(), 3);
975 assert_eq!(ZERO_BIAS_OFFSET_YCBCR_AC.len(), 3);
976 }
977
978 #[test]
979 fn test_zero_bias_dc_is_zero_in_tables() {
980 for c in 0..3 {
982 assert!(
983 ZERO_BIAS_MUL_YCBCR_LQ[c * 64].abs() < 1e-6,
984 "LQ DC mul for component {} should be 0, got {}",
985 c,
986 ZERO_BIAS_MUL_YCBCR_LQ[c * 64]
987 );
988 assert!(
989 ZERO_BIAS_MUL_YCBCR_HQ[c * 64].abs() < 1e-6,
990 "HQ DC mul for component {} should be 0, got {}",
991 c,
992 ZERO_BIAS_MUL_YCBCR_HQ[c * 64]
993 );
994 }
995 }
996
997 #[test]
998 fn test_zero_bias_params_default() {
999 let params = ZeroBiasParams::default();
1000
1001 for k in 0..64 {
1003 assert!((params.mul[k]).abs() < 1e-6);
1004 assert!((params.offset[k]).abs() < 1e-6);
1005 }
1006 }
1007
1008 #[test]
1009 fn test_zero_bias_for_ycbcr_hq() {
1010 let params = ZeroBiasParams::for_ycbcr(0.5, 0);
1012
1013 assert!((params.mul[1] - ZERO_BIAS_MUL_YCBCR_HQ[1]).abs() < 1e-5);
1015 assert!((params.mul[10] - ZERO_BIAS_MUL_YCBCR_HQ[10]).abs() < 1e-5);
1016
1017 assert!((params.offset[0] - ZERO_BIAS_OFFSET_YCBCR_DC[0]).abs() < 1e-5);
1019 assert!((params.offset[1] - ZERO_BIAS_OFFSET_YCBCR_AC[0]).abs() < 1e-5);
1020 }
1021
1022 #[test]
1023 fn test_zero_bias_for_ycbcr_lq() {
1024 let params = ZeroBiasParams::for_ycbcr(5.0, 0);
1026
1027 assert!((params.mul[1] - ZERO_BIAS_MUL_YCBCR_LQ[1]).abs() < 1e-5);
1029 assert!((params.mul[10] - ZERO_BIAS_MUL_YCBCR_LQ[10]).abs() < 1e-5);
1030 }
1031
1032 #[test]
1033 fn test_zero_bias_for_ycbcr_blend() {
1034 let params = ZeroBiasParams::for_ycbcr(2.0, 0);
1036
1037 let hq_val = ZERO_BIAS_MUL_YCBCR_HQ[1];
1039 let lq_val = ZERO_BIAS_MUL_YCBCR_LQ[1];
1040 let expected = 0.5 * hq_val + 0.5 * lq_val;
1041 assert!(
1042 (params.mul[1] - expected).abs() < 1e-5,
1043 "Expected blend {} (HQ={}, LQ={}), got {}",
1044 expected,
1045 hq_val,
1046 lq_val,
1047 params.mul[1]
1048 );
1049 }
1050
1051 #[test]
1052 fn test_zero_bias_for_ycbcr_all_components() {
1053 for c in 0..3 {
1055 let params = ZeroBiasParams::for_ycbcr(1.5, c);
1056
1057 assert!((params.offset[0] - ZERO_BIAS_OFFSET_YCBCR_DC[c]).abs() < 1e-5);
1059
1060 assert!((params.offset[1] - ZERO_BIAS_OFFSET_YCBCR_AC[c]).abs() < 1e-5);
1062 assert!((params.offset[63] - ZERO_BIAS_OFFSET_YCBCR_AC[c]).abs() < 1e-5);
1063 }
1064 }
1065
1066 #[test]
1067 fn test_zero_bias_apply() {
1068 let params = ZeroBiasParams::for_ycbcr(2.0, 0);
1069 let quant = 16.0;
1070
1071 let threshold = (params.mul[1] + params.offset[1]) * quant;
1073 let small_coeff = threshold * 0.5;
1074 assert!((params.apply(small_coeff, 1, quant)).abs() < 1e-6);
1075
1076 let large_coeff = threshold * 2.0;
1078 assert!((params.apply(large_coeff, 1, quant) - large_coeff).abs() < 1e-6);
1079 }
1080
1081 #[test]
1084 fn test_zero_bias_cpp_reference() {
1085 let params = ZeroBiasParams::for_ycbcr(2.0, 0);
1089 let expected_mul_1 = 0.5 * 0.0568 + 0.5 * 0.0044;
1090 assert!(
1091 (params.mul[1] - expected_mul_1).abs() < 1e-4,
1092 "Y mul[1] at d=2.0: expected {}, got {}",
1093 expected_mul_1,
1094 params.mul[1]
1095 );
1096
1097 assert!(
1099 (params.offset[1] - 0.59082).abs() < 1e-4,
1100 "Y offset[1]: expected 0.59082, got {}",
1101 params.offset[1]
1102 );
1103 }
1104}