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