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