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