Coverage Report

Created: 2025-07-11 07:25

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