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