| 1 | //! Encoding of WebP images. |
| 2 | use std::collections::BinaryHeap; |
| 3 | use std::io::{self, Write}; |
| 4 | use std::slice::ChunksExact; |
| 5 | |
| 6 | use quick_error::quick_error; |
| 7 | |
| 8 | /// Color type of the image. |
| 9 | /// |
| 10 | /// Note that the WebP format doesn't have a concept of color type. All images are encoded as RGBA |
| 11 | /// and some decoders may treat them as such. This enum is used to indicate the color type of the |
| 12 | /// input data provided to the encoder, which can help improve compression ratio. |
| 13 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
| 14 | pub enum ColorType { |
| 15 | /// Opaque image with a single luminance byte per pixel. |
| 16 | L8, |
| 17 | /// Image with a luminance and alpha byte per pixel. |
| 18 | La8, |
| 19 | /// Opaque image with a red, green, and blue byte per pixel. |
| 20 | Rgb8, |
| 21 | /// Image with a red, green, blue, and alpha byte per pixel. |
| 22 | Rgba8, |
| 23 | } |
| 24 | |
| 25 | quick_error! { |
| 26 | /// Error that can occur during encoding. |
| 27 | #[derive (Debug)] |
| 28 | #[non_exhaustive ] |
| 29 | pub enum EncodingError { |
| 30 | /// An IO error occurred. |
| 31 | IoError(err: io::Error) { |
| 32 | from() |
| 33 | display("IO error: {}" , err) |
| 34 | source(err) |
| 35 | } |
| 36 | |
| 37 | /// The image dimensions are not allowed by the WebP format. |
| 38 | InvalidDimensions { |
| 39 | display("Invalid dimensions" ) |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | struct BitWriter<W> { |
| 45 | writer: W, |
| 46 | buffer: u64, |
| 47 | nbits: u8, |
| 48 | } |
| 49 | |
| 50 | impl<W: Write> BitWriter<W> { |
| 51 | fn write_bits(&mut self, bits: u64, nbits: u8) -> io::Result<()> { |
| 52 | debug_assert!(nbits <= 64); |
| 53 | |
| 54 | self.buffer |= bits << self.nbits; |
| 55 | self.nbits += nbits; |
| 56 | |
| 57 | if self.nbits >= 64 { |
| 58 | self.writer.write_all(&self.buffer.to_le_bytes())?; |
| 59 | self.nbits -= 64; |
| 60 | self.buffer = bits.checked_shr(u32::from(nbits - self.nbits)).unwrap_or(0); |
| 61 | } |
| 62 | debug_assert!(self.nbits < 64); |
| 63 | Ok(()) |
| 64 | } |
| 65 | |
| 66 | fn flush(&mut self) -> io::Result<()> { |
| 67 | if self.nbits % 8 != 0 { |
| 68 | self.write_bits(0, 8 - self.nbits % 8)?; |
| 69 | } |
| 70 | if self.nbits > 0 { |
| 71 | self.writer |
| 72 | .write_all(&self.buffer.to_le_bytes()[..self.nbits as usize / 8]) |
| 73 | .unwrap(); |
| 74 | self.buffer = 0; |
| 75 | self.nbits = 0; |
| 76 | } |
| 77 | Ok(()) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | fn write_single_entry_huffman_tree<W: Write>(w: &mut BitWriter<W>, symbol: u8) -> io::Result<()> { |
| 82 | w.write_bits(bits:1, nbits:2)?; |
| 83 | if symbol <= 1 { |
| 84 | w.write_bits(bits:0, nbits:1)?; |
| 85 | w.write_bits(bits:u64::from(symbol), nbits:1)?; |
| 86 | } else { |
| 87 | w.write_bits(bits:1, nbits:1)?; |
| 88 | w.write_bits(bits:u64::from(symbol), nbits:8)?; |
| 89 | } |
| 90 | Ok(()) |
| 91 | } |
| 92 | |
| 93 | fn build_huffman_tree( |
| 94 | frequencies: &[u32], |
| 95 | lengths: &mut [u8], |
| 96 | codes: &mut [u16], |
| 97 | length_limit: u8, |
| 98 | ) -> bool { |
| 99 | assert_eq!(frequencies.len(), lengths.len()); |
| 100 | assert_eq!(frequencies.len(), codes.len()); |
| 101 | |
| 102 | if frequencies.iter().filter(|&&f| f > 0).count() <= 1 { |
| 103 | lengths.fill(0); |
| 104 | codes.fill(0); |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | #[derive (Eq, PartialEq, Copy, Clone, Debug)] |
| 109 | struct Item(u32, u16); |
| 110 | impl Ord for Item { |
| 111 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| 112 | other.0.cmp(&self.0) |
| 113 | } |
| 114 | } |
| 115 | impl PartialOrd for Item { |
| 116 | fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 117 | Some(self.cmp(other)) |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // Build a huffman tree |
| 122 | let mut internal_nodes = Vec::new(); |
| 123 | let mut nodes = BinaryHeap::from_iter( |
| 124 | frequencies |
| 125 | .iter() |
| 126 | .enumerate() |
| 127 | .filter(|(_, &frequency)| frequency > 0) |
| 128 | .map(|(i, &frequency)| Item(frequency, i as u16)), |
| 129 | ); |
| 130 | while nodes.len() > 1 { |
| 131 | let Item(frequency1, index1) = nodes.pop().unwrap(); |
| 132 | let mut root = nodes.peek_mut().unwrap(); |
| 133 | internal_nodes.push((index1, root.1)); |
| 134 | *root = Item( |
| 135 | frequency1 + root.0, |
| 136 | internal_nodes.len() as u16 + frequencies.len() as u16 - 1, |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | // Walk the tree to assign code lengths |
| 141 | lengths.fill(0); |
| 142 | let mut stack = Vec::new(); |
| 143 | stack.push((nodes.pop().unwrap().1, 0)); |
| 144 | while let Some((node, depth)) = stack.pop() { |
| 145 | let node = node as usize; |
| 146 | if node < frequencies.len() { |
| 147 | lengths[node] = depth as u8; |
| 148 | } else { |
| 149 | let (left, right) = internal_nodes[node - frequencies.len()]; |
| 150 | stack.push((left, depth + 1)); |
| 151 | stack.push((right, depth + 1)); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | // Limit the codes to length length_limit |
| 156 | let mut max_length = 0; |
| 157 | for &length in lengths.iter() { |
| 158 | max_length = max_length.max(length); |
| 159 | } |
| 160 | if max_length > length_limit { |
| 161 | let mut counts = [0u32; 16]; |
| 162 | for &length in lengths.iter() { |
| 163 | counts[length.min(length_limit) as usize] += 1; |
| 164 | } |
| 165 | |
| 166 | let mut total = 0; |
| 167 | for (i, count) in counts |
| 168 | .iter() |
| 169 | .enumerate() |
| 170 | .skip(1) |
| 171 | .take(length_limit as usize) |
| 172 | { |
| 173 | total += count << (length_limit as usize - i); |
| 174 | } |
| 175 | |
| 176 | while total > 1u32 << length_limit { |
| 177 | let mut i = length_limit as usize - 1; |
| 178 | while counts[i] == 0 { |
| 179 | i -= 1; |
| 180 | } |
| 181 | counts[i] -= 1; |
| 182 | counts[length_limit as usize] -= 1; |
| 183 | counts[i + 1] += 2; |
| 184 | total -= 1; |
| 185 | } |
| 186 | |
| 187 | // assign new lengths |
| 188 | let mut len = length_limit; |
| 189 | let mut indexes = frequencies.iter().copied().enumerate().collect::<Vec<_>>(); |
| 190 | indexes.sort_unstable_by_key(|&(_, frequency)| frequency); |
| 191 | for &(i, frequency) in &indexes { |
| 192 | if frequency > 0 { |
| 193 | while counts[len as usize] == 0 { |
| 194 | len -= 1; |
| 195 | } |
| 196 | lengths[i] = len; |
| 197 | counts[len as usize] -= 1; |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // Assign codes |
| 203 | codes.fill(0); |
| 204 | let mut code = 0u32; |
| 205 | for len in 1..=length_limit { |
| 206 | for (i, &length) in lengths.iter().enumerate() { |
| 207 | if length == len { |
| 208 | codes[i] = (code as u16).reverse_bits() >> (16 - len); |
| 209 | code += 1; |
| 210 | } |
| 211 | } |
| 212 | code <<= 1; |
| 213 | } |
| 214 | assert_eq!(code, 2 << length_limit); |
| 215 | |
| 216 | true |
| 217 | } |
| 218 | |
| 219 | fn write_huffman_tree<W: Write>( |
| 220 | w: &mut BitWriter<W>, |
| 221 | frequencies: &[u32], |
| 222 | lengths: &mut [u8], |
| 223 | codes: &mut [u16], |
| 224 | ) -> io::Result<()> { |
| 225 | if !build_huffman_tree(frequencies, lengths, codes, 15) { |
| 226 | let symbol = frequencies |
| 227 | .iter() |
| 228 | .position(|&frequency| frequency > 0) |
| 229 | .unwrap_or(0); |
| 230 | return write_single_entry_huffman_tree(w, symbol as u8); |
| 231 | } |
| 232 | |
| 233 | let mut code_length_lengths = [0u8; 16]; |
| 234 | let mut code_length_codes = [0u16; 16]; |
| 235 | let mut code_length_frequencies = [0u32; 16]; |
| 236 | for &length in lengths.iter() { |
| 237 | code_length_frequencies[length as usize] += 1; |
| 238 | } |
| 239 | let single_code_length_length = !build_huffman_tree( |
| 240 | &code_length_frequencies, |
| 241 | &mut code_length_lengths, |
| 242 | &mut code_length_codes, |
| 243 | 7, |
| 244 | ); |
| 245 | |
| 246 | const CODE_LENGTH_ORDER: [usize; 19] = [ |
| 247 | 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 248 | ]; |
| 249 | |
| 250 | // Write the huffman tree |
| 251 | w.write_bits(0, 1)?; // normal huffman tree |
| 252 | w.write_bits(19 - 4, 4)?; // num_code_lengths - 4 |
| 253 | |
| 254 | for i in CODE_LENGTH_ORDER { |
| 255 | if i > 15 || code_length_frequencies[i] == 0 { |
| 256 | w.write_bits(0, 3)?; |
| 257 | } else if single_code_length_length { |
| 258 | w.write_bits(1, 3)?; |
| 259 | } else { |
| 260 | w.write_bits(u64::from(code_length_lengths[i]), 3)?; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | match lengths.len() { |
| 265 | 256 => { |
| 266 | w.write_bits(1, 1)?; // max_symbol is stored |
| 267 | w.write_bits(3, 3)?; // max_symbol_nbits / 2 - 2 |
| 268 | w.write_bits(254, 8)?; // max_symbol - 2 |
| 269 | } |
| 270 | 280 => w.write_bits(0, 1)?, |
| 271 | _ => unreachable!(), |
| 272 | } |
| 273 | |
| 274 | // Write the huffman codes |
| 275 | if !single_code_length_length { |
| 276 | for &len in lengths.iter() { |
| 277 | w.write_bits( |
| 278 | u64::from(code_length_codes[len as usize]), |
| 279 | code_length_lengths[len as usize], |
| 280 | )?; |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | Ok(()) |
| 285 | } |
| 286 | |
| 287 | const fn length_to_symbol(len: u16) -> (u16, u8) { |
| 288 | let len: u16 = len - 1; |
| 289 | let highest_bit: u16 = len.ilog2() as u16; |
| 290 | let second_highest_bit: u16 = (len >> (highest_bit - 1)) & 1; |
| 291 | let extra_bits: u16 = highest_bit - 1; |
| 292 | let symbol: u16 = 2 * highest_bit + second_highest_bit; |
| 293 | (symbol, extra_bits as u8) |
| 294 | } |
| 295 | |
| 296 | #[inline (always)] |
| 297 | fn count_run( |
| 298 | pixel: &[u8], |
| 299 | it: &mut std::iter::Peekable<ChunksExact<u8>>, |
| 300 | frequencies1: &mut [u32; 280], |
| 301 | ) { |
| 302 | let mut run_length: usize = 0; |
| 303 | while run_length < 4096 && it.peek() == Some(&pixel) { |
| 304 | run_length += 1; |
| 305 | it.next(); |
| 306 | } |
| 307 | if run_length > 0 { |
| 308 | if run_length <= 4 { |
| 309 | let symbol: usize = 256 + run_length - 1; |
| 310 | frequencies1[symbol] += 1; |
| 311 | } else { |
| 312 | let (symbol: u16, _extra_bits: u8) = length_to_symbol(len:run_length as u16); |
| 313 | frequencies1[256 + symbol as usize] += 1; |
| 314 | } |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | #[inline (always)] |
| 319 | fn write_run<W: Write>( |
| 320 | w: &mut BitWriter<W>, |
| 321 | pixel: &[u8], |
| 322 | it: &mut std::iter::Peekable<ChunksExact<u8>>, |
| 323 | codes1: &[u16; 280], |
| 324 | lengths1: &[u8; 280], |
| 325 | ) -> io::Result<()> { |
| 326 | let mut run_length: usize = 0; |
| 327 | while run_length < 4096 && it.peek() == Some(&pixel) { |
| 328 | run_length += 1; |
| 329 | it.next(); |
| 330 | } |
| 331 | if run_length > 0 { |
| 332 | if run_length <= 4 { |
| 333 | let symbol: usize = 256 + run_length - 1; |
| 334 | w.write_bits(bits:u64::from(codes1[symbol]), nbits:lengths1[symbol])?; |
| 335 | } else { |
| 336 | let (symbol: u16, extra_bits: u8) = length_to_symbol(len:run_length as u16); |
| 337 | w.write_bits( |
| 338 | bits:u64::from(codes1[256 + symbol as usize]), |
| 339 | nbits:lengths1[256 + symbol as usize], |
| 340 | )?; |
| 341 | w.write_bits( |
| 342 | (run_length as u64 - 1) & ((1 << extra_bits) - 1), |
| 343 | nbits:extra_bits, |
| 344 | )?; |
| 345 | } |
| 346 | } |
| 347 | Ok(()) |
| 348 | } |
| 349 | |
| 350 | /// Allows fine-tuning some encoder parameters. |
| 351 | /// |
| 352 | /// Pass to [`WebPEncoder::set_params()`]. |
| 353 | #[non_exhaustive ] |
| 354 | #[derive (Clone, Debug)] |
| 355 | pub struct EncoderParams { |
| 356 | /// Use a predictor transform. Enabled by default. |
| 357 | pub use_predictor_transform: bool, |
| 358 | } |
| 359 | |
| 360 | impl Default for EncoderParams { |
| 361 | fn default() -> Self { |
| 362 | Self { |
| 363 | use_predictor_transform: true, |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | /// Encode image data with the indicated color type. |
| 369 | /// |
| 370 | /// # Panics |
| 371 | /// |
| 372 | /// Panics if the image data is not of the indicated dimensions. |
| 373 | fn encode_frame<W: Write>( |
| 374 | writer: W, |
| 375 | data: &[u8], |
| 376 | width: u32, |
| 377 | height: u32, |
| 378 | color: ColorType, |
| 379 | params: EncoderParams, |
| 380 | ) -> Result<(), EncodingError> { |
| 381 | let w = &mut BitWriter { |
| 382 | writer, |
| 383 | buffer: 0, |
| 384 | nbits: 0, |
| 385 | }; |
| 386 | |
| 387 | let (is_color, is_alpha, bytes_per_pixel) = match color { |
| 388 | ColorType::L8 => (false, false, 1), |
| 389 | ColorType::La8 => (false, true, 2), |
| 390 | ColorType::Rgb8 => (true, false, 3), |
| 391 | ColorType::Rgba8 => (true, true, 4), |
| 392 | }; |
| 393 | |
| 394 | assert_eq!( |
| 395 | (u64::from(width) * u64::from(height)).saturating_mul(bytes_per_pixel), |
| 396 | data.len() as u64 |
| 397 | ); |
| 398 | |
| 399 | if width == 0 || width > 16384 || height == 0 || height > 16384 { |
| 400 | return Err(EncodingError::InvalidDimensions); |
| 401 | } |
| 402 | |
| 403 | w.write_bits(0x2f, 8)?; // signature |
| 404 | w.write_bits(u64::from(width) - 1, 14)?; |
| 405 | w.write_bits(u64::from(height) - 1, 14)?; |
| 406 | |
| 407 | w.write_bits(u64::from(is_alpha), 1)?; // alpha used |
| 408 | w.write_bits(0x0, 3)?; // version |
| 409 | |
| 410 | // subtract green transform |
| 411 | w.write_bits(0b101, 3)?; |
| 412 | |
| 413 | // predictor transform |
| 414 | if params.use_predictor_transform { |
| 415 | w.write_bits(0b111001, 6)?; |
| 416 | w.write_bits(0x0, 1)?; // no color cache |
| 417 | write_single_entry_huffman_tree(w, 2)?; |
| 418 | for _ in 0..4 { |
| 419 | write_single_entry_huffman_tree(w, 0)?; |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | // transforms done |
| 424 | w.write_bits(0x0, 1)?; |
| 425 | |
| 426 | // color cache |
| 427 | w.write_bits(0x0, 1)?; |
| 428 | |
| 429 | // meta-huffman codes |
| 430 | w.write_bits(0x0, 1)?; |
| 431 | |
| 432 | // expand to RGBA |
| 433 | let mut pixels = match color { |
| 434 | ColorType::L8 => data.iter().flat_map(|&p| [p, p, p, 255]).collect(), |
| 435 | ColorType::La8 => data |
| 436 | .chunks_exact(2) |
| 437 | .flat_map(|p| [p[0], p[0], p[0], p[1]]) |
| 438 | .collect(), |
| 439 | ColorType::Rgb8 => data |
| 440 | .chunks_exact(3) |
| 441 | .flat_map(|p| [p[0], p[1], p[2], 255]) |
| 442 | .collect(), |
| 443 | ColorType::Rgba8 => data.to_vec(), |
| 444 | }; |
| 445 | |
| 446 | // compute subtract green transform |
| 447 | for pixel in pixels.chunks_exact_mut(4) { |
| 448 | pixel[0] = pixel[0].wrapping_sub(pixel[1]); |
| 449 | pixel[2] = pixel[2].wrapping_sub(pixel[1]); |
| 450 | } |
| 451 | |
| 452 | // compute predictor transform |
| 453 | if params.use_predictor_transform { |
| 454 | let row_bytes = width as usize * 4; |
| 455 | for y in (1..height as usize).rev() { |
| 456 | let (prev, current) = |
| 457 | pixels[(y - 1) * row_bytes..][..row_bytes * 2].split_at_mut(row_bytes); |
| 458 | for (c, p) in current.iter_mut().zip(prev) { |
| 459 | *c = c.wrapping_sub(*p); |
| 460 | } |
| 461 | } |
| 462 | for i in (4..row_bytes).rev() { |
| 463 | pixels[i] = pixels[i].wrapping_sub(pixels[i - 4]); |
| 464 | } |
| 465 | pixels[3] = pixels[3].wrapping_sub(255); |
| 466 | } |
| 467 | |
| 468 | // compute frequencies |
| 469 | let mut frequencies0 = [0u32; 256]; |
| 470 | let mut frequencies1 = [0u32; 280]; |
| 471 | let mut frequencies2 = [0u32; 256]; |
| 472 | let mut frequencies3 = [0u32; 256]; |
| 473 | let mut it = pixels.chunks_exact(4).peekable(); |
| 474 | match color { |
| 475 | ColorType::L8 => { |
| 476 | frequencies0[0] = 1; |
| 477 | frequencies2[0] = 1; |
| 478 | frequencies3[0] = 1; |
| 479 | while let Some(pixel) = it.next() { |
| 480 | frequencies1[pixel[1] as usize] += 1; |
| 481 | count_run(pixel, &mut it, &mut frequencies1); |
| 482 | } |
| 483 | } |
| 484 | ColorType::La8 => { |
| 485 | frequencies0[0] = 1; |
| 486 | frequencies2[0] = 1; |
| 487 | while let Some(pixel) = it.next() { |
| 488 | frequencies1[pixel[1] as usize] += 1; |
| 489 | frequencies3[pixel[3] as usize] += 1; |
| 490 | count_run(pixel, &mut it, &mut frequencies1); |
| 491 | } |
| 492 | } |
| 493 | ColorType::Rgb8 => { |
| 494 | frequencies3[0] = 1; |
| 495 | while let Some(pixel) = it.next() { |
| 496 | frequencies0[pixel[0] as usize] += 1; |
| 497 | frequencies1[pixel[1] as usize] += 1; |
| 498 | frequencies2[pixel[2] as usize] += 1; |
| 499 | count_run(pixel, &mut it, &mut frequencies1); |
| 500 | } |
| 501 | } |
| 502 | ColorType::Rgba8 => { |
| 503 | while let Some(pixel) = it.next() { |
| 504 | frequencies0[pixel[0] as usize] += 1; |
| 505 | frequencies1[pixel[1] as usize] += 1; |
| 506 | frequencies2[pixel[2] as usize] += 1; |
| 507 | frequencies3[pixel[3] as usize] += 1; |
| 508 | count_run(pixel, &mut it, &mut frequencies1); |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | // compute and write huffman codes |
| 514 | let mut lengths0 = [0u8; 256]; |
| 515 | let mut lengths1 = [0u8; 280]; |
| 516 | let mut lengths2 = [0u8; 256]; |
| 517 | let mut lengths3 = [0u8; 256]; |
| 518 | let mut codes0 = [0u16; 256]; |
| 519 | let mut codes1 = [0u16; 280]; |
| 520 | let mut codes2 = [0u16; 256]; |
| 521 | let mut codes3 = [0u16; 256]; |
| 522 | write_huffman_tree(w, &frequencies1, &mut lengths1, &mut codes1)?; |
| 523 | if is_color { |
| 524 | write_huffman_tree(w, &frequencies0, &mut lengths0, &mut codes0)?; |
| 525 | write_huffman_tree(w, &frequencies2, &mut lengths2, &mut codes2)?; |
| 526 | } else { |
| 527 | write_single_entry_huffman_tree(w, 0)?; |
| 528 | write_single_entry_huffman_tree(w, 0)?; |
| 529 | } |
| 530 | if is_alpha { |
| 531 | write_huffman_tree(w, &frequencies3, &mut lengths3, &mut codes3)?; |
| 532 | } else if params.use_predictor_transform { |
| 533 | write_single_entry_huffman_tree(w, 0)?; |
| 534 | } else { |
| 535 | write_single_entry_huffman_tree(w, 255)?; |
| 536 | } |
| 537 | write_single_entry_huffman_tree(w, 1)?; |
| 538 | |
| 539 | // Write image data |
| 540 | let mut it = pixels.chunks_exact(4).peekable(); |
| 541 | match color { |
| 542 | ColorType::L8 => { |
| 543 | while let Some(pixel) = it.next() { |
| 544 | w.write_bits( |
| 545 | u64::from(codes1[pixel[1] as usize]), |
| 546 | lengths1[pixel[1] as usize], |
| 547 | )?; |
| 548 | write_run(w, pixel, &mut it, &codes1, &lengths1)?; |
| 549 | } |
| 550 | } |
| 551 | ColorType::La8 => { |
| 552 | while let Some(pixel) = it.next() { |
| 553 | let len1 = lengths1[pixel[1] as usize]; |
| 554 | let len3 = lengths3[pixel[3] as usize]; |
| 555 | |
| 556 | let code = u64::from(codes1[pixel[1] as usize]) |
| 557 | | (u64::from(codes3[pixel[3] as usize]) << len1); |
| 558 | |
| 559 | w.write_bits(code, len1 + len3)?; |
| 560 | write_run(w, pixel, &mut it, &codes1, &lengths1)?; |
| 561 | } |
| 562 | } |
| 563 | ColorType::Rgb8 => { |
| 564 | while let Some(pixel) = it.next() { |
| 565 | let len1 = lengths1[pixel[1] as usize]; |
| 566 | let len0 = lengths0[pixel[0] as usize]; |
| 567 | let len2 = lengths2[pixel[2] as usize]; |
| 568 | |
| 569 | let code = u64::from(codes1[pixel[1] as usize]) |
| 570 | | (u64::from(codes0[pixel[0] as usize]) << len1) |
| 571 | | (u64::from(codes2[pixel[2] as usize]) << (len1 + len0)); |
| 572 | |
| 573 | w.write_bits(code, len1 + len0 + len2)?; |
| 574 | write_run(w, pixel, &mut it, &codes1, &lengths1)?; |
| 575 | } |
| 576 | } |
| 577 | ColorType::Rgba8 => { |
| 578 | while let Some(pixel) = it.next() { |
| 579 | let len1 = lengths1[pixel[1] as usize]; |
| 580 | let len0 = lengths0[pixel[0] as usize]; |
| 581 | let len2 = lengths2[pixel[2] as usize]; |
| 582 | let len3 = lengths3[pixel[3] as usize]; |
| 583 | |
| 584 | let code = u64::from(codes1[pixel[1] as usize]) |
| 585 | | (u64::from(codes0[pixel[0] as usize]) << len1) |
| 586 | | (u64::from(codes2[pixel[2] as usize]) << (len1 + len0)) |
| 587 | | (u64::from(codes3[pixel[3] as usize]) << (len1 + len0 + len2)); |
| 588 | |
| 589 | w.write_bits(code, len1 + len0 + len2 + len3)?; |
| 590 | write_run(w, pixel, &mut it, &codes1, &lengths1)?; |
| 591 | } |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | w.flush()?; |
| 596 | Ok(()) |
| 597 | } |
| 598 | |
| 599 | const fn chunk_size(inner_bytes: usize) -> u32 { |
| 600 | if inner_bytes % 2 == 1 { |
| 601 | (inner_bytes + 1) as u32 + 8 |
| 602 | } else { |
| 603 | inner_bytes as u32 + 8 |
| 604 | } |
| 605 | } |
| 606 | |
| 607 | fn write_chunk<W: Write>(mut w: W, name: &[u8], data: &[u8]) -> io::Result<()> { |
| 608 | debug_assert!(name.len() == 4); |
| 609 | |
| 610 | w.write_all(buf:name)?; |
| 611 | w.write_all(&(data.len() as u32).to_le_bytes())?; |
| 612 | w.write_all(buf:data)?; |
| 613 | if data.len() % 2 == 1 { |
| 614 | w.write_all(&[0])?; |
| 615 | } |
| 616 | Ok(()) |
| 617 | } |
| 618 | |
| 619 | /// WebP Encoder. |
| 620 | pub struct WebPEncoder<W> { |
| 621 | writer: W, |
| 622 | icc_profile: Vec<u8>, |
| 623 | exif_metadata: Vec<u8>, |
| 624 | xmp_metadata: Vec<u8>, |
| 625 | params: EncoderParams, |
| 626 | } |
| 627 | |
| 628 | impl<W: Write> WebPEncoder<W> { |
| 629 | /// Create a new encoder that writes its output to `w`. |
| 630 | /// |
| 631 | /// Only supports "VP8L" lossless encoding. |
| 632 | pub fn new(w: W) -> Self { |
| 633 | Self { |
| 634 | writer: w, |
| 635 | icc_profile: Vec::new(), |
| 636 | exif_metadata: Vec::new(), |
| 637 | xmp_metadata: Vec::new(), |
| 638 | params: EncoderParams::default(), |
| 639 | } |
| 640 | } |
| 641 | |
| 642 | /// Set the ICC profile to use for the image. |
| 643 | pub fn set_icc_profile(&mut self, icc_profile: Vec<u8>) { |
| 644 | self.icc_profile = icc_profile; |
| 645 | } |
| 646 | |
| 647 | /// Set the EXIF metadata to use for the image. |
| 648 | pub fn set_exif_metadata(&mut self, exif_metadata: Vec<u8>) { |
| 649 | self.exif_metadata = exif_metadata; |
| 650 | } |
| 651 | |
| 652 | /// Set the XMP metadata to use for the image. |
| 653 | pub fn set_xmp_metadata(&mut self, xmp_metadata: Vec<u8>) { |
| 654 | self.xmp_metadata = xmp_metadata; |
| 655 | } |
| 656 | |
| 657 | /// Set the `EncoderParams` to use. |
| 658 | pub fn set_params(&mut self, params: EncoderParams) { |
| 659 | self.params = params; |
| 660 | } |
| 661 | |
| 662 | /// Encode image data with the indicated color type. |
| 663 | /// |
| 664 | /// # Panics |
| 665 | /// |
| 666 | /// Panics if the image data is not of the indicated dimensions. |
| 667 | pub fn encode( |
| 668 | mut self, |
| 669 | data: &[u8], |
| 670 | width: u32, |
| 671 | height: u32, |
| 672 | color: ColorType, |
| 673 | ) -> Result<(), EncodingError> { |
| 674 | let mut frame = Vec::new(); |
| 675 | encode_frame(&mut frame, data, width, height, color, self.params)?; |
| 676 | |
| 677 | // If the image has no metadata, it can be encoded with the "simple" WebP container format. |
| 678 | if self.icc_profile.is_empty() |
| 679 | && self.exif_metadata.is_empty() |
| 680 | && self.xmp_metadata.is_empty() |
| 681 | { |
| 682 | self.writer.write_all(b"RIFF" )?; |
| 683 | self.writer |
| 684 | .write_all(&(chunk_size(frame.len()) + 4).to_le_bytes())?; |
| 685 | self.writer.write_all(b"WEBP" )?; |
| 686 | write_chunk(&mut self.writer, b"VP8L" , &frame)?; |
| 687 | } else { |
| 688 | let mut total_bytes = 22 + chunk_size(frame.len()); |
| 689 | if !self.icc_profile.is_empty() { |
| 690 | total_bytes += chunk_size(self.icc_profile.len()); |
| 691 | } |
| 692 | if !self.exif_metadata.is_empty() { |
| 693 | total_bytes += chunk_size(self.exif_metadata.len()); |
| 694 | } |
| 695 | if !self.xmp_metadata.is_empty() { |
| 696 | total_bytes += chunk_size(self.xmp_metadata.len()); |
| 697 | } |
| 698 | |
| 699 | let mut flags = 0; |
| 700 | if !self.xmp_metadata.is_empty() { |
| 701 | flags |= 1 << 2; |
| 702 | } |
| 703 | if !self.exif_metadata.is_empty() { |
| 704 | flags |= 1 << 3; |
| 705 | } |
| 706 | if let ColorType::La8 | ColorType::Rgba8 = color { |
| 707 | flags |= 1 << 4; |
| 708 | } |
| 709 | if !self.icc_profile.is_empty() { |
| 710 | flags |= 1 << 5; |
| 711 | } |
| 712 | |
| 713 | self.writer.write_all(b"RIFF" )?; |
| 714 | self.writer.write_all(&total_bytes.to_le_bytes())?; |
| 715 | self.writer.write_all(b"WEBP" )?; |
| 716 | |
| 717 | let mut vp8x = Vec::new(); |
| 718 | vp8x.write_all(&[flags])?; // flags |
| 719 | vp8x.write_all(&[0; 3])?; // reserved |
| 720 | vp8x.write_all(&(width - 1).to_le_bytes()[..3])?; // canvas width |
| 721 | vp8x.write_all(&(height - 1).to_le_bytes()[..3])?; // canvas height |
| 722 | write_chunk(&mut self.writer, b"VP8X" , &vp8x)?; |
| 723 | |
| 724 | if !self.icc_profile.is_empty() { |
| 725 | write_chunk(&mut self.writer, b"ICCP" , &self.icc_profile)?; |
| 726 | } |
| 727 | |
| 728 | write_chunk(&mut self.writer, b"VP8L" , &frame)?; |
| 729 | |
| 730 | if !self.exif_metadata.is_empty() { |
| 731 | write_chunk(&mut self.writer, b"EXIF" , &self.exif_metadata)?; |
| 732 | } |
| 733 | |
| 734 | if !self.xmp_metadata.is_empty() { |
| 735 | write_chunk(&mut self.writer, b"XMP " , &self.xmp_metadata)?; |
| 736 | } |
| 737 | } |
| 738 | |
| 739 | Ok(()) |
| 740 | } |
| 741 | } |
| 742 | |
| 743 | #[cfg (test)] |
| 744 | mod tests { |
| 745 | use rand::RngCore; |
| 746 | |
| 747 | use super::*; |
| 748 | |
| 749 | #[test ] |
| 750 | fn write_webp() { |
| 751 | let mut img = vec![0; 256 * 256 * 4]; |
| 752 | rand::thread_rng().fill_bytes(&mut img); |
| 753 | |
| 754 | let mut output = Vec::new(); |
| 755 | WebPEncoder::new(&mut output) |
| 756 | .encode(&img, 256, 256, crate::ColorType::Rgba8) |
| 757 | .unwrap(); |
| 758 | |
| 759 | let mut decoder = crate::WebPDecoder::new(std::io::Cursor::new(output)).unwrap(); |
| 760 | let mut img2 = vec![0; 256 * 256 * 4]; |
| 761 | decoder.read_image(&mut img2).unwrap(); |
| 762 | assert_eq!(img, img2); |
| 763 | } |
| 764 | |
| 765 | #[test ] |
| 766 | fn write_webp_exif() { |
| 767 | let mut img = vec![0; 256 * 256 * 3]; |
| 768 | rand::thread_rng().fill_bytes(&mut img); |
| 769 | |
| 770 | let mut exif = vec![0; 10]; |
| 771 | rand::thread_rng().fill_bytes(&mut exif); |
| 772 | |
| 773 | let mut output = Vec::new(); |
| 774 | let mut encoder = WebPEncoder::new(&mut output); |
| 775 | encoder.set_exif_metadata(exif.clone()); |
| 776 | encoder |
| 777 | .encode(&img, 256, 256, crate::ColorType::Rgb8) |
| 778 | .unwrap(); |
| 779 | |
| 780 | let mut decoder = crate::WebPDecoder::new(std::io::Cursor::new(output)).unwrap(); |
| 781 | |
| 782 | let mut img2 = vec![0; 256 * 256 * 3]; |
| 783 | decoder.read_image(&mut img2).unwrap(); |
| 784 | assert_eq!(img, img2); |
| 785 | |
| 786 | let exif2 = decoder.exif_metadata().unwrap(); |
| 787 | assert_eq!(Some(exif), exif2); |
| 788 | } |
| 789 | |
| 790 | #[test ] |
| 791 | fn roundtrip_libwebp() { |
| 792 | roundtrip_libwebp_params(EncoderParams::default()); |
| 793 | roundtrip_libwebp_params(EncoderParams { |
| 794 | use_predictor_transform: false, |
| 795 | ..Default::default() |
| 796 | }); |
| 797 | } |
| 798 | |
| 799 | fn roundtrip_libwebp_params(params: EncoderParams) { |
| 800 | println!("Testing {params:?}" ); |
| 801 | |
| 802 | let mut img = vec![0; 256 * 256 * 4]; |
| 803 | rand::thread_rng().fill_bytes(&mut img); |
| 804 | |
| 805 | let mut output = Vec::new(); |
| 806 | let mut encoder = WebPEncoder::new(&mut output); |
| 807 | encoder.set_params(params.clone()); |
| 808 | encoder |
| 809 | .encode(&img[..256 * 256 * 3], 256, 256, crate::ColorType::Rgb8) |
| 810 | .unwrap(); |
| 811 | let decoded = webp::Decoder::new(&output).decode().unwrap(); |
| 812 | assert_eq!(img[..256 * 256 * 3], *decoded); |
| 813 | |
| 814 | let mut output = Vec::new(); |
| 815 | let mut encoder = WebPEncoder::new(&mut output); |
| 816 | encoder.set_params(params.clone()); |
| 817 | encoder |
| 818 | .encode(&img, 256, 256, crate::ColorType::Rgba8) |
| 819 | .unwrap(); |
| 820 | let decoded = webp::Decoder::new(&output).decode().unwrap(); |
| 821 | assert_eq!(img, *decoded); |
| 822 | |
| 823 | let mut output = Vec::new(); |
| 824 | let mut encoder = WebPEncoder::new(&mut output); |
| 825 | encoder.set_params(params.clone()); |
| 826 | encoder.set_icc_profile(vec![0; 10]); |
| 827 | encoder |
| 828 | .encode(&img, 256, 256, crate::ColorType::Rgba8) |
| 829 | .unwrap(); |
| 830 | let decoded = webp::Decoder::new(&output).decode().unwrap(); |
| 831 | assert_eq!(img, *decoded); |
| 832 | |
| 833 | let mut output = Vec::new(); |
| 834 | let mut encoder = WebPEncoder::new(&mut output); |
| 835 | encoder.set_params(params.clone()); |
| 836 | encoder.set_exif_metadata(vec![0; 10]); |
| 837 | encoder |
| 838 | .encode(&img, 256, 256, crate::ColorType::Rgba8) |
| 839 | .unwrap(); |
| 840 | let decoded = webp::Decoder::new(&output).decode().unwrap(); |
| 841 | assert_eq!(img, *decoded); |
| 842 | |
| 843 | let mut output = Vec::new(); |
| 844 | let mut encoder = WebPEncoder::new(&mut output); |
| 845 | encoder.set_params(params); |
| 846 | encoder.set_xmp_metadata(vec![0; 7]); |
| 847 | encoder.set_icc_profile(vec![0; 8]); |
| 848 | encoder.set_icc_profile(vec![0; 9]); |
| 849 | encoder |
| 850 | .encode(&img, 256, 256, crate::ColorType::Rgba8) |
| 851 | .unwrap(); |
| 852 | let decoded = webp::Decoder::new(&output).decode().unwrap(); |
| 853 | assert_eq!(img, *decoded); |
| 854 | } |
| 855 | } |
| 856 | |