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