| 1 | #![allow (clippy::too_many_arguments)] |
| 2 | |
| 3 | use crate::error::{ |
| 4 | ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, |
| 5 | UnsupportedErrorKind, |
| 6 | }; |
| 7 | use crate::image::{ImageEncoder, ImageFormat}; |
| 8 | use crate::utils::clamp; |
| 9 | use crate::{ExtendedColorType, GenericImageView, ImageBuffer, Luma, Pixel, Rgb}; |
| 10 | use num_traits::ToPrimitive; |
| 11 | use std::borrow::Cow; |
| 12 | use std::io::{self, Write}; |
| 13 | |
| 14 | use super::entropy::build_huff_lut_const; |
| 15 | use super::transform; |
| 16 | use crate::traits::PixelWithColorType; |
| 17 | |
| 18 | // Markers |
| 19 | // Baseline DCT |
| 20 | static SOF0: u8 = 0xC0; |
| 21 | // Huffman Tables |
| 22 | static DHT: u8 = 0xC4; |
| 23 | // Start of Image (standalone) |
| 24 | static SOI: u8 = 0xD8; |
| 25 | // End of image (standalone) |
| 26 | static EOI: u8 = 0xD9; |
| 27 | // Start of Scan |
| 28 | static SOS: u8 = 0xDA; |
| 29 | // Quantization Tables |
| 30 | static DQT: u8 = 0xDB; |
| 31 | // Application segments start and end |
| 32 | static APP0: u8 = 0xE0; |
| 33 | static APP2: u8 = 0xE2; |
| 34 | |
| 35 | // section K.1 |
| 36 | // table K.1 |
| 37 | #[rustfmt::skip] |
| 38 | static 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] |
| 51 | static 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 |
| 64 | static 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 | |
| 68 | static STD_LUMA_DC_VALUES: [u8; 12] = [ |
| 69 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, |
| 70 | ]; |
| 71 | |
| 72 | static 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 |
| 76 | static 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 | |
| 80 | static STD_CHROMA_DC_VALUES: [u8; 12] = [ |
| 81 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, |
| 82 | ]; |
| 83 | |
| 84 | static 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 |
| 88 | static 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 | |
| 92 | static 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 | |
| 106 | static 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 |
| 110 | static 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 | ]; |
| 113 | static 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 | |
| 127 | static STD_CHROMA_AC_HUFF_LUT: [(u8, u16); 256] = |
| 128 | build_huff_lut_const(&STD_CHROMA_AC_CODE_LENGTHS, &STD_CHROMA_AC_VALUES); |
| 129 | |
| 130 | static DCCLASS: u8 = 0; |
| 131 | static ACCLASS: u8 = 1; |
| 132 | |
| 133 | static LUMADESTINATION: u8 = 0; |
| 134 | static CHROMADESTINATION: u8 = 1; |
| 135 | |
| 136 | static LUMAID: u8 = 1; |
| 137 | static CHROMABLUEID: u8 = 2; |
| 138 | static CHROMAREDID: u8 = 3; |
| 139 | |
| 140 | /// The permutation of dct coefficients. |
| 141 | #[rustfmt::skip] |
| 142 | static 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)] |
| 155 | struct 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 | |
| 178 | pub(crate) struct BitWriter<W> { |
| 179 | w: W, |
| 180 | accumulator: u32, |
| 181 | nbits: u8, |
| 182 | } |
| 183 | |
| 184 | impl<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)] |
| 285 | pub 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)] |
| 307 | pub 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 | |
| 314 | impl 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 | |
| 327 | impl 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 |
| 338 | pub 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 | |
| 354 | impl<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 | |
| 698 | impl<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 | |
| 716 | fn 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 | |
| 734 | fn 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 | |
| 754 | fn 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 | |
| 768 | fn 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 | |
| 789 | fn 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 | |
| 802 | fn 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 ] |
| 823 | fn 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 ] |
| 861 | fn 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 | |
| 869 | fn 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 | |
| 889 | fn 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)] |
| 901 | mod 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 | |