1 | #![allow (clippy::too_many_arguments)] |
2 | |
3 | use crate::error::{ |
4 | ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, |
5 | UnsupportedErrorKind, |
6 | }; |
7 | use crate::image::{ImageEncoder, ImageFormat}; |
8 | use crate::utils::clamp; |
9 | use crate::{ExtendedColorType, GenericImageView, ImageBuffer, Luma, Pixel, Rgb}; |
10 | use num_traits::ToPrimitive; |
11 | use std::borrow::Cow; |
12 | use std::io::{self, Write}; |
13 | |
14 | use super::entropy::build_huff_lut_const; |
15 | use super::transform; |
16 | use crate::traits::PixelWithColorType; |
17 | |
18 | // Markers |
19 | // Baseline DCT |
20 | static SOF0: u8 = 0xC0; |
21 | // Huffman Tables |
22 | static DHT: u8 = 0xC4; |
23 | // Start of Image (standalone) |
24 | static SOI: u8 = 0xD8; |
25 | // End of image (standalone) |
26 | static EOI: u8 = 0xD9; |
27 | // Start of Scan |
28 | static SOS: u8 = 0xDA; |
29 | // Quantization Tables |
30 | static DQT: u8 = 0xDB; |
31 | // Application segments start and end |
32 | static APP0: u8 = 0xE0; |
33 | static APP2: u8 = 0xE2; |
34 | |
35 | // section K.1 |
36 | // table K.1 |
37 | #[rustfmt::skip] |
38 | static STD_LUMA_QTABLE: [u8; 64] = [ |
39 | 16, 11, 10, 16, 24, 40, 51, 61, |
40 | 12, 12, 14, 19, 26, 58, 60, 55, |
41 | 14, 13, 16, 24, 40, 57, 69, 56, |
42 | 14, 17, 22, 29, 51, 87, 80, 62, |
43 | 18, 22, 37, 56, 68, 109, 103, 77, |
44 | 24, 35, 55, 64, 81, 104, 113, 92, |
45 | 49, 64, 78, 87, 103, 121, 120, 101, |
46 | 72, 92, 95, 98, 112, 100, 103, 99, |
47 | ]; |
48 | |
49 | // table K.2 |
50 | #[rustfmt::skip] |
51 | static STD_CHROMA_QTABLE: [u8; 64] = [ |
52 | 17, 18, 24, 47, 99, 99, 99, 99, |
53 | 18, 21, 26, 66, 99, 99, 99, 99, |
54 | 24, 26, 56, 99, 99, 99, 99, 99, |
55 | 47, 66, 99, 99, 99, 99, 99, 99, |
56 | 99, 99, 99, 99, 99, 99, 99, 99, |
57 | 99, 99, 99, 99, 99, 99, 99, 99, |
58 | 99, 99, 99, 99, 99, 99, 99, 99, |
59 | 99, 99, 99, 99, 99, 99, 99, 99, |
60 | ]; |
61 | |
62 | // section K.3 |
63 | // Code lengths and values for table K.3 |
64 | static STD_LUMA_DC_CODE_LENGTHS: [u8; 16] = [ |
65 | 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
66 | ]; |
67 | |
68 | static STD_LUMA_DC_VALUES: [u8; 12] = [ |
69 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, |
70 | ]; |
71 | |
72 | static STD_LUMA_DC_HUFF_LUT: [(u8, u16); 256] = |
73 | build_huff_lut_const(&STD_LUMA_DC_CODE_LENGTHS, &STD_LUMA_DC_VALUES); |
74 | |
75 | // Code lengths and values for table K.4 |
76 | static STD_CHROMA_DC_CODE_LENGTHS: [u8; 16] = [ |
77 | 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, |
78 | ]; |
79 | |
80 | static STD_CHROMA_DC_VALUES: [u8; 12] = [ |
81 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, |
82 | ]; |
83 | |
84 | static STD_CHROMA_DC_HUFF_LUT: [(u8, u16); 256] = |
85 | build_huff_lut_const(&STD_CHROMA_DC_CODE_LENGTHS, &STD_CHROMA_DC_VALUES); |
86 | |
87 | // Code lengths and values for table k.5 |
88 | static STD_LUMA_AC_CODE_LENGTHS: [u8; 16] = [ |
89 | 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, |
90 | ]; |
91 | |
92 | static STD_LUMA_AC_VALUES: [u8; 162] = [ |
93 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, |
94 | 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, |
95 | 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, |
96 | 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, |
97 | 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, |
98 | 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, |
99 | 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, |
100 | 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, |
101 | 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, |
102 | 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, |
103 | 0xF9, 0xFA, |
104 | ]; |
105 | |
106 | static STD_LUMA_AC_HUFF_LUT: [(u8, u16); 256] = |
107 | build_huff_lut_const(&STD_LUMA_AC_CODE_LENGTHS, &STD_LUMA_AC_VALUES); |
108 | |
109 | // Code lengths and values for table k.6 |
110 | static STD_CHROMA_AC_CODE_LENGTHS: [u8; 16] = [ |
111 | 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, |
112 | ]; |
113 | static STD_CHROMA_AC_VALUES: [u8; 162] = [ |
114 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, |
115 | 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, |
116 | 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, |
117 | 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, |
118 | 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, |
119 | 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, |
120 | 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, |
121 | 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, |
122 | 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, |
123 | 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, |
124 | 0xF9, 0xFA, |
125 | ]; |
126 | |
127 | static STD_CHROMA_AC_HUFF_LUT: [(u8, u16); 256] = |
128 | build_huff_lut_const(&STD_CHROMA_AC_CODE_LENGTHS, &STD_CHROMA_AC_VALUES); |
129 | |
130 | static DCCLASS: u8 = 0; |
131 | static ACCLASS: u8 = 1; |
132 | |
133 | static LUMADESTINATION: u8 = 0; |
134 | static CHROMADESTINATION: u8 = 1; |
135 | |
136 | static LUMAID: u8 = 1; |
137 | static CHROMABLUEID: u8 = 2; |
138 | static CHROMAREDID: u8 = 3; |
139 | |
140 | /// The permutation of dct coefficients. |
141 | #[rustfmt::skip] |
142 | static UNZIGZAG: [u8; 64] = [ |
143 | 0, 1, 8, 16, 9, 2, 3, 10, |
144 | 17, 24, 32, 25, 18, 11, 4, 5, |
145 | 12, 19, 26, 33, 40, 48, 41, 34, |
146 | 27, 20, 13, 6, 7, 14, 21, 28, |
147 | 35, 42, 49, 56, 57, 50, 43, 36, |
148 | 29, 22, 15, 23, 30, 37, 44, 51, |
149 | 58, 59, 52, 45, 38, 31, 39, 46, |
150 | 53, 60, 61, 54, 47, 55, 62, 63, |
151 | ]; |
152 | |
153 | /// A representation of a JPEG component |
154 | #[derive (Copy, Clone)] |
155 | struct Component { |
156 | /// The Component's identifier |
157 | id: u8, |
158 | |
159 | /// Horizontal sampling factor |
160 | h: u8, |
161 | |
162 | /// Vertical sampling factor |
163 | v: u8, |
164 | |
165 | /// The quantization table selector |
166 | tq: u8, |
167 | |
168 | /// Index to the Huffman DC Table |
169 | dc_table: u8, |
170 | |
171 | /// Index to the AC Huffman Table |
172 | ac_table: u8, |
173 | |
174 | /// The dc prediction of the component |
175 | _dc_pred: i32, |
176 | } |
177 | |
178 | pub(crate) struct BitWriter<W> { |
179 | w: W, |
180 | accumulator: u32, |
181 | nbits: u8, |
182 | } |
183 | |
184 | impl<W: Write> BitWriter<W> { |
185 | fn new(w: W) -> Self { |
186 | BitWriter { |
187 | w, |
188 | accumulator: 0, |
189 | nbits: 0, |
190 | } |
191 | } |
192 | |
193 | fn write_bits(&mut self, bits: u16, size: u8) -> io::Result<()> { |
194 | if size == 0 { |
195 | return Ok(()); |
196 | } |
197 | |
198 | self.nbits += size; |
199 | self.accumulator |= u32::from(bits) << (32 - self.nbits) as usize; |
200 | |
201 | while self.nbits >= 8 { |
202 | let byte = self.accumulator >> 24; |
203 | self.w.write_all(&[byte as u8])?; |
204 | |
205 | if byte == 0xFF { |
206 | self.w.write_all(&[0x00])?; |
207 | } |
208 | |
209 | self.nbits -= 8; |
210 | self.accumulator <<= 8; |
211 | } |
212 | |
213 | Ok(()) |
214 | } |
215 | |
216 | fn pad_byte(&mut self) -> io::Result<()> { |
217 | self.write_bits(0x7F, 7) |
218 | } |
219 | |
220 | fn huffman_encode(&mut self, val: u8, table: &[(u8, u16); 256]) -> io::Result<()> { |
221 | let (size, code) = table[val as usize]; |
222 | |
223 | assert!(size <= 16, "bad huffman value" ); |
224 | |
225 | self.write_bits(code, size) |
226 | } |
227 | |
228 | fn write_block( |
229 | &mut self, |
230 | block: &[i32; 64], |
231 | prevdc: i32, |
232 | dctable: &[(u8, u16); 256], |
233 | actable: &[(u8, u16); 256], |
234 | ) -> io::Result<i32> { |
235 | // Differential DC encoding |
236 | let dcval = block[0]; |
237 | let diff = dcval - prevdc; |
238 | let (size, value) = encode_coefficient(diff); |
239 | |
240 | self.huffman_encode(size, dctable)?; |
241 | self.write_bits(value, size)?; |
242 | |
243 | // Figure F.2 |
244 | let mut zero_run = 0; |
245 | |
246 | for &k in &UNZIGZAG[1..] { |
247 | if block[k as usize] == 0 { |
248 | zero_run += 1; |
249 | } else { |
250 | while zero_run > 15 { |
251 | self.huffman_encode(0xF0, actable)?; |
252 | zero_run -= 16; |
253 | } |
254 | |
255 | let (size, value) = encode_coefficient(block[k as usize]); |
256 | let symbol = (zero_run << 4) | size; |
257 | |
258 | self.huffman_encode(symbol, actable)?; |
259 | self.write_bits(value, size)?; |
260 | |
261 | zero_run = 0; |
262 | } |
263 | } |
264 | |
265 | if block[UNZIGZAG[63] as usize] == 0 { |
266 | self.huffman_encode(0x00, actable)?; |
267 | } |
268 | |
269 | Ok(dcval) |
270 | } |
271 | |
272 | fn write_marker(&mut self, marker: u8) -> io::Result<()> { |
273 | self.w.write_all(&[0xFF, marker]) |
274 | } |
275 | |
276 | fn write_segment(&mut self, marker: u8, data: &[u8]) -> io::Result<()> { |
277 | self.w.write_all(&[0xFF, marker])?; |
278 | self.w.write_all(&(data.len() as u16 + 2).to_be_bytes())?; |
279 | self.w.write_all(data) |
280 | } |
281 | } |
282 | |
283 | /// Represents a unit in which the density of an image is measured |
284 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
285 | pub enum PixelDensityUnit { |
286 | /// Represents the absence of a unit, the values indicate only a |
287 | /// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio) |
288 | PixelAspectRatio, |
289 | |
290 | /// Pixels per inch (2.54 cm) |
291 | Inches, |
292 | |
293 | /// Pixels per centimeter |
294 | Centimeters, |
295 | } |
296 | |
297 | /// Represents the pixel density of an image |
298 | /// |
299 | /// For example, a 300 DPI image is represented by: |
300 | /// |
301 | /// ```rust |
302 | /// use image::codecs::jpeg::*; |
303 | /// let hdpi = PixelDensity::dpi(300); |
304 | /// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches}) |
305 | /// ``` |
306 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
307 | pub struct PixelDensity { |
308 | /// A couple of values for (Xdensity, Ydensity) |
309 | pub density: (u16, u16), |
310 | /// The unit in which the density is measured |
311 | pub unit: PixelDensityUnit, |
312 | } |
313 | |
314 | impl PixelDensity { |
315 | /// Creates the most common pixel density type: |
316 | /// the horizontal and the vertical density are equal, |
317 | /// and measured in pixels per inch. |
318 | #[must_use ] |
319 | pub fn dpi(density: u16) -> Self { |
320 | PixelDensity { |
321 | density: (density, density), |
322 | unit: PixelDensityUnit::Inches, |
323 | } |
324 | } |
325 | } |
326 | |
327 | impl Default for PixelDensity { |
328 | /// Returns a pixel density with a pixel aspect ratio of 1 |
329 | fn default() -> Self { |
330 | PixelDensity { |
331 | density: (1, 1), |
332 | unit: PixelDensityUnit::PixelAspectRatio, |
333 | } |
334 | } |
335 | } |
336 | |
337 | /// The representation of a JPEG encoder |
338 | pub struct JpegEncoder<W> { |
339 | writer: BitWriter<W>, |
340 | |
341 | components: Vec<Component>, |
342 | tables: Vec<[u8; 64]>, |
343 | |
344 | luma_dctable: Cow<'static, [(u8, u16); 256]>, |
345 | luma_actable: Cow<'static, [(u8, u16); 256]>, |
346 | chroma_dctable: Cow<'static, [(u8, u16); 256]>, |
347 | chroma_actable: Cow<'static, [(u8, u16); 256]>, |
348 | |
349 | pixel_density: PixelDensity, |
350 | |
351 | icc_profile: Vec<u8>, |
352 | } |
353 | |
354 | impl<W: Write> JpegEncoder<W> { |
355 | /// Create a new encoder that writes its output to ```w``` |
356 | pub fn new(w: W) -> JpegEncoder<W> { |
357 | JpegEncoder::new_with_quality(w, 75) |
358 | } |
359 | |
360 | /// Create a new encoder that writes its output to ```w```, and has |
361 | /// the quality parameter ```quality``` with a value in the range 1-100 |
362 | /// where 1 is the worst and 100 is the best. |
363 | pub fn new_with_quality(w: W, quality: u8) -> JpegEncoder<W> { |
364 | let components = vec![ |
365 | Component { |
366 | id: LUMAID, |
367 | h: 1, |
368 | v: 1, |
369 | tq: LUMADESTINATION, |
370 | dc_table: LUMADESTINATION, |
371 | ac_table: LUMADESTINATION, |
372 | _dc_pred: 0, |
373 | }, |
374 | Component { |
375 | id: CHROMABLUEID, |
376 | h: 1, |
377 | v: 1, |
378 | tq: CHROMADESTINATION, |
379 | dc_table: CHROMADESTINATION, |
380 | ac_table: CHROMADESTINATION, |
381 | _dc_pred: 0, |
382 | }, |
383 | Component { |
384 | id: CHROMAREDID, |
385 | h: 1, |
386 | v: 1, |
387 | tq: CHROMADESTINATION, |
388 | dc_table: CHROMADESTINATION, |
389 | ac_table: CHROMADESTINATION, |
390 | _dc_pred: 0, |
391 | }, |
392 | ]; |
393 | |
394 | // Derive our quantization table scaling value using the libjpeg algorithm |
395 | let scale = u32::from(clamp(quality, 1, 100)); |
396 | let scale = if scale < 50 { |
397 | 5000 / scale |
398 | } else { |
399 | 200 - scale * 2 |
400 | }; |
401 | |
402 | let mut tables = vec![STD_LUMA_QTABLE, STD_CHROMA_QTABLE]; |
403 | tables.iter_mut().for_each(|t| { |
404 | for v in t.iter_mut() { |
405 | *v = clamp((u32::from(*v) * scale + 50) / 100, 1, u32::from(u8::MAX)) as u8; |
406 | } |
407 | }); |
408 | |
409 | JpegEncoder { |
410 | writer: BitWriter::new(w), |
411 | |
412 | components, |
413 | tables, |
414 | |
415 | luma_dctable: Cow::Borrowed(&STD_LUMA_DC_HUFF_LUT), |
416 | luma_actable: Cow::Borrowed(&STD_LUMA_AC_HUFF_LUT), |
417 | chroma_dctable: Cow::Borrowed(&STD_CHROMA_DC_HUFF_LUT), |
418 | chroma_actable: Cow::Borrowed(&STD_CHROMA_AC_HUFF_LUT), |
419 | |
420 | pixel_density: PixelDensity::default(), |
421 | |
422 | icc_profile: Vec::new(), |
423 | } |
424 | } |
425 | |
426 | /// Set the pixel density of the images the encoder will encode. |
427 | /// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied, |
428 | /// and no DPI information will be stored in the image. |
429 | pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) { |
430 | self.pixel_density = pixel_density; |
431 | } |
432 | |
433 | /// Encodes the image stored in the raw byte buffer ```image``` |
434 | /// that has dimensions ```width``` and ```height``` |
435 | /// and ```ColorType``` ```c``` |
436 | /// |
437 | /// The Image in encoded with subsampling ratio 4:2:2 |
438 | /// |
439 | /// # Panics |
440 | /// |
441 | /// Panics if `width * height * color_type.bytes_per_pixel() != image.len()`. |
442 | #[track_caller ] |
443 | pub fn encode( |
444 | &mut self, |
445 | image: &[u8], |
446 | width: u32, |
447 | height: u32, |
448 | color_type: ExtendedColorType, |
449 | ) -> ImageResult<()> { |
450 | let expected_buffer_len = color_type.buffer_size(width, height); |
451 | assert_eq!( |
452 | expected_buffer_len, |
453 | image.len() as u64, |
454 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x {height} image" , |
455 | image.len(), |
456 | ); |
457 | |
458 | match color_type { |
459 | ExtendedColorType::L8 => { |
460 | let image: ImageBuffer<Luma<_>, _> = |
461 | ImageBuffer::from_raw(width, height, image).unwrap(); |
462 | self.encode_image(&image) |
463 | } |
464 | ExtendedColorType::Rgb8 => { |
465 | let image: ImageBuffer<Rgb<_>, _> = |
466 | ImageBuffer::from_raw(width, height, image).unwrap(); |
467 | self.encode_image(&image) |
468 | } |
469 | _ => Err(ImageError::Unsupported( |
470 | UnsupportedError::from_format_and_kind( |
471 | ImageFormat::Jpeg.into(), |
472 | UnsupportedErrorKind::Color(color_type), |
473 | ), |
474 | )), |
475 | } |
476 | } |
477 | |
478 | /// Encodes the given image. |
479 | /// |
480 | /// As a special feature this does not require the whole image to be present in memory at the |
481 | /// same time such that it may be computed on the fly, which is why this method exists on this |
482 | /// encoder but not on others. Instead the encoder will iterate over 8-by-8 blocks of pixels at |
483 | /// a time, inspecting each pixel exactly once. You can rely on this behaviour when calling |
484 | /// this method. |
485 | /// |
486 | /// The Image in encoded with subsampling ratio 4:2:2 |
487 | pub fn encode_image<I: GenericImageView>(&mut self, image: &I) -> ImageResult<()> |
488 | where |
489 | I::Pixel: PixelWithColorType, |
490 | { |
491 | let n = I::Pixel::CHANNEL_COUNT; |
492 | let color_type = I::Pixel::COLOR_TYPE; |
493 | let num_components = if n == 1 || n == 2 { 1 } else { 3 }; |
494 | |
495 | self.writer.write_marker(SOI)?; |
496 | |
497 | let mut buf = Vec::new(); |
498 | |
499 | build_jfif_header(&mut buf, self.pixel_density); |
500 | self.writer.write_segment(APP0, &buf)?; |
501 | |
502 | // Write ICC profile chunks if present |
503 | self.write_icc_profile_chunks()?; |
504 | |
505 | build_frame_header( |
506 | &mut buf, |
507 | 8, |
508 | // TODO: not idiomatic yet. Should be an EncodingError and mention jpg. Further it |
509 | // should check dimensions prior to writing. |
510 | u16::try_from(image.width()).map_err(|_| { |
511 | ImageError::Parameter(ParameterError::from_kind( |
512 | ParameterErrorKind::DimensionMismatch, |
513 | )) |
514 | })?, |
515 | u16::try_from(image.height()).map_err(|_| { |
516 | ImageError::Parameter(ParameterError::from_kind( |
517 | ParameterErrorKind::DimensionMismatch, |
518 | )) |
519 | })?, |
520 | &self.components[..num_components], |
521 | ); |
522 | self.writer.write_segment(SOF0, &buf)?; |
523 | |
524 | assert_eq!(self.tables.len(), 2); |
525 | let numtables = if num_components == 1 { 1 } else { 2 }; |
526 | |
527 | for (i, table) in self.tables[..numtables].iter().enumerate() { |
528 | build_quantization_segment(&mut buf, 8, i as u8, table); |
529 | self.writer.write_segment(DQT, &buf)?; |
530 | } |
531 | |
532 | build_huffman_segment( |
533 | &mut buf, |
534 | DCCLASS, |
535 | LUMADESTINATION, |
536 | &STD_LUMA_DC_CODE_LENGTHS, |
537 | &STD_LUMA_DC_VALUES, |
538 | ); |
539 | self.writer.write_segment(DHT, &buf)?; |
540 | |
541 | build_huffman_segment( |
542 | &mut buf, |
543 | ACCLASS, |
544 | LUMADESTINATION, |
545 | &STD_LUMA_AC_CODE_LENGTHS, |
546 | &STD_LUMA_AC_VALUES, |
547 | ); |
548 | self.writer.write_segment(DHT, &buf)?; |
549 | |
550 | if num_components == 3 { |
551 | build_huffman_segment( |
552 | &mut buf, |
553 | DCCLASS, |
554 | CHROMADESTINATION, |
555 | &STD_CHROMA_DC_CODE_LENGTHS, |
556 | &STD_CHROMA_DC_VALUES, |
557 | ); |
558 | self.writer.write_segment(DHT, &buf)?; |
559 | |
560 | build_huffman_segment( |
561 | &mut buf, |
562 | ACCLASS, |
563 | CHROMADESTINATION, |
564 | &STD_CHROMA_AC_CODE_LENGTHS, |
565 | &STD_CHROMA_AC_VALUES, |
566 | ); |
567 | self.writer.write_segment(DHT, &buf)?; |
568 | } |
569 | |
570 | build_scan_header(&mut buf, &self.components[..num_components]); |
571 | self.writer.write_segment(SOS, &buf)?; |
572 | |
573 | if ExtendedColorType::Rgb8 == color_type || ExtendedColorType::Rgba8 == color_type { |
574 | self.encode_rgb(image) |
575 | } else { |
576 | self.encode_gray(image) |
577 | }?; |
578 | |
579 | self.writer.pad_byte()?; |
580 | self.writer.write_marker(EOI)?; |
581 | Ok(()) |
582 | } |
583 | |
584 | fn encode_gray<I: GenericImageView>(&mut self, image: &I) -> io::Result<()> { |
585 | let mut yblock = [0u8; 64]; |
586 | let mut y_dcprev = 0; |
587 | let mut dct_yblock = [0i32; 64]; |
588 | |
589 | for y in (0..image.height()).step_by(8) { |
590 | for x in (0..image.width()).step_by(8) { |
591 | copy_blocks_gray(image, x, y, &mut yblock); |
592 | |
593 | // Level shift and fdct |
594 | // Coeffs are scaled by 8 |
595 | transform::fdct(&yblock, &mut dct_yblock); |
596 | |
597 | // Quantization |
598 | for (i, dct) in dct_yblock.iter_mut().enumerate() { |
599 | *dct = ((*dct / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; |
600 | } |
601 | |
602 | let la = &*self.luma_actable; |
603 | let ld = &*self.luma_dctable; |
604 | |
605 | y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; |
606 | } |
607 | } |
608 | |
609 | Ok(()) |
610 | } |
611 | |
612 | fn encode_rgb<I: GenericImageView>(&mut self, image: &I) -> io::Result<()> { |
613 | let mut y_dcprev = 0; |
614 | let mut cb_dcprev = 0; |
615 | let mut cr_dcprev = 0; |
616 | |
617 | let mut dct_yblock = [0i32; 64]; |
618 | let mut dct_cb_block = [0i32; 64]; |
619 | let mut dct_cr_block = [0i32; 64]; |
620 | |
621 | let mut yblock = [0u8; 64]; |
622 | let mut cb_block = [0u8; 64]; |
623 | let mut cr_block = [0u8; 64]; |
624 | |
625 | for y in (0..image.height()).step_by(8) { |
626 | for x in (0..image.width()).step_by(8) { |
627 | // RGB -> YCbCr |
628 | copy_blocks_ycbcr(image, x, y, &mut yblock, &mut cb_block, &mut cr_block); |
629 | |
630 | // Level shift and fdct |
631 | // Coeffs are scaled by 8 |
632 | transform::fdct(&yblock, &mut dct_yblock); |
633 | transform::fdct(&cb_block, &mut dct_cb_block); |
634 | transform::fdct(&cr_block, &mut dct_cr_block); |
635 | |
636 | // Quantization |
637 | for i in 0usize..64 { |
638 | dct_yblock[i] = |
639 | ((dct_yblock[i] / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; |
640 | dct_cb_block[i] = ((dct_cb_block[i] / 8) as f32 / f32::from(self.tables[1][i])) |
641 | .round() as i32; |
642 | dct_cr_block[i] = ((dct_cr_block[i] / 8) as f32 / f32::from(self.tables[1][i])) |
643 | .round() as i32; |
644 | } |
645 | |
646 | let la = &*self.luma_actable; |
647 | let ld = &*self.luma_dctable; |
648 | let cd = &*self.chroma_dctable; |
649 | let ca = &*self.chroma_actable; |
650 | |
651 | y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; |
652 | cb_dcprev = self.writer.write_block(&dct_cb_block, cb_dcprev, cd, ca)?; |
653 | cr_dcprev = self.writer.write_block(&dct_cr_block, cr_dcprev, cd, ca)?; |
654 | } |
655 | } |
656 | |
657 | Ok(()) |
658 | } |
659 | |
660 | fn write_icc_profile_chunks(&mut self) -> io::Result<()> { |
661 | if self.icc_profile.is_empty() { |
662 | return Ok(()); |
663 | } |
664 | |
665 | const MAX_CHUNK_SIZE: usize = 65533 - 14; |
666 | const MAX_CHUNK_COUNT: usize = 255; |
667 | const MAX_ICC_PROFILE_SIZE: usize = MAX_CHUNK_SIZE * MAX_CHUNK_COUNT; |
668 | |
669 | if self.icc_profile.len() > MAX_ICC_PROFILE_SIZE { |
670 | return Err(io::Error::new( |
671 | io::ErrorKind::InvalidInput, |
672 | "ICC profile too large" , |
673 | )); |
674 | } |
675 | |
676 | let chunk_iter = self.icc_profile.chunks(MAX_CHUNK_SIZE); |
677 | let num_chunks = chunk_iter.len() as u8; |
678 | let mut segment = Vec::new(); |
679 | |
680 | for (i, chunk) in chunk_iter.enumerate() { |
681 | let chunk_number = (i + 1) as u8; |
682 | let length = 14 + chunk.len(); |
683 | |
684 | segment.clear(); |
685 | segment.reserve(length); |
686 | segment.extend_from_slice(b"ICC_PROFILE \0" ); |
687 | segment.push(chunk_number); |
688 | segment.push(num_chunks); |
689 | segment.extend_from_slice(chunk); |
690 | |
691 | self.writer.write_segment(APP2, &segment)?; |
692 | } |
693 | |
694 | Ok(()) |
695 | } |
696 | } |
697 | |
698 | impl<W: Write> ImageEncoder for JpegEncoder<W> { |
699 | #[track_caller ] |
700 | fn write_image( |
701 | mut self, |
702 | buf: &[u8], |
703 | width: u32, |
704 | height: u32, |
705 | color_type: ExtendedColorType, |
706 | ) -> ImageResult<()> { |
707 | self.encode(image:buf, width, height, color_type) |
708 | } |
709 | |
710 | fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> { |
711 | self.icc_profile = icc_profile; |
712 | Ok(()) |
713 | } |
714 | } |
715 | |
716 | fn build_jfif_header(m: &mut Vec<u8>, density: PixelDensity) { |
717 | m.clear(); |
718 | m.extend_from_slice(b"JFIF" ); |
719 | m.extend_from_slice(&[ |
720 | 0, |
721 | 0x01, |
722 | 0x02, |
723 | match density.unit { |
724 | PixelDensityUnit::PixelAspectRatio => 0x00, |
725 | PixelDensityUnit::Inches => 0x01, |
726 | PixelDensityUnit::Centimeters => 0x02, |
727 | }, |
728 | ]); |
729 | m.extend_from_slice(&density.density.0.to_be_bytes()); |
730 | m.extend_from_slice(&density.density.1.to_be_bytes()); |
731 | m.extend_from_slice(&[0, 0]); |
732 | } |
733 | |
734 | fn build_frame_header( |
735 | m: &mut Vec<u8>, |
736 | precision: u8, |
737 | width: u16, |
738 | height: u16, |
739 | components: &[Component], |
740 | ) { |
741 | m.clear(); |
742 | |
743 | m.push(precision); |
744 | m.extend_from_slice(&height.to_be_bytes()); |
745 | m.extend_from_slice(&width.to_be_bytes()); |
746 | m.push(components.len() as u8); |
747 | |
748 | for &comp: Component in components { |
749 | let hv: u8 = (comp.h << 4) | comp.v; |
750 | m.extend_from_slice(&[comp.id, hv, comp.tq]); |
751 | } |
752 | } |
753 | |
754 | fn build_scan_header(m: &mut Vec<u8>, components: &[Component]) { |
755 | m.clear(); |
756 | |
757 | m.push(components.len() as u8); |
758 | |
759 | for &comp: Component in components { |
760 | let tables: u8 = (comp.dc_table << 4) | comp.ac_table; |
761 | m.extend_from_slice(&[comp.id, tables]); |
762 | } |
763 | |
764 | // spectral start and end, approx. high and low |
765 | m.extend_from_slice(&[0, 63, 0]); |
766 | } |
767 | |
768 | fn build_huffman_segment( |
769 | m: &mut Vec<u8>, |
770 | class: u8, |
771 | destination: u8, |
772 | numcodes: &[u8; 16], |
773 | values: &[u8], |
774 | ) { |
775 | m.clear(); |
776 | |
777 | let tcth: u8 = (class << 4) | destination; |
778 | m.push(tcth); |
779 | |
780 | m.extend_from_slice(numcodes); |
781 | |
782 | let sum: usize = numcodes.iter().map(|&x: u8| x as usize).sum(); |
783 | |
784 | assert_eq!(sum, values.len()); |
785 | |
786 | m.extend_from_slice(values); |
787 | } |
788 | |
789 | fn build_quantization_segment(m: &mut Vec<u8>, precision: u8, identifier: u8, qtable: &[u8; 64]) { |
790 | m.clear(); |
791 | |
792 | let p: u8 = if precision == 8 { 0 } else { 1 }; |
793 | |
794 | let pqtq: u8 = (p << 4) | identifier; |
795 | m.push(pqtq); |
796 | |
797 | for &i: u8 in &UNZIGZAG[..] { |
798 | m.push(qtable[i as usize]); |
799 | } |
800 | } |
801 | |
802 | fn encode_coefficient(coefficient: i32) -> (u8, u16) { |
803 | let mut magnitude: u16 = coefficient.unsigned_abs() as u16; |
804 | let mut num_bits: u8 = 0u8; |
805 | |
806 | while magnitude > 0 { |
807 | magnitude >>= 1; |
808 | num_bits += 1; |
809 | } |
810 | |
811 | let mask: u16 = (1 << num_bits as usize) - 1; |
812 | |
813 | let val: u16 = if coefficient < 0 { |
814 | (coefficient - 1) as u16 & mask |
815 | } else { |
816 | coefficient as u16 & mask |
817 | }; |
818 | |
819 | (num_bits, val) |
820 | } |
821 | |
822 | #[inline ] |
823 | fn rgb_to_ycbcr<P: Pixel>(pixel: P) -> (u8, u8, u8) { |
824 | let [r, g, b] = pixel.to_rgb().0; |
825 | let r: i32 = i32::from(r.to_u8().unwrap()); |
826 | let g: i32 = i32::from(g.to_u8().unwrap()); |
827 | let b: i32 = i32::from(b.to_u8().unwrap()); |
828 | |
829 | /* |
830 | JPEG RGB -> YCbCr is defined as following equations using Bt.601 Full Range matrix: |
831 | Y = 0.29900 * R + 0.58700 * G + 0.11400 * B |
832 | Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + 128 |
833 | Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + 128 |
834 | |
835 | To avoid using slow floating point conversion is done in fixed point, |
836 | using following coefficients with rounding to nearest integer mode: |
837 | */ |
838 | |
839 | const C_YR: i32 = 19595; // 0.29900 = 19595 * 2^-16 |
840 | const C_YG: i32 = 38469; // 0.58700 = 38469 * 2^-16 |
841 | const C_YB: i32 = 7471; // 0.11400 = 7471 * 2^-16 |
842 | const Y_ROUNDING: i32 = (1 << 15) - 1; // + 0.5 to perform rounding shift right in-place |
843 | const C_UR: i32 = 11059; // 0.16874 = 11059 * 2^-16 |
844 | const C_UG: i32 = 21709; // 0.33126 = 21709 * 2^-16 |
845 | const C_UB: i32 = 32768; // 0.5 = 32768 * 2^-16 |
846 | const UV_BIAS_ROUNDING: i32 = (128 * (1 << 16)) + ((1 << 15) - 1); // 128 + 0.5 = ((128 * (1 << 16)) + ((1 << 15) - 1)) * 2^-16 ; + 0.5 to perform rounding shift right in-place |
847 | const C_VR: i32 = C_UB; // 0.5 = 32768 * 2^-16 |
848 | const C_VG: i32 = 27439; // 0.41869 = 27439 * 2^-16 |
849 | const C_VB: i32 = 5329; // 0.08131409 = 5329 * 2^-16 |
850 | |
851 | let y = (C_YR * r + C_YG * g + C_YB * b + Y_ROUNDING) >> 16; |
852 | let cb = (-C_UR * r - C_UG * g + C_UB * b + UV_BIAS_ROUNDING) >> 16; |
853 | let cr = (C_VR * r - C_VG * g - C_VB * b + UV_BIAS_ROUNDING) >> 16; |
854 | |
855 | (y as u8, cb as u8, cr as u8) |
856 | } |
857 | |
858 | /// Returns the pixel at (x,y) if (x,y) is in the image, |
859 | /// otherwise the closest pixel in the image |
860 | #[inline ] |
861 | fn pixel_at_or_near<I: GenericImageView>(source: &I, x: u32, y: u32) -> I::Pixel { |
862 | if source.in_bounds(x, y) { |
863 | source.get_pixel(x, y) |
864 | } else { |
865 | source.get_pixel(x.min(source.width() - 1), y.min(source.height() - 1)) |
866 | } |
867 | } |
868 | |
869 | fn copy_blocks_ycbcr<I: GenericImageView>( |
870 | source: &I, |
871 | x0: u32, |
872 | y0: u32, |
873 | yb: &mut [u8; 64], |
874 | cbb: &mut [u8; 64], |
875 | crb: &mut [u8; 64], |
876 | ) { |
877 | for y: u32 in 0..8 { |
878 | for x: u32 in 0..8 { |
879 | let pixel: ::Pixel = pixel_at_or_near(source, x:x + x0, y:y + y0); |
880 | let (yc: u8, cb: u8, cr: u8) = rgb_to_ycbcr(pixel); |
881 | |
882 | yb[(y * 8 + x) as usize] = yc; |
883 | cbb[(y * 8 + x) as usize] = cb; |
884 | crb[(y * 8 + x) as usize] = cr; |
885 | } |
886 | } |
887 | } |
888 | |
889 | fn copy_blocks_gray<I: GenericImageView>(source: &I, x0: u32, y0: u32, gb: &mut [u8; 64]) { |
890 | use num_traits::cast::ToPrimitive; |
891 | for y: u32 in 0..8 { |
892 | for x: u32 in 0..8 { |
893 | let pixel: ::Pixel = pixel_at_or_near(source, x:x0 + x, y:y0 + y); |
894 | let [luma: <::Pixel as Pixel>::Subpixel] = pixel.to_luma().0; |
895 | gb[(y * 8 + x) as usize] = luma.to_u8().unwrap(); |
896 | } |
897 | } |
898 | } |
899 | |
900 | #[cfg (test)] |
901 | mod tests { |
902 | use std::io::Cursor; |
903 | |
904 | #[cfg (feature = "benchmarks" )] |
905 | extern crate test; |
906 | #[cfg (feature = "benchmarks" )] |
907 | use test::Bencher; |
908 | |
909 | use crate::error::ParameterErrorKind::DimensionMismatch; |
910 | use crate::image::ImageDecoder; |
911 | use crate::{ExtendedColorType, ImageEncoder, ImageError}; |
912 | |
913 | use super::super::JpegDecoder; |
914 | use super::{ |
915 | build_frame_header, build_huffman_segment, build_jfif_header, build_quantization_segment, |
916 | build_scan_header, Component, JpegEncoder, PixelDensity, DCCLASS, LUMADESTINATION, |
917 | STD_LUMA_DC_CODE_LENGTHS, STD_LUMA_DC_VALUES, |
918 | }; |
919 | |
920 | fn decode(encoded: &[u8]) -> Vec<u8> { |
921 | let decoder = JpegDecoder::new(Cursor::new(encoded)).expect("Could not decode image" ); |
922 | |
923 | let mut decoded = vec![0; decoder.total_bytes() as usize]; |
924 | decoder |
925 | .read_image(&mut decoded) |
926 | .expect("Could not decode image" ); |
927 | decoded |
928 | } |
929 | |
930 | #[test ] |
931 | fn roundtrip_sanity_check() { |
932 | // create a 1x1 8-bit image buffer containing a single red pixel |
933 | let img = [255u8, 0, 0]; |
934 | |
935 | // encode it into a memory buffer |
936 | let mut encoded_img = Vec::new(); |
937 | { |
938 | let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); |
939 | encoder |
940 | .write_image(&img, 1, 1, ExtendedColorType::Rgb8) |
941 | .expect("Could not encode image" ); |
942 | } |
943 | |
944 | // decode it from the memory buffer |
945 | { |
946 | let decoded = decode(&encoded_img); |
947 | // note that, even with the encode quality set to 100, we do not get the same image |
948 | // back. Therefore, we're going to assert that it's at least red-ish: |
949 | assert_eq!(3, decoded.len()); |
950 | assert!(decoded[0] > 0x80); |
951 | assert!(decoded[1] < 0x80); |
952 | assert!(decoded[2] < 0x80); |
953 | } |
954 | } |
955 | |
956 | #[test ] |
957 | fn grayscale_roundtrip_sanity_check() { |
958 | // create a 2x2 8-bit image buffer containing a white diagonal |
959 | let img = [255u8, 0, 0, 255]; |
960 | |
961 | // encode it into a memory buffer |
962 | let mut encoded_img = Vec::new(); |
963 | { |
964 | let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); |
965 | encoder |
966 | .write_image(&img[..], 2, 2, ExtendedColorType::L8) |
967 | .expect("Could not encode image" ); |
968 | } |
969 | |
970 | // decode it from the memory buffer |
971 | { |
972 | let decoded = decode(&encoded_img); |
973 | // note that, even with the encode quality set to 100, we do not get the same image |
974 | // back. Therefore, we're going to assert that the diagonal is at least white-ish: |
975 | assert_eq!(4, decoded.len()); |
976 | assert!(decoded[0] > 0x80); |
977 | assert!(decoded[1] < 0x80); |
978 | assert!(decoded[2] < 0x80); |
979 | assert!(decoded[3] > 0x80); |
980 | } |
981 | } |
982 | |
983 | #[test ] |
984 | fn jfif_header_density_check() { |
985 | let mut buffer = Vec::new(); |
986 | build_jfif_header(&mut buffer, PixelDensity::dpi(300)); |
987 | assert_eq!( |
988 | buffer, |
989 | vec![ |
990 | b'J' , |
991 | b'F' , |
992 | b'I' , |
993 | b'F' , |
994 | 0, |
995 | 1, |
996 | 2, // JFIF version 1.2 |
997 | 1, // density is in dpi |
998 | 300u16.to_be_bytes()[0], |
999 | 300u16.to_be_bytes()[1], |
1000 | 300u16.to_be_bytes()[0], |
1001 | 300u16.to_be_bytes()[1], |
1002 | 0, |
1003 | 0, // No thumbnail |
1004 | ] |
1005 | ); |
1006 | } |
1007 | |
1008 | #[test ] |
1009 | fn test_image_too_large() { |
1010 | // JPEG cannot encode images larger than 65,535×65,535 |
1011 | // create a 65,536×1 8-bit black image buffer |
1012 | let img = [0; 65_536]; |
1013 | // Try to encode an image that is too large |
1014 | let mut encoded = Vec::new(); |
1015 | let encoder = JpegEncoder::new_with_quality(&mut encoded, 100); |
1016 | let result = encoder.write_image(&img, 65_536, 1, ExtendedColorType::L8); |
1017 | match result { |
1018 | Err(ImageError::Parameter(err)) => { |
1019 | assert_eq!(err.kind(), DimensionMismatch); |
1020 | } |
1021 | other => { |
1022 | panic!( |
1023 | "Encoding an image that is too large should return a DimensionError \ |
1024 | it returned {:?} instead" , |
1025 | other |
1026 | ) |
1027 | } |
1028 | } |
1029 | } |
1030 | |
1031 | #[test ] |
1032 | fn test_build_jfif_header() { |
1033 | let mut buf = vec![]; |
1034 | let density = PixelDensity::dpi(100); |
1035 | build_jfif_header(&mut buf, density); |
1036 | assert_eq!( |
1037 | buf, |
1038 | [0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0, 100, 0, 100, 0, 0] |
1039 | ); |
1040 | } |
1041 | |
1042 | #[test ] |
1043 | fn test_build_frame_header() { |
1044 | let mut buf = vec![]; |
1045 | let components = vec![ |
1046 | Component { |
1047 | id: 1, |
1048 | h: 1, |
1049 | v: 1, |
1050 | tq: 5, |
1051 | dc_table: 5, |
1052 | ac_table: 5, |
1053 | _dc_pred: 0, |
1054 | }, |
1055 | Component { |
1056 | id: 2, |
1057 | h: 1, |
1058 | v: 1, |
1059 | tq: 4, |
1060 | dc_table: 4, |
1061 | ac_table: 4, |
1062 | _dc_pred: 0, |
1063 | }, |
1064 | ]; |
1065 | build_frame_header(&mut buf, 5, 100, 150, &components); |
1066 | assert_eq!( |
1067 | buf, |
1068 | [5, 0, 150, 0, 100, 2, 1, (1 << 4) | 1, 5, 2, (1 << 4) | 1, 4] |
1069 | ); |
1070 | } |
1071 | |
1072 | #[test ] |
1073 | fn test_build_scan_header() { |
1074 | let mut buf = vec![]; |
1075 | let components = vec![ |
1076 | Component { |
1077 | id: 1, |
1078 | h: 1, |
1079 | v: 1, |
1080 | tq: 5, |
1081 | dc_table: 5, |
1082 | ac_table: 5, |
1083 | _dc_pred: 0, |
1084 | }, |
1085 | Component { |
1086 | id: 2, |
1087 | h: 1, |
1088 | v: 1, |
1089 | tq: 4, |
1090 | dc_table: 4, |
1091 | ac_table: 4, |
1092 | _dc_pred: 0, |
1093 | }, |
1094 | ]; |
1095 | build_scan_header(&mut buf, &components); |
1096 | assert_eq!(buf, [2, 1, (5 << 4) | 5, 2, (4 << 4) | 4, 0, 63, 0]); |
1097 | } |
1098 | |
1099 | #[test ] |
1100 | fn test_build_huffman_segment() { |
1101 | let mut buf = vec![]; |
1102 | build_huffman_segment( |
1103 | &mut buf, |
1104 | DCCLASS, |
1105 | LUMADESTINATION, |
1106 | &STD_LUMA_DC_CODE_LENGTHS, |
1107 | &STD_LUMA_DC_VALUES, |
1108 | ); |
1109 | assert_eq!( |
1110 | buf, |
1111 | vec![ |
1112 | 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
1113 | 10, 11 |
1114 | ] |
1115 | ); |
1116 | } |
1117 | |
1118 | #[test ] |
1119 | fn test_build_quantization_segment() { |
1120 | let mut buf = vec![]; |
1121 | let qtable = [0u8; 64]; |
1122 | build_quantization_segment(&mut buf, 8, 1, &qtable); |
1123 | let mut expected = vec![]; |
1124 | expected.push(1); |
1125 | expected.extend_from_slice(&[0; 64]); |
1126 | assert_eq!(buf, expected); |
1127 | } |
1128 | |
1129 | #[cfg (feature = "benchmarks" )] |
1130 | #[bench ] |
1131 | fn bench_jpeg_encoder_new(b: &mut Bencher) { |
1132 | b.iter(|| { |
1133 | let mut y = vec![]; |
1134 | let _x = JpegEncoder::new(&mut y); |
1135 | }) |
1136 | } |
1137 | } |
1138 | |