/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 | | } |