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
18// Markers
19// Baseline DCT
20static SOF0: u8 = 0xC0;
21// Huffman Tables
22static DHT: u8 = 0xC4;
23// Start of Image (standalone)
24static SOI: u8 = 0xD8;
25// End of image (standalone)
26static EOI: u8 = 0xD9;
27// Start of Scan
28static SOS: u8 = 0xDA;
29// Quantization Tables
30static DQT: u8 = 0xDB;
31// Application segments start and end
32static APP0: u8 = 0xE0;
33static APP2: u8 = 0xE2;
34
35// section K.1
36// table K.1
37#[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// table K.2
50#[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
62// section K.3
63// Code lengths and values for table K.3
64static 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
75// Code lengths and values for table K.4
76static 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
87// Code lengths and values for table k.5
88static 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
109// Code lengths and values for table k.6
110static 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/// The permutation of dct coefficients.
141#[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/// A representation of a JPEG component
154#[derive(Copy, Clone)]
155struct Component {
156 /// The Component's identifier
157 id: u8,
158
159 /// Horizontal sampling factor
160 h: u8,
161
162 /// Vertical sampling factor
163 v: u8,
164
165 /// The quantization table selector
166 tq: u8,
167
168 /// Index to the Huffman DC Table
169 dc_table: u8,
170
171 /// Index to the AC Huffman Table
172 ac_table: u8,
173
174 /// The dc prediction of the component
175 _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 // Differential DC encoding
236 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 // Figure F.2
244 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/// Represents a unit in which the density of an image is measured
284#[derive(Clone, Copy, Debug, Eq, PartialEq)]
285pub enum PixelDensityUnit {
286 /// Represents the absence of a unit, the values indicate only a
287 /// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio)
288 PixelAspectRatio,
289
290 /// Pixels per inch (2.54 cm)
291 Inches,
292
293 /// Pixels per centimeter
294 Centimeters,
295}
296
297/// Represents the pixel density of an image
298///
299/// For example, a 300 DPI image is represented by:
300///
301/// ```rust
302/// use image::codecs::jpeg::*;
303/// let hdpi = PixelDensity::dpi(300);
304/// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches})
305/// ```
306#[derive(Clone, Copy, Debug, Eq, PartialEq)]
307pub struct PixelDensity {
308 /// A couple of values for (Xdensity, Ydensity)
309 pub density: (u16, u16),
310 /// The unit in which the density is measured
311 pub unit: PixelDensityUnit,
312}
313
314impl PixelDensity {
315 /// Creates the most common pixel density type:
316 /// the horizontal and the vertical density are equal,
317 /// and measured in pixels per inch.
318 #[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 /// Returns a pixel density with a pixel aspect ratio of 1
329 fn default() -> Self {
330 PixelDensity {
331 density: (1, 1),
332 unit: PixelDensityUnit::PixelAspectRatio,
333 }
334 }
335}
336
337/// The representation of a JPEG encoder
338pub 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 /// Create a new encoder that writes its output to ```w```
356 pub fn new(w: W) -> JpegEncoder<W> {
357 JpegEncoder::new_with_quality(w, 75)
358 }
359
360 /// Create a new encoder that writes its output to ```w```, and has
361 /// the quality parameter ```quality``` with a value in the range 1-100
362 /// where 1 is the worst and 100 is the best.
363 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 // Derive our quantization table scaling value using the libjpeg algorithm
395 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 /// Set the pixel density of the images the encoder will encode.
427 /// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied,
428 /// and no DPI information will be stored in the image.
429 pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) {
430 self.pixel_density = pixel_density;
431 }
432
433 /// Encodes the image stored in the raw byte buffer ```image```
434 /// that has dimensions ```width``` and ```height```
435 /// and ```ColorType``` ```c```
436 ///
437 /// The Image in encoded with subsampling ratio 4:2:2
438 ///
439 /// # Panics
440 ///
441 /// Panics if `width * height * color_type.bytes_per_pixel() != image.len()`.
442 #[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 /// Encodes the given image.
479 ///
480 /// As a special feature this does not require the whole image to be present in memory at the
481 /// same time such that it may be computed on the fly, which is why this method exists on this
482 /// encoder but not on others. Instead the encoder will iterate over 8-by-8 blocks of pixels at
483 /// a time, inspecting each pixel exactly once. You can rely on this behaviour when calling
484 /// this method.
485 ///
486 /// The Image in encoded with subsampling ratio 4:2:2
487 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 // Write ICC profile chunks if present
503 self.write_icc_profile_chunks()?;
504
505 build_frame_header(
506 &mut buf,
507 8,
508 // TODO: not idiomatic yet. Should be an EncodingError and mention jpg. Further it
509 // should check dimensions prior to writing.
510 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 // Level shift and fdct
594 // Coeffs are scaled by 8
595 transform::fdct(&yblock, &mut dct_yblock);
596
597 // Quantization
598 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 // RGB -> YCbCr
628 copy_blocks_ycbcr(image, x, y, &mut yblock, &mut cb_block, &mut cr_block);
629
630 // Level shift and fdct
631 // Coeffs are scaled by 8
632 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 // Quantization
637 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(image: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: Component in components {
749 let hv: u8 = (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: Component in components {
760 let tables: u8 = (comp.dc_table << 4) | comp.ac_table;
761 m.extend_from_slice(&[comp.id, tables]);
762 }
763
764 // spectral start and end, approx. high and low
765 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: u8 = (class << 4) | destination;
778 m.push(tcth);
779
780 m.extend_from_slice(numcodes);
781
782 let sum: usize = numcodes.iter().map(|&x: u8| 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: u8 = if precision == 8 { 0 } else { 1 };
793
794 let pqtq: u8 = (p << 4) | identifier;
795 m.push(pqtq);
796
797 for &i: u8 in &UNZIGZAG[..] {
798 m.push(qtable[i as usize]);
799 }
800}
801
802fn encode_coefficient(coefficient: i32) -> (u8, u16) {
803 let mut magnitude: u16 = coefficient.unsigned_abs() as u16;
804 let mut num_bits: u8 = 0u8;
805
806 while magnitude > 0 {
807 magnitude >>= 1;
808 num_bits += 1;
809 }
810
811 let mask: u16 = (1 << num_bits as usize) - 1;
812
813 let val: u16 = 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 /*
830 JPEG RGB -> YCbCr is defined as following equations using Bt.601 Full Range matrix:
831 Y = 0.29900 * R + 0.58700 * G + 0.11400 * B
832 Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + 128
833 Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + 128
834
835 To avoid using slow floating point conversion is done in fixed point,
836 using following coefficients with rounding to nearest integer mode:
837 */
838
839 const C_YR: i32 = 19595; // 0.29900 = 19595 * 2^-16
840 const C_YG: i32 = 38469; // 0.58700 = 38469 * 2^-16
841 const C_YB: i32 = 7471; // 0.11400 = 7471 * 2^-16
842 const Y_ROUNDING: i32 = (1 << 15) - 1; // + 0.5 to perform rounding shift right in-place
843 const C_UR: i32 = 11059; // 0.16874 = 11059 * 2^-16
844 const C_UG: i32 = 21709; // 0.33126 = 21709 * 2^-16
845 const C_UB: i32 = 32768; // 0.5 = 32768 * 2^-16
846 const UV_BIAS_ROUNDING: i32 = (128 * (1 << 16)) + ((1 << 15) - 1); // 128 + 0.5 = ((128 * (1 << 16)) + ((1 << 15) - 1)) * 2^-16 ; + 0.5 to perform rounding shift right in-place
847 const C_VR: i32 = C_UB; // 0.5 = 32768 * 2^-16
848 const C_VG: i32 = 27439; // 0.41869 = 27439 * 2^-16
849 const C_VB: i32 = 5329; // 0.08131409 = 5329 * 2^-16
850
851 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/// Returns the pixel at (x,y) if (x,y) is in the image,
859/// otherwise the closest pixel in the image
860#[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: u32 in 0..8 {
878 for x: u32 in 0..8 {
879 let pixel: ::Pixel = pixel_at_or_near(source, x:x + x0, y:y + y0);
880 let (yc: u8, cb: u8, cr: u8) = 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: u32 in 0..8 {
892 for x: u32 in 0..8 {
893 let pixel: ::Pixel = pixel_at_or_near(source, x:x0 + x, y:y0 + y);
894 let [luma: <::Pixel as Pixel>::Subpixel] = 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 // create a 1x1 8-bit image buffer containing a single red pixel
933 let img = [255u8, 0, 0];
934
935 // encode it into a memory buffer
936 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 // decode it from the memory buffer
945 {
946 let decoded = decode(&encoded_img);
947 // note that, even with the encode quality set to 100, we do not get the same image
948 // back. Therefore, we're going to assert that it's at least red-ish:
949 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 // create a 2x2 8-bit image buffer containing a white diagonal
959 let img = [255u8, 0, 0, 255];
960
961 // encode it into a memory buffer
962 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 // decode it from the memory buffer
971 {
972 let decoded = decode(&encoded_img);
973 // note that, even with the encode quality set to 100, we do not get the same image
974 // back. Therefore, we're going to assert that the diagonal is at least white-ish:
975 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, // JFIF version 1.2
997 1, // density is in dpi
998 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, // No thumbnail
1004 ]
1005 );
1006 }
1007
1008 #[test]
1009 fn test_image_too_large() {
1010 // JPEG cannot encode images larger than 65,535×65,535
1011 // create a 65,536×1 8-bit black image buffer
1012 let img = [0; 65_536];
1013 // Try to encode an image that is too large
1014 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}
1138