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