1 #![allow(non_upper_case_globals)]
2 
3 use std::cmp::{max, min};
4 use std::fmt;
5 use std::os::raw::c_uint;
6 type Coef = c_uint;
7 
8 pub struct QTable {
9     pub(crate) coeffs: [Coef; 64],
10 }
11 
12 impl PartialEq for QTable {
eq(&self, other: &Self) -> bool13     fn eq(&self, other: &Self) -> bool {
14         let iter2 = (&other.coeffs).iter().cloned();
15         (&self.coeffs).iter().cloned().zip(iter2).all(|(s,o)|s==o)
16     }
17 }
18 
19 impl fmt::Debug for QTable {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error>20     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
21         write!(fmt, "QTable{{coeffs:{:?}}}", &self.coeffs[..])
22     }
23 }
24 
25 const low_weights : [f32; 19] = [
26     1.00, 0.85, 0.55, 0., 0., 0., 0., 0.,
27     0.85, 0.75, 0.10, 0., 0., 0., 0., 0.,
28     0.55, 0.10, 0.05,
29 ];
30 
31 impl QTable {
compare(&self, other: &Self) -> (f32, f32)32     pub fn compare(&self, other: &Self) -> (f32, f32) {
33         let mut scales = [0.; 64];
34         for (s, (&a, &b)) in scales.iter_mut().zip(self.coeffs.iter().zip(other.coeffs.iter())) {
35             *s = if b > 0 {a as f32 / b as f32} else {0.};
36         }
37         let avg = scales.iter().sum::<f32>() / 64.;
38         let var = scales.iter().map(|&v| (v - avg).powi(2)).sum::<f32>() / 64.;
39         (avg, var)
40     }
41 
42     #[must_use]
scaled(&self, dc_quality: f32, ac_quality: f32) -> Self43     pub fn scaled(&self, dc_quality: f32, ac_quality: f32) -> Self {
44         let dc_scaling = Self::quality_scaling(dc_quality);
45         let ac_scaling = Self::quality_scaling(ac_quality);
46 
47         let mut out = [0; 64];
48         {
49             debug_assert_eq!(self.coeffs.len(), out.len());
50             debug_assert!(low_weights.len() < self.coeffs.len());
51 
52             let (low_coefs, high_coefs) = self.coeffs.split_at(low_weights.len());
53             let (low_out, high_out) = out.split_at_mut(low_weights.len());
54 
55             // TODO: that could be improved for 1x2 and 2x1 subsampling
56             for ((out, coef), w) in low_out.iter_mut().zip(low_coefs).zip(&low_weights) {
57                 *out = min(255, max(1, (*coef as f32 * (dc_scaling * w + ac_scaling * (1.-w))).round() as Coef));
58             }
59             for (out, coef) in high_out.iter_mut().zip(high_coefs) {
60                 *out = min(255, max(1, (*coef as f32 * ac_scaling).round() as Coef));
61             }
62         }
63         Self { coeffs: out }
64     }
65 
as_ptr(&self) -> *const c_uint66     pub fn as_ptr(&self) -> *const c_uint {
67         self.coeffs.as_ptr()
68     }
69 
70     // Similar to libjpeg, but result is 100x smaller
quality_scaling(quality: f32) -> f3271     fn quality_scaling(quality: f32) -> f32 {
72         assert!(quality > 0. && quality <= 100.);
73 
74         return if quality < 50. {
75             50. / quality
76         } else {
77             (100. - quality) / 50.
78         };
79     }
80 }
81 
82 pub static AnnexK_Luma: QTable = QTable{coeffs:[
83     16,  11,  10,  16,  24,  40,  51,  61,
84     12,  12,  14,  19,  26,  58,  60,  55,
85     14,  13,  16,  24,  40,  57,  69,  56,
86     14,  17,  22,  29,  51,  87,  80,  62,
87     18,  22,  37,  56,  68, 109, 103,  77,
88     24,  35,  55,  64,  81, 104, 113,  92,
89     49,  64,  78,  87, 103, 121, 120, 101,
90     72,  92,  95,  98, 112, 100, 103,  99
91 ]};
92 
93 pub static AnnexK_Chroma: QTable = QTable{coeffs:[
94     17,  18,  24,  47,  99,  99,  99,  99,
95     18,  21,  26,  66,  99,  99,  99,  99,
96     24,  26,  56,  99,  99,  99,  99,  99,
97     47,  66,  99,  99,  99,  99,  99,  99,
98     99,  99,  99,  99,  99,  99,  99,  99,
99     99,  99,  99,  99,  99,  99,  99,  99,
100     99,  99,  99,  99,  99,  99,  99,  99,
101     99,  99,  99,  99,  99,  99,  99,  99
102 ]};
103 
104 pub static Flat: QTable = QTable{coeffs:[
105     16,  16,  16,  16,  16,  16,  16,  16,
106     16,  16,  16,  16,  16,  16,  16,  16,
107     16,  16,  16,  16,  16,  16,  16,  16,
108     16,  16,  16,  16,  16,  16,  16,  16,
109     16,  16,  16,  16,  16,  16,  16,  16,
110     16,  16,  16,  16,  16,  16,  16,  16,
111     16,  16,  16,  16,  16,  16,  16,  16,
112     16,  16,  16,  16,  16,  16,  16,  16
113 ]};
114 
115 pub static MSSSIM_Luma: QTable = QTable{coeffs:[
116     12, 17, 20, 21, 30, 34, 56, 63,
117     18, 20, 20, 26, 28, 51, 61, 55,
118     19, 20, 21, 26, 33, 58, 69, 55,
119     26, 26, 26, 30, 46, 87, 86, 66,
120     31, 33, 36, 40, 46, 96, 100, 73,
121     40, 35, 46, 62, 81, 100, 111, 91,
122     46, 66, 76, 86, 102, 121, 120, 101,
123     68, 90, 90, 96, 113, 102, 105, 103
124 ]};
125 
126 pub static MSSSIM_Chroma: QTable = QTable{coeffs:[
127     8, 12, 15, 15, 86, 96, 96, 98,
128     13, 13, 15, 26, 90, 96, 99, 98,
129     12, 15, 18, 96, 99, 99, 99, 99,
130     17, 16, 90, 96, 99, 99, 99, 99,
131     96, 96, 99, 99, 99, 99, 99, 99,
132     99, 99, 99, 99, 99, 99, 99, 99,
133     99, 99, 99, 99, 99, 99, 99, 99,
134     99, 99, 99, 99, 99, 99, 99, 99
135 ]};
136 
137 pub static NRobidoux: QTable = QTable{coeffs:[
138     16,  16,  16,  18,  25,  37,  56,  85,
139     16,  17,  20,  27,  34,  40,  53,  75,
140     16,  20,  24,  31,  43,  62,  91,  135,
141     18,  27,  31,  40,  53,  74,  106, 156,
142     25,  34,  43,  53,  69,  94,  131, 189,
143     37,  40,  62,  74,  94,  124, 169, 238,
144     56,  53,  91,  106, 131, 169, 226, 311,
145     85,  75,  135, 156, 189, 238, 311, 418
146 ]};
147 
148 pub static PSNRHVS_Luma: QTable = QTable{coeffs:[
149     9, 10, 12, 14, 27, 32, 51, 62,
150     11, 12, 14, 19, 27, 44, 59, 73,
151     12, 14, 18, 25, 42, 59, 79, 78,
152     17, 18, 25, 42, 61, 92, 87, 92,
153     23, 28, 42, 75, 79, 112, 112, 99,
154     40, 42, 59, 84, 88, 124, 132, 111,
155     42, 64, 78, 95, 105, 126, 125, 99,
156     70, 75, 100, 102, 116, 100, 107, 98
157 ]};
158 pub static PSNRHVS_Chroma: QTable = QTable{coeffs:[
159     9, 10, 17, 19, 62, 89, 91, 97,
160     12, 13, 18, 29, 84, 91, 88, 98,
161     14, 19, 29, 93, 95, 95, 98, 97,
162     20, 26, 84, 88, 95, 95, 98, 94,
163     26, 86, 91, 93, 97, 99, 98, 99,
164     99, 100, 98, 99, 99, 99, 99, 99,
165     99, 99, 99, 99, 99, 99, 99, 99,
166     97, 97, 99, 99, 99, 99, 97, 99
167 ]};
168 
169 pub static KleinSilversteinCarney: QTable = QTable{coeffs:[
170     /* Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney.
171      */
172     10, 12, 14, 19, 26, 38, 57, 86,
173     12, 18, 21, 28, 35, 41, 54, 76,
174     14, 21, 25, 32, 44, 63, 92, 136,
175     19, 28, 32, 41, 54, 75, 107, 157,
176     26, 35, 44, 54, 70, 95, 132, 190,
177     38, 41, 63, 75, 95, 125, 170, 239,
178     57, 54, 92, 107, 132, 170, 227, 312,
179     86, 76, 136, 157, 190, 239, 312, 419
180 ]};
181 
182 pub static WatsonTaylorBorthwick: QTable = QTable{coeffs:[
183     /* DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick
184      */
185     7, 8, 10, 14, 23, 44, 95, 241,
186     8, 8, 11, 15, 25, 47, 102, 255,
187     10, 11, 13, 19, 31, 58, 127, 255,
188     14, 15, 19, 27, 44, 83, 181, 255,
189     23, 25, 31, 44, 72, 136, 255, 255,
190     44, 47, 58, 83, 136, 255, 255, 255,
191     95, 102, 127, 181, 255, 255, 255, 255,
192     241, 255, 255, 255, 255, 255, 255, 255
193   ]};
194 
195 pub static AhumadaWatsonPeterson: QTable = QTable{coeffs:[
196     /* A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson
197      */
198     15, 11, 11, 12, 15, 19, 25, 32,
199     11, 13, 10, 10, 12, 15, 19, 24,
200     11, 10, 14, 14, 16, 18, 22, 27,
201     12, 10, 14, 18, 21, 24, 28, 33,
202     15, 12, 16, 21, 26, 31, 36, 42,
203     19, 15, 18, 24, 31, 38, 45, 53,
204     25, 19, 22, 28, 36, 45, 55, 65,
205     32, 24, 27, 33, 42, 53, 65, 77
206   ]};
207 
208 pub static PetersonAhumadaWatson: QTable = QTable { coeffs:[
209     /* An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson
210      */
211     14, 10, 11, 14, 19, 25, 34, 45,
212     10, 11, 11, 12, 15, 20, 26, 33,
213     11, 11, 15, 18, 21, 25, 31, 38,
214     14, 12, 18, 24, 28, 33, 39, 47,
215     19, 15, 21, 28, 36, 43, 51, 59,
216     25, 20, 25, 33, 43, 54, 64, 74,
217     34, 26, 31, 39, 51, 64, 77, 91,
218     45, 33, 38, 47, 59, 74, 91, 108
219 ]};
220 
221 pub static ALL_TABLES: [(&str, &QTable); 12] = [
222     ("Annex-K Luma", &AnnexK_Luma),
223     ("Annex-K Chroma", &AnnexK_Chroma),
224     ("Flat", &Flat),
225     ("MSSSIM Luma", &MSSSIM_Luma),
226     ("MSSSIM Chroma", &MSSSIM_Chroma),
227     ("N. Robidoux", &NRobidoux),
228     ("PSNRHVS Luma", &PSNRHVS_Luma),
229     ("PSNRHVS Chroma", &PSNRHVS_Chroma),
230     ("Klein, Silverstein, Carney", &KleinSilversteinCarney),
231     ("Watson, Taylor, Borthwick", &WatsonTaylorBorthwick),
232     ("Ahumada, Watson, Peterson", &AhumadaWatsonPeterson),
233     ("Peterson, Ahumada, Watson", &PetersonAhumadaWatson),
234 ];
235 
236 #[test]
scaling()237 fn scaling() {
238     assert_eq!(QTable { coeffs: [100; 64] }, QTable { coeffs: [100; 64] });
239     assert!(QTable { coeffs: [1; 64] } != QTable { coeffs: [2; 64] });
240 
241     assert_eq!(QTable{coeffs:[36; 64]}, Flat.scaled(22.,22.));
242     assert_eq!(QTable{coeffs:[8; 64]}, Flat.scaled(75.,75.));
243     assert_eq!(QTable{coeffs:[1; 64]}, Flat.scaled(100.,100.));
244     assert_eq!(QTable{coeffs:[2; 64]}, Flat.scaled(95.,95.));
245     assert_eq!(QTable{coeffs:[
246          2,  6, 15, 32, 32, 32, 32, 32,
247          6,  9, 29, 32, 32, 32, 32, 32,
248         15, 29, 30, 32, 32, 32, 32, 32,
249         32, 32, 32, 32, 32, 32, 32, 32,
250         32, 32, 32, 32, 32, 32, 32, 32,
251         32, 32, 32, 32, 32, 32, 32, 32,
252         32, 32, 32, 32, 32, 32, 32, 32,
253         32, 32, 32, 32, 32, 32, 32, 32]}, Flat.scaled(95.,25.));
254     assert_eq!(PetersonAhumadaWatson, PetersonAhumadaWatson.scaled(50.,50.));
255 
256     assert_eq!(QTable { coeffs: [1; 64] }, NRobidoux.scaled(99.9, 99.9));
257     assert_eq!(QTable { coeffs: [1; 64] }, MSSSIM_Chroma.scaled(99.8, 99.8));
258 }
259