Coverage Report

Created: 2025-12-05 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jpeg-encoder-0.6.1/src/quantization.rs
Line
Count
Source
1
use alloc::boxed::Box;
2
use core::num::NonZeroU16;
3
4
/// # Quantization table used for encoding
5
///
6
/// Tables are based on tables from mozjpeg
7
#[derive(Debug, Clone)]
8
pub enum QuantizationTableType {
9
    /// Sample quantization tables given in Annex K (Clause K.1) of Recommendation ITU-T T.81 (1992) | ISO/IEC 10918-1:1994.
10
    Default,
11
12
    /// Flat
13
    Flat,
14
15
    /// Custom, tuned for MS-SSIM
16
    CustomMsSsim,
17
18
    /// Custom, tuned for PSNR-HVS
19
    CustomPsnrHvs,
20
21
    /// ImageMagick table by N. Robidoux
22
    ///
23
    /// From <http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008>
24
    ImageMagick,
25
26
    /// Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney.
27
    KleinSilversteinCarney,
28
29
    /// DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick
30
    DentalXRays,
31
32
    /// A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson
33
    VisualDetectionModel,
34
35
    /// An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson
36
    ImprovedDetectionModel,
37
38
    /// A user supplied quantization table
39
    Custom(Box<[u16; 64]>),
40
}
41
42
impl QuantizationTableType {
43
0
    fn index(&self) -> usize {
44
        use QuantizationTableType::*;
45
46
0
        match self {
47
0
            Default => 0,
48
0
            Flat => 1,
49
0
            CustomMsSsim => 2,
50
0
            CustomPsnrHvs => 3,
51
0
            ImageMagick => 4,
52
0
            KleinSilversteinCarney => 5,
53
0
            DentalXRays => 6,
54
0
            VisualDetectionModel => 7,
55
0
            ImprovedDetectionModel => 8,
56
0
            Custom(_) => panic!("Custom types not supported"),
57
        }
58
0
    }
59
}
60
61
// Tables are based on mozjpeg jcparam.c
62
static DEFAULT_LUMA_TABLES: [[u16; 64]; 9] = [
63
    [
64
        // Annex K
65
        16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69,
66
        56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81,
67
        104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99,
68
    ],
69
    [
70
        // Flat
71
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
72
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
73
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
74
    ],
75
    [
76
        // Custom, tuned for MS-SSIM
77
        12, 17, 20, 21, 30, 34, 56, 63, 18, 20, 20, 26, 28, 51, 61, 55, 19, 20, 21, 26, 33, 58, 69,
78
        55, 26, 26, 26, 30, 46, 87, 86, 66, 31, 33, 36, 40, 46, 96, 100, 73, 40, 35, 46, 62, 81,
79
        100, 111, 91, 46, 66, 76, 86, 102, 121, 120, 101, 68, 90, 90, 96, 113, 102, 105, 103,
80
    ],
81
    [
82
        // Custom, tuned for PSNR-HVS
83
        9, 10, 12, 14, 27, 32, 51, 62, 11, 12, 14, 19, 27, 44, 59, 73, 12, 14, 18, 25, 42, 59, 79,
84
        78, 17, 18, 25, 42, 61, 92, 87, 92, 23, 28, 42, 75, 79, 112, 112, 99, 40, 42, 59, 84, 88,
85
        124, 132, 111, 42, 64, 78, 95, 105, 126, 125, 99, 70, 75, 100, 102, 116, 100, 107, 98,
86
    ],
87
    [
88
        // ImageMagick table by N. Robidoux
89
        // From http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008
90
        16, 16, 16, 18, 25, 37, 56, 85, 16, 17, 20, 27, 34, 40, 53, 75, 16, 20, 24, 31, 43, 62, 91,
91
        135, 18, 27, 31, 40, 53, 74, 106, 156, 25, 34, 43, 53, 69, 94, 131, 189, 37, 40, 62, 74,
92
        94, 124, 169, 238, 56, 53, 91, 106, 131, 169, 226, 311, 85, 75, 135, 156, 189, 238, 311,
93
        418,
94
    ],
95
    [
96
        // Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney.
97
        10, 12, 14, 19, 26, 38, 57, 86, 12, 18, 21, 28, 35, 41, 54, 76, 14, 21, 25, 32, 44, 63, 92,
98
        136, 19, 28, 32, 41, 54, 75, 107, 157, 26, 35, 44, 54, 70, 95, 132, 190, 38, 41, 63, 75,
99
        95, 125, 170, 239, 57, 54, 92, 107, 132, 170, 227, 312, 86, 76, 136, 157, 190, 239, 312,
100
        419,
101
    ],
102
    [
103
        // DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick
104
        7, 8, 10, 14, 23, 44, 95, 241, 8, 8, 11, 15, 25, 47, 102, 255, 10, 11, 13, 19, 31, 58, 127,
105
        255, 14, 15, 19, 27, 44, 83, 181, 255, 23, 25, 31, 44, 72, 136, 255, 255, 44, 47, 58, 83,
106
        136, 255, 255, 255, 95, 102, 127, 181, 255, 255, 255, 255, 241, 255, 255, 255, 255, 255,
107
        255, 255,
108
    ],
109
    [
110
        // A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson
111
        15, 11, 11, 12, 15, 19, 25, 32, 11, 13, 10, 10, 12, 15, 19, 24, 11, 10, 14, 14, 16, 18, 22,
112
        27, 12, 10, 14, 18, 21, 24, 28, 33, 15, 12, 16, 21, 26, 31, 36, 42, 19, 15, 18, 24, 31, 38,
113
        45, 53, 25, 19, 22, 28, 36, 45, 55, 65, 32, 24, 27, 33, 42, 53, 65, 77,
114
    ],
115
    [
116
        // An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson
117
        14, 10, 11, 14, 19, 25, 34, 45, 10, 11, 11, 12, 15, 20, 26, 33, 11, 11, 15, 18, 21, 25, 31,
118
        38, 14, 12, 18, 24, 28, 33, 39, 47, 19, 15, 21, 28, 36, 43, 51, 59, 25, 20, 25, 33, 43, 54,
119
        64, 74, 34, 26, 31, 39, 51, 64, 77, 91, 45, 33, 38, 47, 59, 74, 91, 108,
120
    ],
121
];
122
123
// Tables are based on mozjpeg jcparam.c
124
static DEFAULT_CHROMA_TABLES: [[u16; 64]; 9] = [
125
    [
126
        // Annex K
127
        17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99,
128
        99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
129
        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
130
    ],
131
    [
132
        // Flat
133
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
134
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
135
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
136
    ],
137
    [
138
        // Custom, tuned for MS-SSIM
139
        8, 12, 15, 15, 86, 96, 96, 98, 13, 13, 15, 26, 90, 96, 99, 98, 12, 15, 18, 96, 99, 99, 99,
140
        99, 17, 16, 90, 96, 99, 99, 99, 99, 96, 96, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
141
        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
142
    ],
143
    [
144
        //Custom, tuned for PSNR-HVS
145
        9, 10, 17, 19, 62, 89, 91, 97, 12, 13, 18, 29, 84, 91, 88, 98, 14, 19, 29, 93, 95, 95, 98,
146
        97, 20, 26, 84, 88, 95, 95, 98, 94, 26, 86, 91, 93, 97, 99, 98, 99, 99, 100, 98, 99, 99,
147
        99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 97, 97, 99, 99, 99, 99, 97, 99,
148
    ],
149
    [
150
        // ImageMagick table by N. Robidoux
151
        // From http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008
152
        16, 16, 16, 18, 25, 37, 56, 85, 16, 17, 20, 27, 34, 40, 53, 75, 16, 20, 24, 31, 43, 62, 91,
153
        135, 18, 27, 31, 40, 53, 74, 106, 156, 25, 34, 43, 53, 69, 94, 131, 189, 37, 40, 62, 74,
154
        94, 124, 169, 238, 56, 53, 91, 106, 131, 169, 226, 311, 85, 75, 135, 156, 189, 238, 311,
155
        418,
156
    ],
157
    [
158
        // Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney.
159
        10, 12, 14, 19, 26, 38, 57, 86, 12, 18, 21, 28, 35, 41, 54, 76, 14, 21, 25, 32, 44, 63, 92,
160
        136, 19, 28, 32, 41, 54, 75, 107, 157, 26, 35, 44, 54, 70, 95, 132, 190, 38, 41, 63, 75,
161
        95, 125, 170, 239, 57, 54, 92, 107, 132, 170, 227, 312, 86, 76, 136, 157, 190, 239, 312,
162
        419,
163
    ],
164
    [
165
        // DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick
166
        7, 8, 10, 14, 23, 44, 95, 241, 8, 8, 11, 15, 25, 47, 102, 255, 10, 11, 13, 19, 31, 58, 127,
167
        255, 14, 15, 19, 27, 44, 83, 181, 255, 23, 25, 31, 44, 72, 136, 255, 255, 44, 47, 58, 83,
168
        136, 255, 255, 255, 95, 102, 127, 181, 255, 255, 255, 255, 241, 255, 255, 255, 255, 255,
169
        255, 255,
170
    ],
171
    [
172
        // A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson
173
        15, 11, 11, 12, 15, 19, 25, 32, 11, 13, 10, 10, 12, 15, 19, 24, 11, 10, 14, 14, 16, 18, 22,
174
        27, 12, 10, 14, 18, 21, 24, 28, 33, 15, 12, 16, 21, 26, 31, 36, 42, 19, 15, 18, 24, 31, 38,
175
        45, 53, 25, 19, 22, 28, 36, 45, 55, 65, 32, 24, 27, 33, 42, 53, 65, 77,
176
    ],
177
    [
178
        // An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson
179
        14, 10, 11, 14, 19, 25, 34, 45, 10, 11, 11, 12, 15, 20, 26, 33, 11, 11, 15, 18, 21, 25, 31,
180
        38, 14, 12, 18, 24, 28, 33, 39, 47, 19, 15, 21, 28, 36, 43, 51, 59, 25, 20, 25, 33, 43, 54,
181
        64, 74, 34, 26, 31, 39, 51, 64, 77, 91, 45, 33, 38, 47, 59, 74, 91, 108,
182
    ],
183
];
184
185
const SHIFT: u32 = 2 * 8 - 1;
186
187
0
fn compute_reciprocal(divisor: u32) -> (i32, i32) {
188
0
    if divisor <= 1 {
189
0
        return (1, 0);
190
0
    }
191
192
0
    let mut reciprocals = (1 << SHIFT) / divisor;
193
0
    let fractional = (1 << SHIFT) % divisor;
194
195
    // Correction for rounding errors in division
196
0
    let mut correction = divisor / 2;
197
198
0
    if fractional != 0 {
199
0
        if fractional <= correction {
200
0
            correction += 1;
201
0
        } else {
202
0
            reciprocals += 1;
203
0
        }
204
0
    }
205
206
0
    (reciprocals as i32, correction as i32)
207
0
}
208
209
pub struct QuantizationTable {
210
    table: [NonZeroU16; 64],
211
    reciprocals: [i32; 64],
212
    corrections: [i32; 64],
213
}
214
215
impl QuantizationTable {
216
0
    pub fn new_with_quality(
217
0
        table: &QuantizationTableType,
218
0
        quality: u8,
219
0
        luma: bool,
220
0
    ) -> QuantizationTable {
221
0
        let table = match table {
222
0
            QuantizationTableType::Custom(table) => Self::get_user_table(table),
223
0
            table => {
224
0
                let table = if luma {
225
0
                    &DEFAULT_LUMA_TABLES[table.index()]
226
                } else {
227
0
                    &DEFAULT_CHROMA_TABLES[table.index()]
228
                };
229
0
                Self::get_with_quality(table, quality)
230
            }
231
        };
232
233
0
        let mut reciprocals = [0i32; 64];
234
0
        let mut corrections = [0i32; 64];
235
236
0
        for i in 0..64 {
237
0
            let (reciprocal, correction) = compute_reciprocal(table[i].get() as u32);
238
0
239
0
            reciprocals[i] = reciprocal;
240
0
            corrections[i] = correction;
241
0
        }
242
243
0
        QuantizationTable {
244
0
            table,
245
0
            reciprocals,
246
0
            corrections,
247
0
        }
248
0
    }
249
250
0
    fn get_user_table(table: &[u16; 64]) -> [NonZeroU16; 64] {
251
0
        let mut q_table = [NonZeroU16::new(1).unwrap(); 64];
252
0
        for (i, &v) in table.iter().enumerate() {
253
0
            q_table[i] = match NonZeroU16::new(v.clamp(1, 2 << 10) << 3) {
254
0
                Some(v) => v,
255
0
                None => panic!("Invalid quantization table value: {}", v),
256
            };
257
        }
258
0
        q_table
259
0
    }
260
261
0
    fn get_with_quality(table: &[u16; 64], quality: u8) -> [NonZeroU16; 64] {
262
0
        let quality = quality.clamp(1, 100) as u32;
263
264
0
        let scale = if quality < 50 {
265
0
            5000 / quality
266
        } else {
267
0
            200 - quality * 2
268
        };
269
270
0
        let mut q_table = [NonZeroU16::new(1).unwrap(); 64];
271
272
0
        for (i, &v) in table.iter().enumerate() {
273
0
            let v = v as u32;
274
0
275
0
            let v = (v * scale + 50) / 100;
276
0
277
0
            let v = v.clamp(1, 255) as u16;
278
0
279
0
            // Table values are premultiplied with 8 because dct is scaled by 8
280
0
            q_table[i] = NonZeroU16::new(v << 3).unwrap();
281
0
        }
282
0
        q_table
283
0
    }
284
285
    #[inline]
286
0
    pub fn get(&self, index: usize) -> u8 {
287
0
        (self.table[index].get() >> 3) as u8
288
0
    }
Unexecuted instantiation: <jpeg_encoder::quantization::QuantizationTable>::get
Unexecuted instantiation: <jpeg_encoder::quantization::QuantizationTable>::get
289
290
    #[inline]
291
0
    pub fn quantize(&self, in_value: i16, index: usize) -> i16 {
292
0
        let value = in_value as i32;
293
294
0
        let reciprocal = self.reciprocals[index];
295
0
        let corrections = self.corrections[index];
296
297
0
        let abs_value = value.abs();
298
299
0
        let mut product = (abs_value + corrections) * reciprocal;
300
0
        product >>= SHIFT;
301
302
0
        if value != abs_value {
303
0
            product *= -1;
304
0
        }
305
306
0
        product as i16
307
0
    }
Unexecuted instantiation: <jpeg_encoder::quantization::QuantizationTable>::quantize
Unexecuted instantiation: <jpeg_encoder::quantization::QuantizationTable>::quantize
308
}
309
310
#[cfg(test)]
311
mod tests {
312
    use crate::quantization::{QuantizationTable, QuantizationTableType};
313
314
    #[test]
315
    fn test_new_100() {
316
        let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, true);
317
318
        for &v in &q.table {
319
            let v = v.get();
320
            assert_eq!(v, 1 << 3);
321
        }
322
323
        let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, false);
324
325
        for &v in &q.table {
326
            let v = v.get();
327
            assert_eq!(v, 1 << 3);
328
        }
329
    }
330
331
    #[test]
332
    fn test_new_100_quantize() {
333
        let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, true);
334
335
        for i in -255..255 {
336
            assert_eq!(i, q.quantize(i << 3, 0));
337
        }
338
    }
339
}